实践剖析.NET Core 如何支持 Cookie 滑动过期和 JWT 混合认证、授权

开发 后端
MaxAge控制着cookie的生命周期,若cookie过期,浏览器将会自动清除,如果没有设置该值,实质上它的生命周期就是ExpireTimeSpan,那么它到底有何意义呢?

首先我们实现Cookie认证,然后再次引入JWT,最后在结合二者使用时联系其他我们可能需要注意的事项

Cookie认证

在startup中我们添加cookie认证服务,如下:

  1. services.AddAuthentication(options => 
  2.     options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; 
  3.     options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; 
  4. }) 
  5. .AddCookie(options => 
  6.     options.ExpireTimeSpan = TimeSpan.FromMinutes(1); 
  7.     options.Cookie.Name = "user-session"
  8.     options.SlidingExpiration = true
  9. }); 

接下来则是使用认证和授权中间件,注意将其置于路由和终结点终结点之间,否则启动也会有明确异常提示

  1. app.UseRouting(); 
  2.  
  3. app.UseAuthentication(); 
  4.  
  5. app.UseAuthorization(); 
  6.  
  7. app.UseEndpoints(endpoints => 
  8.   ...... 
  9. }); 

我们给出测试视图页,并要求认证即控制器添加特性

  1. [Authorize] 
  2. public class HomeController : Controller 
  3.     public IActionResult Index() 
  4.     { 
  5.         return View(); 
  6.     } 

当进入首页,未认证默认进入account/login,那么接下来创建该视图

  1. public class AccountController : Controller 
  2.     [AllowAnonymous] 
  3.     public IActionResult Login() 
  4.     { 
  5.       return View(); 
  6.     } 
  7.     ...... 

我们启动程序先看看效果

如上图,自动跳转至登录页,此时我们点击模拟登录按钮,发起请求去模拟登录(发起ajax请求代码就占不用篇幅给出了)

  1. /// <summary> 
  2. /// 模拟登录 
  3. /// </summary> 
  4. /// <returns></returns
  5. [HttpPost] 
  6. [AllowAnonymous] 
  7. public async Task<IActionResult> TestLogin() 
  8.     var claims = new Claim[] 
  9.     { 
  10.       new Claim(ClaimTypes.Name"Jeffcky"), 
  11.     }; 
  12.  
  13.     var claimsIdentity = new ClaimsIdentity(claims, "Login"); 
  14.  
  15.     await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); 
  16.  
  17.     return Ok(); 

上述无非就是构建身份以及该身份下所具有的身份属性,类似个人身份证唯一标识个人,身份证上各个信息即表示如上声明

同时呢,肯定要调用上下文去登录,在整个会话未过期之前,根据认证方案获取对应处理方式,最后将相关信息进行存储等等,有兴趣的童鞋可以去了解其实现细节哈

当我们请求过后,再次访问首页,将看到生成当前会话信息,同时我们将会话过期设置为1分钟,在1分钟内未进行会话,将自动重定向至登录页

注意如上标注并没有值,那么这个值可以设置吗?当然可以,在开始配置时我们并未给出,那么这个属性又代表什么含义呢?

  1. options.Cookie.MaxAge = TimeSpan.FromMinutes(2); 

那么结合ExpireTimeSpan和MaxAge使用,到底代表什么意思呢?我们暂且撇开滑动过期设置

ExpireTimeSpan表示用户身份认证票据的生命周期,它是认证cookie的有效负载,存储的cookie值是一段加密字符串,在每次请求时,web应用程序都会根据请求对其进行解密

MaxAge控制着cookie的生命周期,若cookie过期,浏览器将会自动清除,如果没有设置该值,实质上它的生命周期就是ExpireTimeSpan,那么它到底有何意义呢?

上述我们设置票据的生命周期为1分钟,同时我们控制cookie的生命周期为2分钟,若在2分钟内关闭浏览器或重启web应用程序,此时cookie生命周期并未过期,所以仍将处于会话状态即无需登录,若未设置MaxAge,关闭浏览器或重启后将自动清除其值即需登录,当然一切前提是未手动清除浏览器cookie

