处理JWT Token失效需求
JWT 本身是无状态的,这意味着服务器不会保存任何关于 Token 的状态信息。但为了支持 JWT 的状态管理(例如:强制使某些 Token 失效),可以借助 Redis 这样的外部存储来维护一个黑名单或白名单。
- 安装必要的 NuGet 包
首先需要安装以下 NuGet 包:
StackExchange.Redis;//用于与 Redis 数据库交互。
Microsoft.AspNetCore.Authentication.JwtBearer;//用于处理 JWT 认证。
- 配置 Redis 连接
在 appsettings.json 文件中添加 Redis 配置:
{
"Redis": {
"ConnectionString": "localhost:6379"
}
}
3.创建 Redis 缓存服务
接口
public interface IRedisCacheService
{
/// <summary>
/// 设置redis
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expiry"></param>
void Set(string key, string value, TimeSpan expiry);
/// <summary>
/// 删除
/// </summary>
/// <param name="key"></param>
void Remove(string key);
/// <summary>
/// 获取
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
string Get(string key);
/// <summary>
/// 判断是否存在
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool Exists(string key);
}
实现类
public class RedisCacheService : IRedisCacheService
{
private readonly IDatabase _redisDb;
public RedisCacheService(IConnectionMultiplexer redis)
{
_redisDb = redis.GetDatabase();
}
public void Set(string key, string value, TimeSpan expiry)
{
_redisDb.StringSet(key, value, expiry);
}
public void Remove(string key)
{
_redisDb.KeyDelete(key);
}
public string Get(string key)
{
return _redisDb.StringGet(key);
}
public bool Exists(string key)
{
return _redisDb.KeyExists(key);
}
}
自定义一个扩展类
public static IServiceCollection AddRedisCacheService(this IServiceCollection services)
{
return services.AddSingleton<IRedisCacheService, RedisCacheService>();
}
- 注册依赖注入服务
在Program.cs注册服务
在这里插入代码片
builder.Services.AddControllers(opt =>
{
opt.Filters.Add<Filter.JWTAuthorizationFilter>(); // 添加 JWT 授权过滤器
});
// 配置 Redis服务
builder.Services.Configure<RedisSetting>(builder.Configuration.GetSection("RedisConnection")); // 注入 Redis 配置
var redisSetting = builder.Configuration.GetSection("RedisConnection").Get<RedisSetting>();
builder.Services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(redisSetting.ConnectionString));
builder.Services.AddRedisCacheService();
// 创建个DTO保存redis配置
public class RedisSetting
{
public string ConnectionString { get; set; }
}
- 登录时保存Redis信息
a.登录成功后,将用户的 JWTVersion 写入 Redis。每次登录版本号不一致,可以解决跨浏览器登录和跨地区登录。
b.如果登录人状态被禁用了,重新设置下Redis的值即可。
/// <summary>
/// 登录
/// </summary>
/// <param name="login"></param>
/// <returns></returns>
[HttpPost]
[NoAuthAttribute]
public async Task<IActionResult> Login([FromBody] LoginModel login)
{
var user = await _userManager.FindByNameAsync(login.UserName);
if (user == null)
{
return new JsonResult(new { Code = 400, Message = "用户不存在" });
}
if (!await _userManager.CheckPasswordAsync(user, login.Password))
{
return new JsonResult(new { Code = 400, Message = "密码错误" });
}
var guid = Guid.NewGuid().ToString();
var redisKey = $"{user.Id}_user.Id";
var roles = await _userManager.GetRolesAsync(user);
var token = _jwtService.GenerateToken(user.Id, user.UserName, guid, roles.ToList());
// 设置redis和redis1小时过期
_redisDb.Set(redisKey, guid, TimeSpan.FromHours(1));
return new JsonResult(new { Code = 200, Message = "登录成功", Token = token });
}
// 使用自定义注解,去掉登录时的过滤拦截
public class NoAuthAttribute : Attribute
{
}
- 使用过滤器来过滤请求中的数据是否有效
public class JWTAuthorizationFilter : IAsyncActionFilter
{
private readonly UserManager<MyUser> userManager;
private readonly IRedisCacheService redisDb;
public JWTAuthorizationFilter(UserManager<MyUser> userManager, IRedisCacheService redisCache)
{
this.userManager = userManager;
this.redisDb = redisCache;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 排除掉登录不需要校验的问题
var controllerAttr = context.Controller.GetType().GetCustomAttributes(typeof(NoAuthAttribute), true).Any();
if (controllerAttr)
{
await next();
return;
}
var haveNoAuth = context.ActionDescriptor.EndpointMetadata.Any(p => p is NoAuthAttribute || p is AllowAnonymousAttribute);
if (haveNoAuth)
{
await next();
return;
}
// 这里可以添加 JWT 验证逻辑
// 如果验证失败,可以返回 401
// 如果验证成功,继续执行下一个操作
if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var token))
{
context.Result = new ObjectResult("请求头,没有Authorization参数") { StatusCode = 401 };
return;
}
token = token.ToString().Replace("Bearer ", "");
if (string.IsNullOrEmpty(token))
{
context.Result = new ObjectResult("请求头,token为空") { StatusCode = 401 };
return;
}
var jwtVersion = context.HttpContext.User.FindFirstValue("JWTVersion");
var userId = context.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(jwtVersion) || string.IsNullOrEmpty(userId))
{
context.Result = new ObjectResult("JWTVersion或NameIdentifier为空") { StatusCode = 401 };
return;
}
var redisKey = $"{userId}_user.Id";
var sourceGuid = redisDb.Get(redisKey);
// 判断当前的版本号是否一致,不一致校验token失效
if (string.IsNullOrEmpty(sourceGuid) || sourceGuid != jwtVersion)
{
context.Result = new ObjectResult("token已失效") { StatusCode = 401 };
return;
}
await next();
}
}