.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; }