问题又来了,在配置cookie选项中,还有一个也可以设置过期的属性

  1. options.Cookie.Expiration = TimeSpan.FromMinutes(3); 

当配置ExpireTimeSpan或同时配置MaxAge时,无需设置Expiration,因为会抛出异常

JWT认证

上述已经实现Cookie认证,那么在与第三方进行对接时,我们要使用JWT认证,我们又该如何处理呢?

首先我们添加JWT认证服务

  1. .AddJwtBearer(options => 
  2.     options.TokenValidationParameters = new TokenValidationParameters 
  3.     { 
  4.       ValidateIssuerSigningKey = true
  5.       IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456")), 
  6.       ValidateIssuer = true
  7.       ValidIssuer = "http://localhost:5000"
  8.       ValidateAudience = true
  9.       ValidAudience = "http://localhost:5001"
  10.       ValidateLifetime = true
  11.       ClockSkew = TimeSpan.FromMinutes(5) 
  12.     }; 
  13. }); 

将JWT Token置于cookie中,此前文章已有讲解,这里我们直接给出代码,先生成Token

  1. private string GenerateToken(Claim[] claims) 
  2.     var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456")); 
  3.  
  4.     var token = new JwtSecurityToken( 
  5.       issuer: "http://localhost:5000"
  6.       audience: "http://localhost:5001"
  7.       claims: claims, 
  8.       notBefore: DateTime.Now, 
  9.       expires: DateTime.Now.AddMinutes(5), 
  10.       signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) 
  11.     ); 
  12.  
  13.     return new JwtSecurityTokenHandler().WriteToken(token); 

在登录方法中,将其写入响应cookie中,如下这般

  1. /// <summary> 
  2. /// 模拟登录 
  3. /// </summary> 
  4. /// <returns></returns
  5. [HttpPost] 
  6. [AllowAnonymous] 
  7. public async Task<IActionResult> TestLogin() 
  8.     var claims = new Claim[] 
  9.     { 
  10.       new Claim(ClaimTypes.Name"Jeffcky"), 
  11.     }; 
  12.  
  13.     var claimsIdentity = new ClaimsIdentity(claims, "Login"); 
  14.  
  15.     Response.Cookies.Append("x-access-token", GenerateToken(claims), 
  16.       new CookieOptions() 
  17.       { 
  18.         Path = "/"
  19.         HttpOnly = true 
  20.       }); 
  21.  
  22.     await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); 
  23.  
  24.  return Ok(); 

去取Bearer Token值,若成功取到这赋值给如下context.Token,所以此时我们需要手动从cookie中取出token并赋值

  1. options.Events = new JwtBearerEvents 
  2.     OnMessageReceived = context => 
  3.     { 
  4.         var accessToken = context.Request.Cookies["x-access-token"]; 
  5.  
  6.         if (!string.IsNullOrEmpty(accessToken)) 
  7.         { 
  8.             context.Token = accessToken; 
  9.         } 
  10.  
  11.         return Task.CompletedTask; 
  12.     } 
  13. }; 

一切已就绪,接下来我们写个api接口测试验证看看

  1. [Authorize("Bearer")] 
  2. [Route("api/[controller]/[action]")] 
  3. [ApiController] 
  4. public class JwtController : ControllerBase 
  5.     [HttpGet] 
  6.     public IActionResult Test() 
  7.     { 
  8.       return Ok("test jwt"); 
  9.     } 

思考一下,我们通过Postman模拟测试,会返回401吗?结果会是怎样的呢?

问题不大,主要在于该特性参数为声明指定策略,但我们需要指定认证方案即scheme,修改成如下:

如此在与第三方对接时,请求返回token,后续将token置于请求头中即可验证通过,同时上述取cookie中token并手动赋值,对于对接第三方则是多余,不过是为了诸多其他原因而已

  1. [Authorize(AuthenticationSchemes = "Bearer,Cookies")] 

