当前位置: 首页 > 科技观察

.NETCore如何支持Cookie滑动过期和JWT混合认证授权的实战分析

时间:2023-03-18 15:23:30 科技观察

首先我们实现Cookie认证,然后再引入JWT,最后联系一下其他我们在结合两者的时候可能需要注意的事项。Cookie认证在启动时,我们添加cookie认证服务,如下:=TimeSpan.FromMinutes(1);options.Cookie.Name="user-session";options.SlidingExpiration=true;});接下来就是使用认证授权中间件了,注意放在routing和endpoint端点之间,否则启动app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(endpoints=>{...});我们给测试视图页面要求鉴权,即controller添加功能[Authorize]publicclassHomeController:Controller{publicIActionResultIndex(){returnView();}}进入首页默认会进入account/login如果未认证,则创建视图publicclassAccountController:Controller{[AllowAnonymous]publicIActionResultLogin(){returnView();}......}我们先启动程序效果如上图,会自动跳转到登录页面。这时候我们点击模拟登录按钮,发起请求模拟登录(发起ajax请求的代码会占空间)///

///模拟登录//////[HttpPost][AllowAnonymous]publicasyncTaskTestLogin(){varclaims=newClaim[]{newClaim(ClaimTypes.Name,"Jeffcky"),};varclaimsIdentity=newClaimsIdentity(声明,"登录"");awaitHttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,newClaimsPrincipal(claimsIdentity));returnOk();}上面无非就是建立一个身份以及这个身份下的身份属性,类似于个人身份证,唯一标识一个人,以及身份证上的每条信息也就是说,在上面声明的同时,必须调用context进行登录,在整个session到期之前,根据认证方案获取相应的处理方法,最后存储相关信息等,有兴趣的童鞋可以了解一下它的实现细节。我们请求之后,再次访问首页,会看到生成了当前session信息,我们设置session过期时间为1分钟,如果1分钟内没有session,会自动跳转到登录页面,注意上面的标记没有值,那么这个值可以设置吗?当然我们一开始配置的时候并没有给,那么这个属性是什么意思呢?options.Cookie.MaxAge=TimeSpan.FromMinutes(2);那么结合ExpireTimeSpan和MaxAge是什么意思呢?让我们暂时搁置滑动到期。ExpireTimeSpan表示用户身份认证票据的生命周期。它是身份验证cookie的有效负载。存储的cookie值是一个加密的字符串。在每个请求中,Web应用程序将基于该请求。它解密MaxAge以控制cookie的生命周期。如果cookie过期,浏览器会自动清除它。如果不设置这个值,它的生命周期实际上就是ExpireTimeSpan。那是什么意思呢?上面我们设置ticketcookie的生命周期为1分钟,我们控制cookie的生命周期为2分钟。如果在2分钟内关闭浏览器或重新启动Web应用程序,cookie的生命周期将不会它没有过期,所以它仍然处于会话状态,不需要登录。如果不设置MaxAge,关闭浏览器或重启后其值会自动清除,需要登录。当然,一切前提是浏览器cookie没有被手动清除。问题又来了。在配置的cookie选项中,还有一个属性也可以设置过期options.Cookie.Expiration=TimeSpan.FromMinutes(3);配置ExpireTimeSpan或者同时配置MaxAge时,不需要设置Expiration,因为会抛出异常。JWT认证上面提到的cookie已经实现了认证,那么在连接第三方的时候,我们需要使用JWT认证,应该怎么处理呢?首先,我们添加JWT身份验证服务。(Encoding.UTF8.GetBytes("1234567890123456")),ValidateIssuer=true,ValidIssuer="http://localhost:5000",ValidateAudience=true,ValidateAudience="http://localhost:5001",ValidateLifetime=true,ClockSkew=TimeSpan.FromMinutes(5)};});将JWTToken放入cookie中,上一篇文章已经讲解,这里直接给出代码,Mr.GenerateTokenprivatestringGenerateToken(Claim[]claims){varkey=newSymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456"));vartoken=newJwtSecurityToken(发行人:“http://localhost:5000”,观众:“http://localhost:5001",claims:claims,notBefore:DateTime.Now,expires:DateTime.Now.AddMinutes(5),signingCredentials:newSigningCredentials(key,SecurityAlgorithms.HmacSha256));返回newJwtSecurityTokenHandler().WriteToken(token);}login方法中,写入响应cookie,如下//////模拟登录//////[HttpPost][AllowAnonymous]publicasyncTaskTestLogin(){varclaims=newClaim[]{newClaim(ClaimTypes.Name,"Jeffcky"),};varclaimsIdentity=newClaimsIdentity(claims,"Login");Response.Cookies.Append("x-access-token",GenerateToken(claims),newCookieOptions(){Path="/",HttpOnly=true});awaitHttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,newClaimsPrincipal(claimsIdentity));returnOk();}获取BearerToken值,如果成功的话这赋值给了下面的context.Token,所以这时候我们需要手动把cookie中的token去掉,赋值options.Events=newJwtBearerEvents{OnMessageReceived=context=>{varaccessToken=context.Request.Cookies["x-access-token"];if(!string.IsNullOrEmpty(accessToken)){context.Token=accessToken;}returnTask.CompletedTask;}};万事俱备,我们来写一个api接口测试验证[Authorize("Bearer")][Route("api/[controller]/[action]")][ApiController]publicclassJwtController:ControllerBase{[HttpGet]publicIActionResultTest(){returnOk("testjwt");}}想一想,我们通过Postman将模拟测试返回401?结果会是什么?问题不大,主要是feature参数为语句指定了策略,但是我们需要指定认证方案,即scheme,修改如下:so连接第三方时,请求返回token,然后将token放在请求头中通过校验,同时把token放在cookie中,手动赋值,对于连接第三方来说是多余的,但它是出于许多其他原因[Authorize(AuthenticationSchemes="Bearer,Cookies")]Pay注意mixedauthenticationscheme设置的顺序,后者会覆盖前者,也就是上面的设置,此时会考虑cookie认证滑动过期。如果我们实现基于cookie的滑动过期,使用signalr进行数据推送,难免会出现问题。因为session会一直刷新,会导致session永不过期的问题。从安全的角度来看,我们应该如何处理呢?我们知道ticket生命周期保存在context的AuthenticationProperties属性中,所以我们可以在cookie选项事件中配置。定义处理publicclassCookieAuthenticationEventsExetensions:CookieAuthenticationEvents{privateconststringTicketIssuedTicks=nameof(TicketIssuedTicks);publicoverrideasyncTaskSigningIn(CookieSigningInContextcontext){context.Properties.SetString(TicketIssuedTicks,DateTimeOffset.UtcNow.Ticks.ToString());awaitbase.SigningIn(context);}publicoverrideasyncTaskValidatePrincipal(CookieValidatePrincipalContextcontext){varticketIssuedTicksValue=context.Properties.GetString(TicketIsketsValuesifsuedTick);|!long.TryParse(ticketIssuedTicksValue,outvarticketIssuedTicks)){awaitRejectPrincipalAsync(上下文);返回;}varticketIssuedUtc=newDateTimeOffset(ticketIssuedTicks,TimeSpan.FromHours(0));awaitRejectPrincipalAsync(context);return;}awaitbase.ValidatePrincipal(context);}privatestaticasyncTaskRejectPrincipalAsync(CookieValidatePrincipalContextcontext){context.RejectPrincipal();awaitcontext.HttpContext.SignOutAsync();}}添加Cookie事件选项时,有如下对应选项。EventsType=typeof(CookieAuthenticationEventsExetensions);扩展事件实现是指如果第一个session到当前时间超过3天,会自动跳转到登录页面,最后注册上面的扩展事件