.Net Core之JWT认证方案

.Net Core提供了JWT的认证方案,开箱即用,我们再配合Redis启用黑名单机制,基本可以满足需求

基本功能 

开启JWT认证:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
                {
                    opt.TokenValidationParameters = new TokenValidationParameters
                    {
                        NameClaimType = ClaimTypes.Name,
                        RoleClaimType = ClaimTypes.Role,

                        ValidIssuer = Issuer,
                        ValidAudience = Audience,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Secret))
                    };
                });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseAuthentication();
}

开启认证之后,就可以在增加认证接口:

/// <summary>
/// 认证
/// </summary>
[AllowAnonymous]
[HttpPost("authenticate")]
public async Task<IActionResult> Authenticate([FromBody]UserDTO userDto)
{
    var user = await _userRepo.GetAll()
                              .FirstOrDefaultAsync(e => e.Code == userDto.UserName);
    if (user == null)
    {
        return BadRequest("user_not_found");
    }
    //这里对密码MD5加密
    if (user.Password is Wrong)
    {
        return BadRequest("wrong_password");
    }
    //Token生成:按需配置
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(Secret);
    var authTime = DateTime.Now;
    var expiresAt = authTime.AddDays(1);//按需设置时长
    var jti = Guid.NewGuid().ToString();
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Audience = Audience,
        Issuer = Issuer,
        Subject = new ClaimsIdentity(new Claim[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.Code),
            new Claim(JwtRegisteredClaimNames.Jti, jti)
        }),
        IssuedAt = authTime,
        Expires = expiresAt,
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    var tokenString = tokenHandler.WriteToken(token);

    return Ok(new AuthResponseDTO
    {
        Access_Token = tokenString,
        Token_Type = "Bearer",
        Expires_At = expiresAt,
        User = user
    });
}

黑名单机制

经过以上两步,初步实现了JWT认证的功能,但我们还需要关注Token失效的问题。我们可以尽量设置短的过期时间,同时配合RefreshToken。不过这里不讨论这种方案,只采用黑名单机制(Redis)。

  • 在退出登录的时候将相应Token加入黑名单;
  • 修改人员的时候记录人员和时间,将当时间之前的所有Token判定为无效;
  • 每次请求都通过Redis黑名单验证Token的有效性。
  • 配合Redis验证Token有效性,就要重写默认的JwtSecurityTokenHandler:
public class CustomJwtSecurityTokenHandler : JwtSecurityTokenHandler
{
    private readonly IRedisRepo _redis;

    public RevokableJwtSecurityTokenHandler(IServiceProvider serviceProvider)
    {
        _redis = serviceProvider.GetRequiredService<IRedisRepo>();
    }

    public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters,
        out SecurityToken validatedToken)
    {
        var claimsPrincipal = base.ValidateToken(token, validationParameters, out validatedToken);
        //通过Redis验证Token
        if (!_redis.IsTokenActive(claimsPrincipal))
        {
            throw new UnauthorizedException();
        }

        return claimsPrincipal;
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
                {
                    opt.SecurityTokenValidators.Clear();
                    opt.SecurityTokenValidators.Add(new CustomJwtSecurityTokenHandler(services.BuildServiceProvider()));
                });
}

public bool IsTokenActive(ClaimsPrincipal principal)
{
    //解析ClaimsPrincipal取出UserId、Iat和Jti
    //具体的验证步骤有两个:
    //- 到Redis查找该用户的Token失效时间,如果当前Token的颁发时间在此之前就是无效的;
    //- 到Redis的黑名单里判断是否存在该Token;
}

 

posted @ 2022-08-25 14:06  进步者One  阅读(972)  评论(0编辑  收藏  举报