.Net Core之JWT认证方案

.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 @ 2019-03-16 09:22  shadowxs  阅读(226)  评论(0)    收藏  举报