ASP.NETコアウェブアピ之JWT VSセッションVSクッキー(二)

序文

本論文では、問題があまりにも激しい議論のコメントを見て、あまりにもこれについて考えることではない、JWT VSセッションで問題を議論、それが、これは、ブログに利点です勉強や方法によって、まとめるには少し時間がかかりました書き込みブログの記事は、いくつかの経験であってもよいし、一部が共有するように学習することができるが、あなたはより多くのまたはより良いアイデアを持っている記事を見て免れることはできない、との交換から、それ自体は、なぜそれをしない、より多くを得ることができます?私は、これはより多くの疑問や交換を得ることができると思います。私たちは、スローの問題点を指示することができます。サービス側よりも良い、それを維持するため、クライアント側のストレージJWTセッションを使うのか? 

共通セッションベースJWTと認定

あなたはJWT VSセッションを比較したいので、我々はあなたが問題を解決するために一緒にいる、JWTとセッションを必要とする理由を知っている必要がありますか?その後、我々はシーンで始まり、オンラインショッピングは、私たちは、カートに項目で、その後、他の商品は、まだショッピングカートに商品を必要とする前に、この時点で選択したページにジャンプしますと、しかし、今当たり前の事です共通してHTTPのステートレスなので、JWTのセッションは、セッションの永続性は、HTTPのステートレスな状況を克服するために、存在を維持するように設計されているので、あなたは、JWTとセッションはそれに対処する方法をされているセッションを維持する必要がありますか?

 

JWT VSセッション認証

セッション:アプリケーションシステムにおけるユーザ・ログは、この時間は、サーバーが(我々はまた、セッションと呼ばれる)のセッションを作成すると、その後のSessionIdは限り、ユーザが要求ごとにログに記録されるよう、クッキー、クッキーの使用者に保存されますSessionIdは、サーバがユーザのアイデンティティと応答を認証するために、比較のSessionIdのメモリ内のSessionIdとクッキーが保存されます、サーバーに送信されます。

JWT:アプリケーション・システムのユーザがログインすると、サーバはJWT、JWTを作成し、クライアントに送信し、クライアント・ストア(典型的にはローカルストレージに)JWT認可に含まれる要求ヘッダーの各一方JWTは、要求ごとに、サーバは、ローカルに認証するために、直接サーバに、JWTの正当性を検証する、など発行者、受付など、ネットワーク要求またはデータベースと対話を発行せずに、このアプローチは使用することができるようによりセッション速く、これ応答性能を高速化、サーバーとデータベースサーバーの負荷を軽減。

 

JWTのセッションの認証および証明の上記の簡単な説明で、我々は2の最大の違いは、JWTは、クライアントに保存されている間のセッションは、サーバー側のストレージであるということであることを知っています。サーバーストアセッションが2未満のものではなく、1セッション識別子であるが、1つのセッションを維持するために、メモリに格納され、データベースに格納され、私はほとんどの場合、セッションを維持するために、メモリに基づいていると思うが、これはもたらしますいくつかの問題、多数のユーザがシステムにアクセスする場合は、クッキーの一般的ながら、メモリベースのセッションを使用して、この時間は、展開のレベルを維持するために制限されているが、このような問題は、トークンベースの認証が存在しないことを意味し、大流量システムがある場合サードパーティクッキーは、お使いのブラウザはクッキーを無効にする可能性がある場合、クロスドメインのために、それはまた、ブラウザの制限の影響を受けている場合にのみは、単一のドメインまたはサブドメインに適用されますが、それはリクエストヘッダに格納されているため、トークン認証は、問題ではありませんインチ

 

私たちは、クライアントに転送された場合、それはトークン認証の使用は、この時間は持ち上げるセッションはブラウザの制限なしに、水平にスケーラブルまた、サービス終了に依存しますが、と言うことですが、同時にいくつかの問題をもたらすでしょう、まず、トランスポート・セキュリティトークン送信セキュリティトークンは、我々は解決するために、HTTPS暗号化されたチャネルを使用することができ、そして第二に、セッションIDでの比較クッキーに格納されたJWTは、ユーザ情報が含まれているため、JWTは、明らかにはるかに大きいですしたがって、この問題を解決するために、我々は、JWTは、必要な情報のみ(ほとんどの場合にのみ、サブおよびその他の重要な情報が含まれている)、機密情報のために、我々はXSS攻撃を防ぐために省略されなければならないが含まれていることを確認してみてください。JWTは、それは我々が、データベースの負荷を軽減し、ユーザー情報JWTを埋め込むことができますされ、コア文は、文はJWTでJSONデータであるということです。だから、JWTは、既存の問題や他のセッションの欠点を解決する合計:

