好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

Cookie认证

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

services.AddAuthentication(options =>  {      options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;      options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;  })  .AddCookie(options =>  {      options.ExpireTimeSpan = TimeSpan.FromMinutes(1);      options.Cookie. Name  =  "user-session" ;      options.SlidingExpiration =  true ;  }); 

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

app.UseRouting();    app.UseAuthentication();    app.UseAuthorization();    app.UseEndpoints(endpoints =>  {    ......  }); 

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

[Authorize]  public  class HomeController : Controller  {       public  IActionResult  Index ()      {           return   View ();      }  } 

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

public  class AccountController : Controller  {      [AllowAnonymous]       public  IActionResult Login()      {         return   View ();      }      ......  } 

我们启动程序先看看效果

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

/// <summary>  /// 模拟登录  /// </summary>  /// < returns ></ returns >  [HttpPost]  [AllowAnonymous]  public  async Task<IActionResult> TestLogin()  {      var claims = new Claim[]      {        new Claim(ClaimTypes. Name ,  "Jeffcky" ),      };        var claimsIdentity = new ClaimsIdentity(claims,  "Login" );        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));         return  Ok();  } 

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

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

当我们请求过后,再次访问首页,将看到生成当前会话信息,同时我们将会话过期设置为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选项中,还有一个也可以设置过期的属性

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

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

JWT认证

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

首先我们添加JWT认证服务

.AddJwtBearer(options =>  {      options.TokenValidationParameters = new TokenValidationParameters      {        ValidateIssuerSigningKey =  true ,        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( "1234567890123456" )),        ValidateIssuer =  true ,        ValidIssuer =  "http://localhost:5000" ,        ValidateAudience =  true ,        ValidAudience =  "http://localhost:5001" ,        ValidateLifetime =  true ,        ClockSkew = TimeSpan.FromMinutes(5)      };  }); 

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

private string GenerateToken(Claim[] claims)  {      var  key  = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( "1234567890123456" ));        var token = new JwtSecurityToken(        issuer:  "http://localhost:5000" ,        audience:  "http://localhost:5001" ,        claims: claims,        notBefore: DateTime.Now,        expires: DateTime.Now.AddMinutes(5),        signingCredentials: new SigningCredentials( key , SecurityAlgorithms.HmacSha256)      );         return  new JwtSecurityTokenHandler().WriteToken(token);  } 

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

/// <summary>  /// 模拟登录  /// </summary>  /// < returns ></ returns >  [HttpPost]  [AllowAnonymous]  public  async Task<IActionResult> TestLogin()  {      var claims = new Claim[]      {        new Claim(ClaimTypes. Name ,  "Jeffcky" ),      };        var claimsIdentity = new ClaimsIdentity(claims,  "Login" );        Response.Cookies.Append( "x-access-token" , GenerateToken(claims),        new CookieOptions()        {          Path =  "/" ,          HttpOnly =  true         });        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));      return  Ok();  } 

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

options.Events = new JwtBearerEvents  {      OnMessageReceived = context =>      {          var accessToken = context.Request.Cookies[ "x-access-token" ];            if (!string.IsNullOrEmpty(accessToken))          {              context.Token = accessToken;          }             return  Task.CompletedTask;      }  }; 

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

[Authorize( "Bearer" )]  [Route( "api/[controller]/[action]" )]  [ApiController]  public  class JwtController : ControllerBase  {      [HttpGet]       public  IActionResult Test()      {         return  Ok( "test jwt" );      }  } 

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

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

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

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

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

滑动过期思考扩展

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

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

public  class CookieAuthenticationEventsExetensions : CookieAuthenticationEvents  {      private const string TicketIssuedTicks = nameof(TicketIssuedTicks);         public  override async Task SigningIn(CookieSigningInContext context)      {          context.Properties.SetString(            TicketIssuedTicks,            DateTimeOffset.UtcNow.Ticks.ToString());            await base.SigningIn(context);      }         public  override async Task ValidatePrincipal(        CookieValidatePrincipalContext context)      {          var ticketIssuedTicksValue = context            .Properties.GetString(TicketIssuedTicks);            if (ticketIssuedTicksValue  is   null  ||            !long.TryParse(ticketIssuedTicksValue,  out  var ticketIssuedTicks))          {            await RejectPrincipalAsync(context);             return ;          }            var ticketIssuedUtc =            new DateTimeOffset(ticketIssuedTicks, TimeSpan.FromHours(0));            if (DateTimeOffset.UtcNow - ticketIssuedUtc > TimeSpan.FromDays(3))          {            await RejectPrincipalAsync(context);             return ;          }            await base.ValidatePrincipal(context);      }        private  static  async Task RejectPrincipalAsync(        CookieValidatePrincipalContext context)      {          context.RejectPrincipal();          await context.HttpContext.SignOutAsync();      }  } 

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

options.EventsType = typeof(CookieAuthenticationEventsExetensions); 

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

原文链接:https://mp.weixin.qq.com/s/zff_H_7eG0EaV7dP5xoMhg

查看更多关于实践剖析.NET Core 如何支持 Cookie 滑动过期和 JWT 混合认证、授权的详细内容...

  阅读:70次