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

Redis - Token JWT 概念解析及双token实现分布式session存储实战

Token

  1. 定义:令牌,访问资源接口(API)时所需要的资源凭证

一、Access Token

  1. 定义:访问资源接口(API)时所需要的资源凭证,存储在客户端

  2. 组成

    组成部分说明
    uid用户唯一的身份标识
    time当前时间的时间戳
    sign签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串
  3. 验证流程

    1. 登录:客户端使用 username & password 请求登录
    2. 验证:服务端收到请求,验证 username & password
      1. 验证成功 → 服务端会签发一个 token 并把这个 token 发送给客户端
      2. 验证失败 → 登录失败
    3. 存储 token:客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
    4. 携带 token:客户端每次向服务端请求资源的时候需要携带服务端签发的 token(放在 HTTP 的 Header 中)
    5. 解析 token:服务端收到请求,解析客户端的 token 数据
      1. 验证成功 → 向客户端返回请求的数据
      2. 验证失败 → 拒绝请求,要求重新登录

二、Refresh Token

  1. 定义:专用于刷新 access token 的 token

  2. 功能:减少重复登录操作,Access Token 失效时,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作

  3. 存储位置:服务器的数据库

  4. 工作流程


JWT

一、概述

  1. 定义:JSON Web Token(简称 JWT),一种认证授权机制,是目前最流行的跨域认证解决方案
  2. 功能:实现跨域请求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录(session 所有数据都保存在客户端,每次请求都发回服务器)
  3. 存储位置:HTTP 请求的头信息 Authorization 字段中,格式为:Authorization: Bearer <token>
  4. 基本格式:(Header.Payload.Signature)

二、组成部分

  1. Header

    1. 定义:描述 JWT 的元数据(配置信息),记录令牌类型、签名算法等配置,由 Base64URL 算法转为字符串

    2. 示例

      {
        "alg": "HS256",        // 签名算法类型
        "typ": "JWT"           // Token类型
      }
      
  2. Payload

    1. 定义:记录用户信息的数据(不是加密数据,不能存敏感信息)

    2. 示例

      {
        "sub": "1234567890",
        "name": "John Doe",
        "admin": true
      }
      
  3. Signature

    1. 定义:对前两部分(Header和Payload)的数字签名,用于验证消息的完整性和确保数据未被篡改。这是JWT安全性的核心保障

    2. 生成过程:

      1. 服务器持有一个密钥(secret),该密钥必须妥善保管且不能泄露
      2. 使用Header中指定的签名算法(默认为HMAC SHA256)
      3. 将编码后的Header和Payload用"."连接,再使用密钥和签名算法生成签名
    3. 获取签名方式:

      HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret)
      

三、优缺点

  1. 优点
    1. JWT 是自包含的(内部包含了一些会话信息),因此减少了查询数据库的需要,有效使用 JWT,可以降低服务器查询数据库的次数
    2. JWT 不仅可以用于认证,也可以用于交换信息
    3. JWT 并不使用 Cookie 的,所以可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
  2. 缺点
    1. JWT 的最大缺点:由于服务器不保存 session 状态,因此无法在使用过程中废止或更改某个 token 的权限。一旦 JWT 签发,在到期之前就会始终有效
    2. JWT 默认不加密,但也是可以加密(生成原始 Token 以后,可以用密钥再加密一次)
    3. JWT 不加密的情况下,不能将秘密数据写入 JWT
    4. JWT 本身包含了认证信息,一旦泄露,任何人都可以通过 JWT 获得该 JWT 的所有权限

四、工作流程



代码实现

⭐参考资料:

  1. https://blog.csdn.net/gitblog_09788/article/details/143407938#:~:text=1 JWT集成:生成安全的JWT令牌,用于用户身份验证。 2 Redis存储:将刷新令牌存储在Redis中,利用其高速特性进行快速校验。 3 双Token机制:,访问令牌:短寿命,用于直接的API访问。 刷新令牌:长寿命,用于在访问令牌过期时自动获取新令牌。 4 自动刷新:当访问令牌过期时,系统可自动利用有效的刷新令牌获取新的访问令牌。 5 安全性强化:通过Redis的过期策略、JWT的签名验证以及适当的访问控制,增强系统的安全性。
  2. GitHub - dolyw/ShiroJwt: API SpringBoot + Shiro + Java-Jwt + Redis(Jedis) 

方案设计

  1. 方案对比

    特性JWT + Redis 白名单JWT + Redis 存储用户会话
    设计理念无状态为主,Redis 仅辅助校验强状态管理,Redis 为中心,JWT 辅助
    Redis 存储压力存储 jti 或少量数据,存储压力小存储完整会话信息,存储占用较高
    实现复杂度较低,直接存储 jti 和过期时间,校验简单较高,需要设计用户会话结构、处理多端登录等逻辑
    主动失效能力易实现:只需从 Redis 删除对应的 jti 即可易实现:删除会话即可失效
    会话扩展能力较弱,只适合验证 Token 是否有效强,可以存储用户登录的扩展信息(设备、角色等)
    支持多端登录较弱,需要额外逻辑强,天然支持多个会话实例
    性能开销性能更高(JWT 主要靠自包含验证,少量 Redis 查询)Redis 频繁交互性能略低,适合中等并发的场景
    业务需求复杂度简单业务场景复杂业务场景
  2. 方案选择:JWT + Redis 存储用户会话,可以存储更多用户信息,并且没有泄露风险