より柔軟な

より安全な

スケーラブルのレベルを達成するために、データベース・ラウンドトリップを減らします。

耐タンパー性のクライアントの文

モバイルデバイス上でより良い仕事することができます

クッキーのをブロックするユーザーの場合

有効期間内でJWTの利点を要約すると、完全にJWTは明らかに理不尽で否定することは必須で無効化された機能ではありません、もちろん、それは他のタイプの認証を達成するために多くの制限など全く反論できない使用がない場合は、完全に合理的かつ正当なものであるということです、トレードオフの必要性は、再び結論の死を明らかにしません。これまでのところ、我々はそのようなタイトルとして、代わりにJWT VSクッキー認定の、JWT VSセッション認証について話されているが、我々はまた、クッキーに含まれる、学習者はただJWT VSクッキー認証は、この文があるので、混同されないようにします間違った、クッキーは単にメディアストレージ及び情報の送信があり、我々は唯一のクッキーの保存および伝送JWTを言うことができます。次はクッキーの保存と送信JWTトークンを実現するために来ます。

 

クッキーIDクレームAS JWT

スタートアップでは私たちは、その後、私たちはクッキーの下の設定オプションのいくつかを理解する必要があり、以下のクッキー認証ミドルウェアを追加し、ブラウザに現れCookie認証ミドルウェアを知らせるために、これらのオプションを設定することができ、我々はいくつかを見てセキュリティオプションに関連する1つ。

           services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
           .AddCookie(オプション => 
           { 
               options.LoginPath = " /アカウント/ログイン" ; 
               options.LogoutPath = " /アカウント/ログアウト" ; 
               options.Cookie.Expiration = TimeSpan.FromMinutes(5 )。
               options.Cookie.HttpOnly = ; 
               options.Cookie.SecurePolicy = CookieSecurePolicy.None; 
               options.Cookie.SameSite =SameSiteMode.Lax; 
           });

HttpOnlyのCookieの設定は、サーバーの使用をマークしたが、前面から直接アクセスされていません。

SecurePolicyは、クッキーはHTTPSで制限し、本番環境では、このパラメータを設定します設定すると、HTTPSを推奨しサポートしています。

SameSiteを設定すると、クッキー認証は、制限するように設定されている場合は、外部リンクは、そのようなセッションを維持するために、POSTリクエストを発行リダイレクトできるように、ラックスに設定することができOAuth認証のためならば、ブラウザは、クッキークロスサイトリクエストと併せて使用することができるかどうかを示すために使用されますクッキーの認定は、単一のサイトに適用されるので、[なし]に設定している場合、それはクッキーヘッダの値を設定しません。(注:Google、FirefoxでSameSiteプロパティが達成されている、IE11のためにサポートすることではないようで、Safariがバージョン12.1で始まることは、このプロパティをサポートしています)

次のように、ConfigureServices方法で、.NETのコアのデフォルトのWebアプリケーション、ミドルウェア直接クッキーグローバル戦略の構成を作成する場合:

            services.Configure <CookiePolicyOptions>(オプション=> 
            { 
                options.CheckConsentNeeded =コンテキスト=> ; 
                options.MinimumSameSitePolicy = SameSiteMode.None; 
            });

次のようにもちろん、だけでなく、その戦略に使用される設定方法では、デフォルトの設定のクッキーグローバル戦略は次のとおりです。

            app.UseCookiePolicy();

次のように我々はまた、直接、戦略に対応するパラメータを設定するには、クッキーポリシーミドルウェアを使用して上記のメソッドを呼び出すことができます。

我々はまたのconfigureグローバルポリシークッキークッキーミドルウェア同じ時間を追加した場合、我々はHttpOnlyのためのプロパティを見つけるとSameSiteは次のように、カバーの状況が存在します。この時の個人的な推測で設定することができます。

 

我々は、このような機能を追加しなければならなかった各コントローラの特性[Authroize]に追加する必要があり、認定のコントローラの必要性は、私は靴のほとんどはとても乾燥していると信じています。実際には、我々は匿名でアクセスすることができ、我々はプロパティを追加することができ、認証を必要とせずに、逆の操作を持つことができ、我々は次のように認定コントローラは、世界的に認定フィルタを構成している必要があります。

services.AddMvc(オプション=> options.Filters.Add(新しいAuthorizeFilter()) 
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)。

