当前位置: 首页 > news >正文

处理JWT Token失效需求

JWT 本身是无状态的,这意味着服务器不会保存任何关于 Token 的状态信息。但为了支持 JWT 的状态管理(例如:强制使某些 Token 失效),可以借助 Redis 这样的外部存储来维护一个黑名单或白名单。

  1. 安装必要的 NuGet 包
    首先需要安装以下 NuGet 包:
StackExchange.Redis;//用于与 Redis 数据库交互。
Microsoft.AspNetCore.Authentication.JwtBearer;//用于处理 JWT 认证。
  1. 配置 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>();
 }
  1. 注册依赖注入服务
    在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; }
 }
  1. 登录时保存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
  {
  }
  1. 使用过滤器来过滤请求中的数据是否有效
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();
    }
}

相关文章:

  • Debezium日常分享系列之:Debezium3.1版本之增量快照
  • 通过 Markdown 改进 RAG 文档处理
  • 1ms软延时,不用定时器,stm32
  • 智谱AutoGLM:从对话到自主操作的AI智能体革新
  • 第八章 图论
  • 06-03-自考数据结构(20331)- 查找技术-哈希表知识点
  • 【GPIO8个函数解释】
  • 1759. 统计同质子字符串的数目
  • 【深度学习】tf.math.multiply与tf.multiply有啥区别?
  • 在 .NET 8 中使用自定义令牌身份验证掌握 SignalR Hub 安全性
  • 【设计模式】设计模式六大原则
  • 《Golang高性能网络编程:构建低延迟服务器应用》
  • docker导出image再导入到其它docker中
  • openfga-spring-boot3-starter自己封装
  • SAP CEO引领云端与AI转型
  • rk3588配置静态IP和DNS
  • 运筹帷幄:制胜软件开发
  • K8s的资源管理
  • Spring Boot + MySQL + MyBatis(注解和XML配置两种方式)集成Redis的完整启用及配置详解,包含代码示例、注释说明和表格总结
  • AI设计再现新引擎,科技创新又添新动能——广东省首家行业AI设计工程中心获批成立