注意混合认证方案设置存在顺序,后者将覆盖前者即如上设置,此时将走cookie认证

滑动过期思考扩展

若我们实现基于Cookie滑动过期,同时使用signalr进行数据推送,势必存在问题,因为会一直刷新会话,那么将导致会话永不过期问题,从安全层面角度考虑,我们该如何处理呢?

我们知道票据生命周期存储在上下文AuthenticationProperties属性中,所以在配置Cookie选项事件中我们可以进行自定义处理

  1. public class CookieAuthenticationEventsExetensions : CookieAuthenticationEvents 
  2.     private const string TicketIssuedTicks = nameof(TicketIssuedTicks); 
  3.  
  4.     public override async Task SigningIn(CookieSigningInContext context) 
  5.     { 
  6.         context.Properties.SetString( 
  7.           TicketIssuedTicks, 
  8.           DateTimeOffset.UtcNow.Ticks.ToString()); 
  9.  
  10.         await base.SigningIn(context); 
  11.     } 
  12.  
  13.     public override async Task ValidatePrincipal( 
  14.       CookieValidatePrincipalContext context) 
  15.     { 
  16.         var ticketIssuedTicksValue = context 
  17.           .Properties.GetString(TicketIssuedTicks); 
  18.  
  19.         if (ticketIssuedTicksValue is null || 
  20.           !long.TryParse(ticketIssuedTicksValue, out var ticketIssuedTicks)) 
  21.         { 
  22.           await RejectPrincipalAsync(context); 
  23.           return
  24.         } 
  25.  
  26.         var ticketIssuedUtc = 
  27.           new DateTimeOffset(ticketIssuedTicks, TimeSpan.FromHours(0)); 
  28.  
  29.         if (DateTimeOffset.UtcNow - ticketIssuedUtc > TimeSpan.FromDays(3)) 
  30.         { 
  31.           await RejectPrincipalAsync(context); 
  32.           return
  33.         } 
  34.  
  35.         await base.ValidatePrincipal(context); 
  36.     } 
  37.  
  38.     private static async Task RejectPrincipalAsync( 
  39.       CookieValidatePrincipalContext context) 
  40.     { 
  41.         context.RejectPrincipal(); 
  42.         await context.HttpContext.SignOutAsync(); 
  43.     } 

在添加Cookie服务时,有对应事件选项,使用如下

  1. options.EventsType = typeof(CookieAuthenticationEventsExetensions); 

 

扩展事件实现表示在第一次会话到当前时间截止超过3天,则自动重定向至登录页,最后将上述扩展事件进行注册即可

 

责任编辑:武晓燕 来源: JeffckyShare
相关推荐

2019-11-08 08:00:00

ASP .NETASP .NET Cocookie

2024-09-09 07:37:51

AspJWT权限

2021-07-11 17:17:08

.NET 授权自定义

2021-02-17 08:51:55

cookie身份验证

2019-03-27 15:51:51

API 认证授权

2009-07-21 15:47:19

2024-03-14 11:57:53

.NET Core反射开发

2015-01-13 10:01:03

AWS市场亚马逊云平台

2009-12-03 10:00:46

Linux系统启动

2021-12-05 18:22:20

.NETLS Cipher套件

2021-08-09 08:53:30

HTTP状态化协议

2022-04-21 09:00:00

API安全密钥

2024-05-17 09:51:11

2009-08-05 18:30:36

Session和CooASP.NET表单

2024-03-27 14:43:07

.NET Core后端监控可观测性

2021-07-11 12:12:49

.NETJWTjson

2024-10-08 10:11:57

2021-07-06 23:48:45

.NET用户信息

2024-05-31 12:54:37

.NET CoreLinux语言

2021-03-23 10:45:23

CookieSession前端
点赞
收藏

51CTO技术栈公众号