さて、ここでは、と私たちは特徴点を研究するために、あまりにも心の奥底ではない、ミドルウェアクッキークッキーグローバルコンフィギュレーションパラメータおよびポリシーにラフ下の手順を説明し、他の問題は、それのより具体的な分析に遭遇しました。トピックに戻る続行、安全率の面でAPIへのアクセスに比べて低いクッキー認証JWTので、我々は使用するためにクッキー認証でJWTを組み合わせることができます。具体的にどのように我々はそれをしようとすることができますか?次のようにアイデンティティ情報文の中にそれを入れて、私はそれが実現可能な方法であるべきだと思う、私たちは、ログインとログアウトをシミュレートしようとすると、おそらくコードは次のとおりです。

    public class AccountController : Controller
    {
        /// <summary>
        /// 登录
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> Login()
        {            
            var claims = new Claim[]
            {
                new Claim(ClaimTypes.Name, "Jeffcky"),
                new Claim(JwtRegisteredClaimNames.Email, "[email protected]"),
                new Claim(JwtRegisteredClaimNames.Sub, "D21D099B-B49B-4604-A247-71B0518A0B1C"),
                new Claim("access_token", GenerateAccessToken()),
            };

            var claimsIdentity = new ClaimsIdentity(
                claims, CookieAuthenticationDefaults.AuthenticationScheme);

            var authticationProperties = new AuthenticationProperties();

            await HttpContext.SignInAsync(
              CookieAuthenticationDefaults.AuthenticationScheme,
              new ClaimsPrincipal(claimsIdentity),
              authticationProperties);

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

        string GenerateAccessToken()
        {
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456"));

            var token = new JwtSecurityToken(
                issuer: "http://localhost:5000",
                audience: "http://localhost:5001",
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddHours(1),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );

            return new JwtSecurityTokenHandler().WriteToken(token);
        }

        /// <summary>
        /// 退出
        /// </summary>
        /// <returns></returns>
        [Authorize]
        [HttpPost]
        public async Task<IActionResult> Logout()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

    }

上述代码很简单,无需我再多讲,和Cookie认证无异,只是我们在声明中添加了access_token来提高安全性,接下来我们自定义一个Action过滤器特性,并将此特性应用于Action方法,如下:

    public class AccessTokenActionFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var principal = context.HttpContext.User as ClaimsPrincipal;

            var accessTokenClaim = principal?.Claims
              .FirstOrDefault(c => c.Type == "access_token");

            if (accessTokenClaim is null || string.IsNullOrEmpty(accessTokenClaim.Value))
            {
                context.HttpContext.Response.Redirect("/account/login", permanent: true);

                return;
            }

            var sharedKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456"));

            var validationParameters = new TokenValidationParameters
            {
                ValidateAudience = true,
                ValidIssuer = "http://localhost:5000",
                ValidAudiences = new[] { "http://localhost:5001" },
                IssuerSigningKeys = new[] { sharedKey }
            };

            var accessToken = accessTokenClaim.Value;

            var handler = new JwtSecurityTokenHandler();

            var user = (ClaimsPrincipal)null;

            try
            {
                user = handler.ValidateToken(accessToken, validationParameters, out SecurityToken validatedToken);
            }
            catch (SecurityTokenValidationException exception)
            {
                throw new Exception($"Token failed validation: {exception.Message}");
            }

            base.OnActionExecuting(context);
        }
    }

JWT Combine Cookie Authentication

如上是采用将JWT放到声明的做法,我想这么做也未尝不可,至少我没找到这么做有什么不妥当的地方。我们也可以将Cookie认证和JWT认证进行混合使用,只不过是在上一节的基础上添加了Cookie中间件罢了,如下图:

通过如上配置后我们就可以将Cookie和JWT认证来组合使用了,比如我们在用户登录后,如下图点击登录后显示当前登录用户名,然后点击退出,在退出Action方法上我们添加组合特性:

        /// <summary>
        /// 退出
        /// </summary>
        /// <returns></returns>
        [Authorize(AuthenticationSchemes = "Bearer,Cookies")]
        [HttpPost]
        public async Task<IActionResult> Logout()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

在上一节中,我们通过获取AccessToken,从而访问端口号为5001的客户端来获取当前时间,那现在我们针对获取当前时间的方法添加上需要Cookie认证,如下:

        [Authorize(CookieAuthenticationDefaults.AuthenticationScheme)]
        [HttpGet("api/[controller]")]
        public string GetCurrentTime()
        {
            var sub = User.FindFirst(d => d.Type == JwtRegisteredClaimNames.Sub)?.Value;

            return DateTime.Now.ToString("yyyy-MM-dd");
        }

Cookie认证撤销

