web-dev-qa-db-ja.com

ASP.NET Core 2.1Identityを使用して認証Cookieを検証します

ASP.NET Core 2(Identityの有無にかかわらず)でCookie認証を使用すると、ユーザーの電子メールまたは名前が変更されたり、Cookieの存続期間中にアカウントが削除されたりする場合があります。そのため、 docs は、Cookieを検証する必要があると指摘しています。ドキュメントの例はコメントされています

ここで説明するアプローチは、すべてのリクエストでトリガーされます。これにより、アプリのパフォーマンスが大幅に低下する可能性があります。

だから私はクッキープリンシパルを検証するための最良のパターンは何であるか疑問に思っています。 Startup.csで行ったことは、OnValidatePrincipalイベントにサブスクライブし、プリンシパルの有効性を確認することです。次のように、CookieにLastValidatedOnクレームを追加して5分ごとに:

services.ConfigureApplicationCookie(options =>
{
    // other cookie options go here

    options.Events.OnValidatePrincipal = async context =>
    {
        const string claimType = "LastValidatedOn";
        const int reValidateAfterMinutes = 5;

        if (!(context.Principal?.Identity is ClaimsIdentity claimIdentity)) return;

        if (!context.Principal.HasClaim(c => c.Type == claimType) ||
            DateTimeOffset.Now.UtcDateTime.Subtract(new DateTime(long.Parse(context.Principal.Claims.First(c => c.Type == claimType).Value))) > TimeSpan.FromMinutes(reValidateAfterMinutes))
        {
            var mgr = context.HttpContext.RequestServices.GetRequiredService<SignInManager<ApplicationUser>>();
            var user = await mgr.UserManager.FindByNameAsync(claimIdentity.Name);
            if (user != null && claimIdentity.Claims.FirstOrDefault(c => c.Type == "AspNet.Identity.SecurityStamp")?.Value == await mgr.UserManager.GetSecurityStampAsync(user))
            {
                claimIdentity.FindAll(claimType).ToList().ForEach(c => claimIdentity.TryRemoveClaim(c));
                claimIdentity.AddClaim(new Claim(claimType, DateTimeOffset.Now.UtcDateTime.Ticks.ToString(), typeof(long).ToString()));
                context.ShouldRenew = true;
            }
            else
            {
                context.RejectPrincipal();
                await mgr.SignOutAsync();
            }
        }
    };
});
7
axuno

@MarkGは私を正しい方向に向けてくれました、ありがとう。 ソースコードSecurityStampValidatorIdentityを詳しく調べたところ、状況が明らかになりました。実際、質問とともに投稿したサンプルコードは不要です。これは、ASP.NET CoreIdentityが機能をすぐに使用できるようにするためです。

このような要約はまだ見つかりませんでしたので、他の人にも役立つかもしれません。

認証Cookieの検証とは何の関係もありません

...しかしそれでも知っておくと良い...

_services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Expiration = TimeSpan.FromDays(30);
    options.ExpireTimeSpan = TimeSpan.FromDays(30);
    options.SlidingExpiration = true;
});
_

ExpireTimeSpan

デフォルトはTimeSpan.FromDays(14)です。

認証チケットの発行時間はCookie(_CookieValidatePrincipalContext.Properties.IssuedUtc_)の一部です。 Cookieがサーバーに返送されるとき、現在の時刻から発行時刻を引いた値は、ExpireTimeSpanよりも大きい必要があります。そうでない場合、ユーザーはそれ以上の調査なしにサインアウトされます。実際には、ExpireTimeSpanを設定すると、ほとんどの場合、SlidingExpirationtrueに設定します。これは、ユーザーがアプリを積極的に操作していることを確認するための手段であり、デバイスを無人のままにします。負のTimeSpansは、ユーザーをすぐにサインオフします(ただし、_TimeSpan.Zero_はサインオフしません)。

認証Cookieの検証を制御するために必要なもの

_services.AddOptions();
services.Configure<SecurityStampValidatorOptions>(options =>
{
    // This is the key to control how often validation takes place
    options.ValidationInterval = TimeSpan.FromMinutes(5);
});
_

ValidationInterval

デフォルトはTimeSpan.FromMinutes(30)です。

これにより、認証Cookieの有効性が永続ストレージに対してチェックされるまでの期間が決まります。これは、サーバーへのすべてのリクエストに対してSecurityStampValidatorを呼び出すことによって実現されます。現在の時刻からCookieの発行時刻を引いた値が以下ValidationIntervalの場合、ValidateSecurityStampAsyncが呼び出されます。つまり、_ValidationInterval = TimeSpan.Zero_は、リクエストごとにValidateSecurityStampAsyncを呼び出すことになります。

UserManagerはセキュリティスタンプの取得をサポートする必要があります。サポートしていないと、失敗します。カスタムユーザーマネージャーまたはユーザーストアの場合、両方が_IUserSecurityStampStore<TUser>_を適切に実装する必要があります。

Startupでのサービスの読み込みシーケンス

注意すべき点は次のとおりです。services. AddIdentity()は、認証Cookieのデフォルトも設定します。 services.ConfigureApplicationCookie()の後に追加すると、以前の設定が上書きされます。上記の前の関数の後にservices.Configure<SecurityStampValidatorOptions>()を呼び出しました。

道を示してくれた@MarkGにもう一度感謝します。

12
axuno