ASP.NET 5.トークン認証

ASP.NET WebApiアプリケーションと、このAPIを使用するJavaScriptクライアントアプリケーションを作成する必要がありました。 ASP.NET 5で同時に記述し、新しいリリースを検討することが決定されました。



通常のMVCアプリケーションであれば、Cookieベースの認証を使用しますが、クロスドメインリクエストではCookieの送信は許可されません。 したがって、トークンベースの認証を使用する必要があります。



Microsoftはその実装-JwtBearerAuthenticationを提供しています。 しかし、それを把握するための狩り自体。 そこで、実装を記述することにしました-BearerAuthentication。



ユーザー認証アルゴリズム



ユーザーはユーザー名とパスワードを入力し、AJAXを介してPOSTリクエストでサーバーに送信されます。 サーバーはユーザーを認証し、応答ヘッダーでユーザーに送信するトークンを生成します。 新しいAPIリクエストごとに、クライアントアプリケーションは受信したトークンをリクエストヘッダーで送信する必要があります。 トークンが失われないようにするために、トークンをCookieに保存することができます(はい、再度Cookieですが、現在はクライアント部分でのみ使用されます)。



実装



ASP.NET 5の現在のバージョンはRC1 Update1です。 認証を実装するには、Microsoft.AspNet.Authenticationパッケージが必要です。

以下は、実装する必要がある主なクラスのリストです。



BearerAuthenticationExtensions -IApplicationBuilderインターフェイス拡張のUseBearerAuthenticationメソッドが含まれます。 app.UseMiddleware()メソッドは単にここで呼び出されます。

BearerAuthenticationMiddleware -AuthenticationMiddlewareクラスを継承します。

BearerAuthenticationOptions -AuthenticationOptionsクラスを継承します。

BearerAuthenticationHandler -AuthenticationHandlerクラスを継承し、認証要求を処理するためのメインクラスです。

ヘルパークラス:

BearerAuthenticationDefaults -AuthenticationSchemeおよびHeaderNameという文字列定数が含まれています。

IBearerAuthenticationEventsは、BearerAuthenticationHandlerから呼び出されるメソッドを定義するインターフェイスであり、ミドルウェアの外部で要求を処理できるようにします。 このインターフェイスの実装は、BearerAuthenticationOptionsで指定できます。



BearerAuthenticationOptionsクラスを検討してください。



public class BearerAuthenticationOptions : AuthenticationOptions, IOptions<BearerAuthenticationOptions> { public BearerAuthenticationOptions() { AuthenticationScheme = BearerAuthenticationDefaults.AuthenticationScheme; HeaderName = BearerAuthenticationDefaults.HeaderName; SystemClock = new SystemClock(); Events = new BearerAuthenticationEvents(); } public string HeaderName { get; set; } public ISecureDataFormat<AuthenticationTicket> TicketDataFormat { get; set; } public IDataProtectionProvider DataProtectionProvider { get; set; } public ISystemClock SystemClock { get; set; } public IBearerAuthenticationEvents Events { get; set; } public BearerAuthenticationOptions Value => this; }
      
      





TicketDataFormatは、トークンの暗号化と復号化に使用されます。 TicketDataFormatがパラメーターで渡されない場合、渡されたDataProtectionProviderに基づいて生成されます。 SystemClockは、トークンの有効期限を確認するために現在の日付を取得するために必要です。



BearerAuthenticationMiddlewareクラスには、BearerAuthenticationHandlerクラスの新しいインスタンスを返すオーバーライドされたCreateHandler()メソッドがあります。



次に、 BearerAuthenticationHandlerクラスで認証要求が処理される方法を見てみましょう。 このクラスには、いくつかのオーバーライドされたメソッドが含まれます。



HandleSignInAsync-ここでは、チケット(AuthenticationTicket)を作成し、暗号化して、応答ヘッダーに書き込む必要があります。 チケットはClaimsPrincipal、AuthenticationPropertiesおよびAuthenticationSchemeから形成されます。

HandleSignOutAsync-ここでは、クライアントアプリケーションが空のトークンを受け入れるように、応答ヘッダーにvoidを書き込むだけです。

