.NET Core支持Cookie和JWT混合认证、授权的方法

2022-04-16 13:41:07
目录
前言Cookie认证JWT认证滑动过期思考扩展总结

前言

为防止JWT Token被窃取,我们将Token置于Cookie中,但若与第三方对接,调用我方接口进行认证、授权此时仍需将Token置于请求头,通过实践并联系理论,我们继续开始整活!首先我们实现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();    }    ......}

我们启动程序先看看效果

.NETCore支持Cookie和JWT混合认证、授权的方法

如上图,自动跳转至登录页,此时我们点击模拟登录按钮,发起请求去模拟登录(发起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();}

上述无非就是构建身份以及该身份下所具有的身份属性,类似个人身份证唯一标识个人,身份证上各个信息即表示如上声明,同时呢,肯定要调用上下文去登录,在整个会话未过期之前,根据认证方案获取对应处理方式,最后将相ns.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 = trpOtWqbuue, 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();}

那么JWT是如何验证Token的呢?默认是从请求去取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吗?结果会是怎样的呢?

.NETCore支持Cookie和JWT混合认证、授权的方法

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

.NETCore支持Cookie和JWT混合认证、授权的方法

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

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

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

.NETCore支持Cookie和JWT混合认证、授权的方法

滑动过期思考扩展

若我们实现基于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 Rejectwww.easck.comPrincipalAsync(      CookieValidatePrincipalContext context)    {        context.RejectPrincipal();        await context.HttpContext.SignOutAsync();    }}

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

options.EventsType = typeof(CookieAuthenticationEventsExetensions);

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

总结

暂无,下次再会!

你所看到的并非事物本身,而是经过诠释后所赋予的意义