JWT はJSON Web Tokenの略で、認証ではなく承認メカニズムです。それでは、これら 2 つの違いは何かを考えてみましょう。
認証は、ユーザーが本人であることを確認できるメカニズムです。これは、ユーザーがユーザー名とパスワードを入力し、システムがそれらを検証するログイン プロセスです。したがって、認証は「ユーザーは誰なのか?」という質問に答えます。
認可は、ユーザーが特定のリソースに対してどのアクセス権を持っているかを検証できるメカニズムです。これは、ユーザーにいくつかの役割と、特定の役割が持つ一連の権限を付与するプロセスです。したがって、承認は、ユーザーがシステム内でどのような権限を持っているかという質問に答えます。
常に認証が最初に行われ、認可が 2 番目に行われることを理解することが重要です。つまり、本人確認をするまでは許可を得ることができないのです。しかし、最も一般的な認証方法は何でしょうか? Web アプリケーションの承認を処理するには、主に 2 つのアプローチがあります。
認証ユーザーに対する Web 上の従来のアプローチは、Cookie ベースのサーバー側セッションです。このプロセスは、ユーザーがログインし、サーバーがユーザーを認証すると開始されます。その後、サーバーはセッション ID を持つセッションを作成し、それをサーバーのメモリのどこかに保存します。サーバーはセッション ID をクライアントに送り返し、クライアントはセッション ID を Cookie に保存します。すべてのリクエストに対して、クライアントはリクエストの一部としてセッション ID を送信し、サーバーはメモリ内のセッション ID と、このセッションに関連するユーザーの権限を確認します。
もう 1 つの一般的なアプローチは、承認にトークンを使用することです。このプロセスは、ユーザーがログインとパスワードを入力し、クライアントがサーバーにログイン要求を送信すると同様に開始されます。セッションを作成する代わりに、サーバーはシークレット トークンで署名されたトークンを生成します。次に、サーバーはトークンをクライアントに送り返し、クライアントはそれをローカル ストレージに保存する必要があります。セッションベースのアプローチと同様に、クライアントはリクエストごとにトークンをサーバーに送信する必要があります。ただし、サーバーはユーザー セッションに関する追加情報を保存しません。サーバーは、トークンが作成されて秘密鍵で署名されてから変更されていないことを検証する必要があります。
セッションベースの認証アプローチは、クロスサイト リクエスト フォージェリ (CSRF) として知られる攻撃に対して脆弱になる可能性があります。これは、攻撃者がログインしているサイトを参照して、支払いの送信やパスワードの変更など、意図しないアクションを実行する一種の攻撃です。
もう 1 つは、セッション ベースの承認アプローチを使用すると、クライアントとサーバーの間にステートフル セッションが作成されることです。問題は、クライアントが同じアプリケーションのスコープ内の異なるサーバーにアクセスしたい場合、それらのサーバーがセッション状態を共有する必要があることです。別のケースでは、セッションが異なるため、クライアントは各サーバーで認証される必要があります。
一方、トークンベースの認証アプローチでは、サーバー側にセッション データを保存する必要がなく、複数のサーバー間の認証が簡素化される可能性があります。
ただし、トークンは依然として攻撃者によって盗まれる可能性があり、トークンを無効にすることも困難な場合があります。この記事では、詳細と無効化の処理方法について詳しく説明します。
JSON Web Token (JWT) は、関係者間で情報を JSON オブジェクトとして安全に送信するためのコンパクトで自己完結型の方法を定義するオープン スタンダードです。この情報はデジタル署名されているため、検証および信頼できます。 JWT は、シークレット ( HMACアルゴリズムを使用)、またはRSAまたはECDSAを使用した公開/秘密キーのペアを使用して署名できます。
JSON Web トークンは、ドットで区切られた 3 つの部分で構成されます.
{ "alg": "HS256", "typ": "JWT" }
ヘッダーは通常、トークンのタイプと使用されている署名アルゴリズムの 2 つの部分で構成されます。
{ "sub": "1234567890", "name": "John Doe", "admin": true }
ペイロードには、ユーザーに関するステートメントであるクレームが含まれています。ペイロードはBase64Url でエンコードされて、JSON Web トークンの 2 番目の部分を形成します。クレームとして使用される標準フィールドの説明は、ここで見つけることができます。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
署名部分を作成するには、エンコードされたヘッダー、エンコードされたペイロード、シークレット、およびヘッダーで指定されたアルゴリズムを取得し、署名する必要があります。
通常、トークンは次のようになります。
xxxxx.yyyyy.zzzzz
jwt.ioに移動して、サンプル トークンまたは独自のトークンをデバッグできます。トークンを「エンコード済み」フィールドに貼り付けて、トークン署名のアルゴリズムを選択するだけです。
JWT がどのように機能するかについての理論的な知識が得られたので、それを実際のプロジェクトに適用できます。コーヒー エンティティの CRUD 操作を表す単純な API があると仮定します。 Coffee API を表す ASP.NET Core API プロジェクトを作成します。その後、JWT を生成できる Identity API を表す別の ASP.NET Core API プロジェクトを作成します。実際には、認証/認可の目的でIdentity ServerまたはOkta 、またはAuth0を使用することになるでしょう。ただし、JWT の生成方法を示すために独自の Identity API を作成します。 Identity API が完了したら、そのコントローラーを呼び出し、ユーザーのデータに基づいて JWT を生成できます。また、リクエストごとに JWT を渡すことを必要とする認可構成を使用して、Coffee API を保護することもできます。
まず、Coffee API を表す単純な ASP.NET Core API プロジェクトを作成します。このプロジェクトの構造は次のとおりです。
Model
フォルダー内のCoffee.cs
から始めましょう。これは、 Id
プロパティとName
プロパティを持つ単純なエンティティです。
namespace Hackernoon.Coffee.API.Model; public class Coffee { public int Id { get; set; } public string Name { get; set; } }
API の操作中にエンティティを保存する必要があります。そこで、簡単なインメモリストレージを紹介しましょう。これは、 Data
フォルダーのStorage.cs
ファイルにあります。
namespace Hackernoon.Coffee.API.Data; public static class Storage { private static readonly List<Model.Coffee> Data = new(); public static List<Model.Coffee> GetAll() { return Data; } public static bool Create(Model.Coffee model) { if (Data.Any(c => c.Id == model.Id || c.Name == model.Name)) return false; Data.Add(new Model.Coffee { Id = model.Id, Name = model.Name }); return true; } public static bool Delete(int id) { if (Data.All(c => c.Id != id)) return false; Data.Remove(Storage.Data.First(c => c.Id == id)); return true; } public static bool Update(Model.Coffee model) { if (Data.All(c => c.Id != model.Id)) return false; Data.First(c => c.Id == model.Id).Name = model.Name; return true; } }
Coffee API へのリクエストを表すクラスが必要です。そこで、 Contracts
フォルダーにCoffeeRequest.cs
を作成しましょう。
namespace Hackernoon.Coffee.API.Contracts; public class CoffeeRequest { public int Id { get; set; } public string Name { get; set; } }
それが完了すると、コーヒー エンティティの CRUD 操作を表すCoffeeController.cs
Controller
フォルダーに実装できます。
using Hackernoon.Coffee.API.Contracts; using Hackernoon.Coffee.API.Data; using Microsoft.AspNetCore.Mvc; namespace Hackernoon.Coffee.API.Controllers; [Route("coffee")] [ApiController] public class CoffeeController : ControllerBase { [HttpGet] public IList<Model.Coffee> GetAll() { return Storage.GetAll(); } [HttpPost] public IActionResult Create([FromBody]CoffeeRequest request) { var model = new Model.Coffee { Id = request.Id, Name = request.Name }; if (!Storage.Create(model)) return new BadRequestResult(); return new OkResult(); } [HttpDelete] public IActionResult Delete(int id) { if (!Storage.Delete(id)) return new BadRequestResult(); return new OkResult(); } [HttpPut] public IActionResult Update([FromBody] CoffeeRequest request) { var model = new Model.Coffee() { Id = request.Id, Name = request.Name }; if (!Storage.Update(model)) return new BadRequestResult(); return new OkResult(); } }
Coffee API が完成したので、プロジェクトを実行して次のように Swagger UI を確認できます。
Identity API を表す別の ASP.NET Core API プロジェクトを作成してみましょう。このプロジェクトの構造は次のとおりです。
まず、 Contracts
フォルダー内のTokenGenerationRequest.cs
から始めましょう。これは、 Email
プロパティとPassword
プロパティを含む新しい JWT の生成のリクエストを表します。
namespace Hackernoon.Identity.API.Contracts; public class TokenGenerationRequest { public string Email { get; set; } public string Password { get; set; } }
JWT 生成のロジックを表すTokenController.cs
のみを実装する必要があります。ただし、その前にMicrosoft.AspNetCore.Authentication.JwtBearer
NuGet パッケージをインストールする必要があります。
using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Hackernoon.Identity.API.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace Hackernoon.Identity.API.Controllers; [Route("token")] public class TokenController : ControllerBase { private const string SecretKey = "VerySecretAndLongKey-NeedMoreSymbolsHere-123"; private const string Issuer = "IdentityServerIssuer"; private const string Audience = "IdentityServerClient"; private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(20); [HttpPost] public string Create([FromBody]TokenGenerationRequest request) { var claims = new List<Claim> {new Claim(ClaimTypes.Email, request.Email) }; var jwt = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.Add(Lifetime), signingCredentials: CreateSigningCredentials()); return new JwtSecurityTokenHandler().WriteToken(jwt); } private static SigningCredentials CreateSigningCredentials() { return new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)), SecurityAlgorithms.HmacSha256); } }
SecretKey
、 Issuer
、 Audience
などの機密性の高い const は構成内のどこかに配置する必要があることに注意してください。これらは、このテスト プロジェクトを簡素化するためだけにハードコーディングされています。 Lifetime
フィールドは 20 分に設定されています。これは、トークンがその時間有効であることを意味します。このパラメータを構成することもできます。
これで、プロジェクトを実行し、次のように Swagger UI を確認できるようになります。
/token
エンドポイントを呼び出して、新しい JWT を生成しましょう。次のペイロードを試してください。
{ "email": "[email protected]", "password": "password" }
Identity API は、対応する JWT を生成します。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJJc0dvdXJtZXQiOiJmYWxzZSIsImV4cCI6MTcwNzc4Mzk4MCwiaXNzIjoiSWRlbnRpdHlTZXJ2ZXJJc3N1ZXIiLCJhdWQiOiJJZGVudGl0eVNlcnZlckNsaWVudCJ9.4odXsbWak1C0uK3Ux-n7f58icYQQwlHjM54OjgMCVPM
Identity API の準備が整い、トークンが提供されると、承認によって Coffee API を保護できるようになります。再度、 Microsoft.AspNetCore.Authentication.JwtBearer
NuGet パッケージをインストールする必要があります。
認証サービスにより必要なサービスを登録する必要があります。ビルダーを作成した直後に、次のコードをProgram.cs
ファイルに追加します。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "IdentityServerIssuer", ValidateAudience = true, ValidAudience = "IdentityServerClient", ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("VerySecretAndLongKey-NeedMoreSymbolsHere-123")), ValidateIssuerSigningKey = true, }; }); builder.Services.AddAuthorization();
ミドルウェア内の順序が重要であることを覚えておくことが重要です。 AddAuthentication()
メソッドを呼び出し、認証スキーマとしてJwtBearerDefaults.AuthenticationScheme
を指定することで、認証を有効にします。これは、 Bearer
値を含む定数です。
namespace Microsoft.AspNetCore.Authentication.JwtBearer { /// <summary>Default values used by bearer authentication.</summary> public static class JwtBearerDefaults { /// <summary> /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions /// </summary> public const string AuthenticationScheme = "Bearer"; } }
認可中に JWT のどのパラメータが検証されるかを記述するTokenValidationParameters
を指定する必要があります。また、Identity API のsigningCredentials
と同様にIssuerSigningKey
を指定して、JWT 署名を検証します。 TokenValidationParameters
の詳細については、こちらをご覧ください。
次のコード部分は、認証および認可機能を有効にするミドルウェアをビルダーに追加します。 UseHttpsRedirection()
メソッドとMapControllers()
メソッドの間に追加する必要があります。
app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();
これで、コントローラーまたはそのアクションに対してAuthorize
属性を使用できるようになりました。このコードを適用すると、 CoffeeController
のすべてのアクションが承認メカニズムで保護され、JWT をリクエストの一部として送信する必要があります。
[Route("coffee")] [ApiController] [Authorize] public class CoffeeController : ControllerBase { ..
Coffee API のエンドポイントを呼び出すと、 HttpContext.User
デバッグして、JWT で指定したクレームを含むIdentity
が設定されていることを確認できます。これは、ASP.NET Core が内部で承認をどのように処理するかを理解する上で重要です。
私たちは認可によって Coffee API を保護するために素晴らしい仕事をしました。ただし、Coffee API プロジェクトを実行して Swagger UI を開いた場合、リクエストの一部として JWT を送信することはできません。これを修正するには、次のコードでProgram.cs
ファイルを更新する必要があります。
builder.Services.AddSwaggerGen(option => { option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); option.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); });
その後、右上隅に[承認]ボタンが表示されるようになります。
「承認」ボタンをクリックすると、次のように JWT を入力できるようになります。
Swagger UI の使用に限定する必要はなく、Postman ツールを通じて API のテストを実行できます。まず、Identity API の/token
エンドポイントを呼び出してみましょう。 JSON をペイロードとして使用するため、 Headersセクションで値application/json
を使用してContent-Type
ヘッダーを指定する必要があります。
その後、 /token
エンドポイントを呼び出して、新しい JWT を取得できます。
これで、JWT をコピーし、それを使用して Coffee API を呼び出すことができます。エンドポイントをテスト、作成、更新する場合は、Identity API と同様のContent-Type
ヘッダーを指定する必要があります。 Authorization
ヘッダーにも値Bearer [your JWT value]
を設定する必要があります。その後、送信ボタンを押すだけで結果が表示されます。
覚えているとおり、JWT のペイロード部分は、正確にキーと値のペアの値を持つクレームのセットです。ロールベースの認可を使用すると、ユーザーが属するロールに応じてアプリケーション リソースへのアクセスを区別できます。
Identity API のTokenController.cs
ファイルのCreate()
メソッドを、ロールの新しいクレームを追加するコードで更新するとします。 Coffee API でロールベースの認証を処理できます。 ClaimTypes.Role
、ロール要求の事前定義された名前です。
var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim(ClaimTypes.Role, "Barista") };
CoffeeController.cs
ファイルのAuthorize
属性を更新して、ロール名を指定します。
[Authorize(Roles = "Barista")]
現在、Coffee API を呼び出すすべてのユーザーは、 Barista
値を含むロール要求を持っている必要があります。そうしないと、 403 Forbidden
ステータス コードが返されます。
Authorize
属性は、ロールベースの認証を簡単に処理できます。しかし、それだけでは不十分で、年齢などのユーザーのプロパティに基づいてアクセスを区別したい場合はどうすればよいでしょうか?おそらく、クレームを JWT に追加し、それを使用して承認ロジックを構築できることはすでに推測されているでしょう。ロールが事前定義されたタイプの同じクレーム オブジェクトであるのと同様、ロール ベースの承認自体はクレーム ベースの承認の特殊なケースです。
新しいクレームIsGourmet
を追加するコードを使用して、Identity API のTokenController.cs
ファイルのCreate()
メソッドを更新しましょう。
var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim("IsGourmet", "true") };
Coffee API の Program.cs ファイルでは、クレームを検証し、 Authorize
属性で使用できるポリシーを作成する必要があります。次のコードは、 AddAuthentication()
メソッド呼び出しの直後に追加する必要があります。
builder.Services.AddAuthorization(opts => { opts.AddPolicy("OnlyForGourmet", policy => { policy.RequireClaim("IsGourmet", "true"); }); });
CoffeeController.cs
ファイルのAuthorize
属性を更新して、ポリシー名を指定します。
[Authorize(Policy = "OnlyForGourmet")]
おめでとう! .NET での JWT の学習に多大な努力を払いました。ここで、JWT の原則と、.NET アプリケーションで承認を実行するために JWT を使用することが重要である理由をしっかりと理解する必要があります。ただし、ASP.NET Core アプリケーションの認証と承認の領域では表面をなぞっただけです。
この記事で説明したトピックに関する Microsoft のドキュメントを参照することをお勧めします。 .NET プラットフォームには、承認とロール管理のための組み込み機能も多数あります。この記事に追加するとよいのは、承認に関する Microsoft のドキュメントです。