校验请求

  1. 目标:过滤所有 token 为空或不合法的请求

  2. 代码(com.lloop.authcheckdemo.interceptor.UserLoginFilter)

    @Slf4j
    @Component
    public class UserLoginFilter implements HandlerInterceptor {
    
        @Resource
        JwtUtils jwtUtils;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            // 1. 获取token
            String token = request.getHeader(jwtUtils.header);
    
            // 2. 判断token是否有效
            ThrowUtils.throwIf(StringUtils.isEmpty(token), ErrorCode.NULL_ERROR, "请登录后操作!");
            ThrowUtils.throwIf(jwtUtils.isTokenExpired(token), ErrorCode.PARAMS_ERROR, "登录已过期!");
            ThrowUtils.throwIf(jwtUtils.checkBlacklist(token), ErrorCode.PARAMS_ERROR, "用户已被禁止登录!");
    
            // 3. token有效 => 记录登录用户信息
            UserTokenInfo userTokenInfo = jwtUtils.getUserInfoToken(token);
            ThrowUtils.throwIf(ObjectUtils.isEmpty(userTokenInfo), ErrorCode.NULL_ERROR, "对不起,身份认证出现错误,请重新登录...");
            UserHolder.saveUser(userTokenInfo);
    
            return true;
        }
    
        /**
         * 移除用户信息,防止内存溢出
         * @param request
         * @param response
         * @param handler
         * @param ex
         * @throws Exception
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            UserHolder.removeUser();
        }
    
    }
    

注册拦截器

  1. 目标:将校验请求的拦截器注册到项目中

  2. 注意:只需要匹配 controller 的路径部分,server.servlet.context-path 不用管

  3. 代码

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Resource
        private UserLoginFilter userLoginFilter;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(userLoginFilter)
                    .addPathPatterns("/**")
                    .excludePathPatterns("/login", "/register");
        }
    
    }
    

创建用户信息类

  1. 目标:解析用户token中存储的信息,存入 UserHolder 中供 Controller 调用

  2. 代码

    @Data
    public class UserTokenInfo {
    
        /**
         * ID,唯一
         */
        private Long id;
    
        /**
         * 账号
         */
        private String account;
    
        /**
         * 用户昵称
         */
        private String username;
    
        /**
         * 用户角色 0 - 普通用户 1 - 管理员
         */
        private Integer role;
    
    }
    

创建JWT工具类

  1. 目标:创建、校验、解析 token

  2. 代码

    @Data
    @Component
    public class JwtUtils {
    
        @Value("${jwt.secret}")
        public String secret;
    
        @Value("${jwt.header}")
        public String header;
    
        @Value("${jwt.expire.accessToken}")
        public Integer accessTokenExpire;
    
        @Value("${jwt.expire.refreshToken}")
        public Integer refreshTokenExpire;
    
        @Resource
        RedisUtils redisUtils;
    
        private static final Gson gson = new Gson();
        
        
        /**
         * 获取用户信息
         *
         * @param token
         * @return
         */
        public UserTokenInfo getUserInfoToken(String token) {
            String subject = getTokenClaim(token).getSubject();
            UserTokenInfo userTokenInfo = gson.fromJson(subject, UserTokenInfo.class);
            return userTokenInfo;
        }
        
        /**
         * 获取 token 中注册信息
         *
         * @param token
         * @return
         */
        public Claims getTokenClaim(String token) {
            try {
                return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            } catch (Exception e) {
                return null;
            }
        }
        
    		
    }
    


http://www.kler.cn/a/457864.html

相关文章:

  • 我的博客年度之旅:感恩、成长与展望
  • 我用AI学Android Jetpack Compose之开篇
  • C++并发编程之内存顺序一致性
  • 设计模式 创建型 单例模式(Singleton Pattern)与 常见技术框架应用 解析
  • 网络分析工具-tcpdump
  • 三维场景重建3D高斯点渲染复现
  • C++ 设计模式:享元模式(Flyweight Pattern)
  • c#中using语句
  • 【LLM综述】29种大模型Prompt Engineering技术
  • 【大语言模型】LangChain 核心模块介绍(Memorys)
  • Python Polars快速入门指南:LazyFrames
  • 苹果手机iOS18.2系统苹果手机便签测评
  • Type-C接口的拍摄云台
  • OpenCV-Python实战(13)——图像轮廓
  • 【每日学点鸿蒙知识】Provider、Navigation返回参数、隐私声明问题、Text判断函数、自定义hvigor插件
  • 初入图像处理:水稻剑叶夹角测量
  • 【Hackthebox 中英 Write-Up】通过 POST 请求绕过前端限制:基于 Cookie 的认证与数据提取实操指南
  • AI大模型语音识别转文字
  • 在 CentOS 7 上安装 Node.js 20 并升级 GCC、make 和 glibc
  • 图像处理-Ch7-快速小波变换和小波包
  • redis cluster实验详解
  • 蓝桥杯速成教程{三}(adc,i2c,uart)
  • vulhub-wordpress靶场
  • 区块链安全常见的攻击合约和简单复现,附带详细分析——不安全调用漏洞 (Unsafe Call Vulnerability)【6】
  • 【513. 找树左下角的值 中等】
  • 【Leetcode刷题随笔】977 有序数组的平方