ASP.NET Core Web Api之JWT VS Session VS Cookie(二)

Foreword

In this paper we discuss the issue at JWT VS Session, the issue is not to think about this too much, seen too intense discussion comments, it took a little time to study and summarize, by the way, this is the benefit to blog, a write blog articles may be some experience, some may be learning to share, but can not escape seeing the article you have more or better ideas, and from the exchange itself can gain more, why not do it ? I hope this can get more doubts or exchange. We can direct throw problems: use client-side storage JWT Session maintain it better than the service side? 

Session-based JWT and certification in common

Since you want to compare JWT VS Session, then we have to know why you need JWT and Session, which together to solve the problem? Then we start with a scene, online shopping is now a commonplace thing, however, when we will be an item to the cart, and then jump to the selected page at this time before other commodities still needed commodity in the shopping cart , then you need to maintain session because HTTP stateless, so JWT session in common and are designed to maintain session persistence exists, in order to overcome the HTTP stateless situation, JWT and session are how to deal with it?

 

JWT VS Session Authentication

Session: When a user logs in the application system, this time the server creates a Session (We also referred to as a session), then SessionId will be saved to the user of Cookie, as long as the user is logged for each request, Cookie the SessionId will be sent to the server, then the server will save SessionId and Cookie in memory of SessionId compare to authenticate the user's identity and response.

JWT: when the user logs in the application system, then the server creates a JWT, JWT and sends to the client, the client stores JWT (typically in the Local Storage) while each of the request header that is included in the Authorization JWT, for each request, the server will verify the legality of JWT, directly on the server to authenticate locally, such as issuer, receptionist, etc., so that without issuing a network request or interact with the database, this approach may use than Session faster, thus speeding up the response performance, reduce server and database server load.

 

With the above brief description of JWT Session authentication and certification, we know that the biggest difference between the two is that Session is a server side storage, while JWT stored on the client. The server stores session is nothing less than two, one is the session identifier is stored in the database, one is stored in memory to maintain the session, I think in most cases are based on memory to maintain the session, but this will bring some problems, if there is a large flow system, meaning that if a large number of users accessing the system, this time using a memory-based session is restricted to maintain the level of expansion, but such problems Token-based authentication not exist, while Cookie general only applies to a single domain or subdomain, if for cross-domain, if a third-party Cookie, your browser may disable Cookie, it is also affected by browser restrictions, but Token authentication is not a problem, because it is stored in the request header in.

 

If we will then be transferred to the client, that is to say the use of Token authentication, this time lifting session will depend on the service end, but also horizontally scalable, without browser limitations, but at the same time will bring some problems, First, transmission security token, token for transport security we can use HTTPS encrypted channel to solve, and second, compared with SessionId stored in the Cookie, JWT is obviously much larger, because JWT also contains user information, so in order to solve this problem, we try to ensure that JWT includes only the necessary information (in most cases contain only sub and other important information), for sensitive information we should be omitted in order to prevent XSS attacks. JWT is that the core statement, the statement is JSON data in JWT, that is we can embed user information JWT, reducing database load. So sum JWT solve existing problems or disadvantages of other sessions:

More flexible

safer

Reduce database round-trip, in order to achieve the level of scalable.

Tamper-resistant client statement

Can work better on mobile devices

For users to block Cookie's

To sum up the benefits of JWT within the validity period is not mandatory invalidated ability to completely negate JWT is clearly untenable, of course, it is that if there is no irrefutable use as many restrictions to achieve other types of authentication is entirely reasonable and legitimate , the need to tradeoff, not again revealing the death of a conclusion. So far, we have been talking about JWT VS Session authentication, instead of JWT VS Cookie certification, such as the title but we will also be included in the Cookie, learners do not just want to be confused, because JWT VS Cookie certification this statement is wrong, Cookie is just a media storage and transmission of information, we can only say Cookie storage and transmission JWT. Next we come to realize Cookie storage and transmission JWT token.

 

JWT AS Cookies Identity Claim

In the Startup We can add the following Cookie authentication middleware, then we need to understand some of the configuration options under Cookie, configure these options to inform manifestation Cookie Authentication middleware in the browser, we look at a few one related to the security option.

           services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
           .AddCookie(options =>
           {
               options.LoginPath = "/Account/Login";
               options.LogoutPath = "/Account/Logout";
               options.Cookie.Expiration = TimeSpan.FromMinutes(5);
               options.Cookie.HttpOnly = true;
               options.Cookie.SecurePolicy = CookieSecurePolicy.None;
               options.Cookie.SameSite =SameSiteMode.Lax; 
           });

HttpOnly Cookie configuration is marked only server use, but not directly accessible through the front.

Configuring SecurePolicy will limit Cookie is HTTPS, configure this parameter in a production environment supports HTTPS is recommended.

Configuring SameSite used to indicate whether the browser can be used in conjunction with Cookie cross-site request, if for OAuth authentication can be set to Lax, allow external links redirect issue a POST request to maintain such a session, if Cookie certification, is set to Restrict, because Cookie certification applies only to single-site, if set to None, it will not set the Cookie Header value. (Note: SameSite property in Google, Firefox have been achieved, seems not to support for IE11, Safari starting with version 12.1 supports this property)

When you create a .NET Core default Web application, in ConfigureServices method, the configuration of middleware direct Cookie global strategy, as follows:

            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

Of course, the default configuration Cookie global strategy, but also in Configure method used in its strategy is as follows:

            app.UseCookiePolicy();

We can also directly call the above method using Cookie Policy middleware to set the corresponding parameter in strategy, as follows:

If we also add configure global policies Cookie Cookie middleware same time, we will find properties for HTTPOnly and SameSite can be configured at this time personal guess there will be situations covered, as follows:

 

The need for certified controllers we need to add on [Authroize] characteristics of each controller we had to add such a feature, I believe that most of the shoes are so dry. In fact, we could have the reverse operation, without the need for certification we can add properties can be accessed anonymously, we need certified controllers are configured globally certified filter, as follows:

 services.AddMvc(options=> options.Filters.Add(new AuthorizeFilter()))
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Well, here, and we just explain the instructions under rough on middleware Cookie Cookie global configuration parameters and policies, not too deep inside to study the minutiae, and other problems encountered more specific analysis of it. Continue back to the topic, Cookie certification JWT low compared to API access in terms of the safety factor, so we can combine JWT in Cookie authentication to use. Specifically how we can try to do it? Put it into identity information statement, I think it should be feasible way, we try to simulate the login and logout, probably code is as follows:

    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,感谢阅读。

Guess you like

Origin www.cnblogs.com/CreateMyself/p/11197497.html