在.NET Core 2.1版本通过Cookie进行认证中,当用户与应用程序进行交互修改了信息,需要在cookie的整个生命周期,也就说在注销或cookie过期之前看不到信息的更改时,我们可通过cookie的身份认证事件【撤销身份】来实现这样的需求,下面我们来看看。

    public class RevokeCookieAuthenticationEvents : CookieAuthenticationEvents
    {
        private readonly IDistributedCache _cache;

        public RevokeCookieAuthenticationEvents(
          IDistributedCache cache)
        {
            _cache = cache;
        }

        public override Task ValidatePrincipal(
          CookieValidatePrincipalContext context)
        {
            var userId = context.Principal?.Claims
            .First(c => c.Type == JwtRegisteredClaimNames.Sub)?.Value;

            if (!string.IsNullOrEmpty(_cache.GetString("revoke-" + userId)))
            {
                context.RejectPrincipal();

                _cache.Remove("revoke-" + userId);
            }

            return Task.CompletedTask;
        }
    }

我们通过重写CookieAuthenticationEvents事件中的ValidatePrincipal,然后判断写在内存中关于用户表示是否存在,若存在则调用 context.RejectPrincipal() 撤销用户身份。然后我们在添加Cookie中间件里配置该事件类型以及对其进行注册:

 services.AddScoped<RevokeCookieAuthenticationEvents>();

接下来我们写一个在页面上点击【修改信息】的方法,并在内存中设置撤销指定用户,如下:

        [HttpPost]
        public IActionResult ModifyInformation()
        {
            var principal = HttpContext?.User as ClaimsPrincipal;

            var userId = principal?.Claims
              .First(c => c.Type == JwtRegisteredClaimNames.Sub)?.Value;

            if (!string.IsNullOrEmpty(userId))
            {
                _cache.SetString("revoke-" + userId, userId);
            }
            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

从如上动图中我们可以看到,当点击修改信息后,然后将撤销的用户标识写入到内存中,然后跳转到Index页面,此时调用我们写的撤销事件,最终重定向到登录页,且此时用户cookie仍未过期,所以我们能够在左上角看到用户名,不清楚这种场景在什么情况下才会用到。

重定向至登录携带或移除参数

当我们在某个页面进行操作时,若此时Token或Cookie过期了,此时则会自动引导用户且将用户当前访问的URL携带并重定向跳转到登录页进行登录,比如关于博客园如下跳转URL:

https://account.cnblogs.com/signin?returnUrl=http%3a%2f%2fi.cnblogs.com%2f

但是如果我们有这样的业务场景:用于跳转至登录页时,在URL上需要携带额外的参数,我们需要获取此业务参数才能进行对应业务处理,那么此时我们应该如何做呢?我们依然是重写CookieAuthenticationEvents事件中的RedrectToLogin方法,如下:

    public class RedirectToLoginCookieAuthenticationEvents : CookieAuthenticationEvents
    {
        private IUrlHelperFactory _helper;
        private IActionContextAccessor _accessor;
        public RedirectToLoginCookieAuthenticationEvents(IUrlHelperFactory helper,
            IActionContextAccessor accessor)
        {
            _helper = helper;
            _accessor = accessor;
        }

        public override Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> context)
        {
            //获取路由数据
            var routeData = context.Request.HttpContext.GetRouteData();

            //获取路由数据中的路由值
            var routeValues = routeData.Values;

            var uri = new Uri(context.RedirectUri);

            //解析跳转URL查询参数
            var returnUrl = HttpUtility.ParseQueryString(uri.Query)[context.Options.ReturnUrlParameter];

            //add extra parameters for redirect to login
            var parameters = $"id={Guid.NewGuid().ToString()}";

            //添加额外参数到路由值中
            routeValues.Add(context.Options.ReturnUrlParameter, $"{returnUrl}{parameters}");

            var urlHelper = _helper.GetUrlHelper(_accessor.ActionContext);

            context.RedirectUri = UrlHelperExtensions.Action(urlHelper, "Login", "Account", routeValues);

            return base.RedirectToLogin(context);
        }
    }

这里需要注意的是因为上述我们用到了IActionContextAccessor,所以我们需要将其进行对应如下注册:

 services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

最终我们跳转到登录页将会看到我们添加的额外参数id也将呈现在url上,如下:

http://localhost:5001/Account/Login?ReturnUrl=%2FAccount%2FGetCurrentTime%3Fid%3Da309f451-e2ff-4496-bf18-65ba5c3ace9f

总结

本节我们讲解了Session和JWT的优缺点以及Cookie认证中可能我们需要用到的地方,下一节也是JWT最后一节内容,我们讲讲并探讨如何实现刷新Token,感谢阅读。

おすすめ

転載: www.cnblogs.com/CreateMyself/p/11197497.html