HandleAuthenticateAsync-リクエストハンドラ-ここでは、ヘッダーからチケットへのトークンを解読し、有効期限を確認する必要があります。

HandleUnauthorizedAsync-未承認のリクエストに401のコードで応答します。

HandleForbiddenAsync-ユーザーがアクセスを拒否されたリクエストに対して、コード403で応答します。

FinishResponseAsync-各要求ハンドラーの後に呼び出されます。



クラスのソースコード:



 public class BearerAuthenticationHandler : AuthenticationHandler<BearerAuthenticationOptions> { private bool _shouldRenew; private AuthenticationTicket GetTicket() { if (!Context.Request.Headers.ContainsKey(Options.HeaderName)) return null; var bearer = Context.Request.Headers[Options.HeaderName]; if (string.IsNullOrEmpty(bearer)) return null; var ticket = Options.TicketDataFormat.Unprotect(bearer); if (ticket == null) return null; var currentUtc = Options.SystemClock.UtcNow; var expiresUtc = ticket.Properties.ExpiresUtc; if (expiresUtc.HasValue && expiresUtc.Value < currentUtc) return null; return ticket; } private void ApplyBearer(AuthenticationTicket ticket) { if (ticket != null) { var protectedData = Options.TicketDataFormat.Protect(ticket); Response.Headers["Access-Control-Expose-Headers"] = Options.HeaderName; Response.Headers[Options.HeaderName] = protectedData; } else { Response.Headers["Access-Control-Expose-Headers"] = Options.HeaderName; Response.Headers[Options.HeaderName] = StringValues.Empty; } } protected override async Task HandleSignInAsync(SignInContext signIn) { var signingInContext = new BearerSigningInContext(Context, Options, signIn.Principal, new AuthenticationProperties(signIn.Properties)); await Options.Events.SigningIn(signingInContext); var ticket = new AuthenticationTicket(signingInContext.Principal, signingInContext.Properties, Options.AuthenticationScheme); ApplyBearer(ticket); var signedInContext = new BearerSignedInContext(Context, Options, signingInContext.Principal, signingInContext.Properties); await Options.Events.SignedIn(signedInContext); } protected override async Task HandleSignOutAsync(SignOutContext context) { var signingOutContext = new BearerSigningOutContext(Context, Options); await Options.Events.SigningOut(signingOutContext); ApplyBearer(null); } protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { var ticket = GetTicket(); if (ticket == null) return AuthenticateResult.Failed("No ticket."); var context = new BearerValidatePrincipalContext(Context, Options, ticket.Principal, ticket.Properties); await Options.Events.ValidatePrincipal(context); if (context.Principal == null) return AuthenticateResult.Failed("No principal."); if (context.ShouldRenew) _shouldRenew = true; return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme)); } protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context) { Response.StatusCode = 401; var unauthorizedContext = new BearerUnauthorizedContext(Context, Options); await Options.Events.Unauthorized(unauthorizedContext); return true; } protected override async Task<bool> HandleForbiddenAsync(ChallengeContext context) { Response.StatusCode = 403; var forbiddenContext = new BearerForbiddenContext(Context, Options); await Options.Events.Forbidden(forbiddenContext); return true; } protected override async Task FinishResponseAsync() { if (!_shouldRenew || SignInAccepted || SignOutAccepted) return; var result = await HandleAuthenticateOnceAsync(); var ticket = result?.Ticket; if (ticket == null) return; ApplyBearer(ticket); } }
      
      





ユーザー認証は、たとえばコントローラーから呼び出すことができます。



 await HttpContext.Authentication.SignInAsync(BearerAuthenticationDefaults.AuthenticationScheme, principal);
      
      





ここで、principalは、HandleSignInAsyncメソッドに渡されるClaimsPrincipalクラスのインスタンスです。



この例は、認証プロセスのみを示しています。 もちろん、たとえば、ITicketStoreのセッションにトークンを保存する機能を追加することにより、このハンドラーを拡張できます。



プロジェクトのソースはGitHubで取得できます。



All Articles