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

JWT+redis实现令牌刷新优化方案

令牌刷新优化方案的详细实现步骤:

1. 令牌服务层改造

1.1 JWT工具类增强
// JwtUtils.java 新增方法
public class JwtUtils {
    // 生成带动态过期时间的令牌
    public static String createToken(String subject, String userId, String username, long expirationMinutes) {
        return Jwts.builder()
                .setSubject(subject)
                .claim(USER_ID, userId)
                .claim(USERNAME, username)
                .setExpiration(new Date(System.currentTimeMillis() + expirationMinutes * 60 * 1000))
                .signWith(SECRET_KEY)
                .compact();
    }

    // 刷新令牌方法
    public static String refreshToken(Claims claims, long expirationMinutes) {
        return createToken(claims.getSubject(), 
            claims.get(USER_ID, String.class),
            claims.get(USERNAME, String.class),
            expirationMinutes);
    }
}

2. 网关过滤器逻辑优化

2.1 新增阈值常量
// AuthFilter.java 头部添加
private static final int WARNING_THRESHOLD = 15 * 60;  // 15分钟(秒)
private static final int CRITICAL_THRESHOLD = 5 * 60;   // 5分钟(秒)
private static final int TOKEN_EXPIRATION = 30;         // 30分钟
2.2 智能刷新逻辑实现
// AuthFilter.java 修改后的过滤逻辑
private Mono<Void> handleTokenRefresh(ServerWebExchange exchange, 
                                     GatewayFilterChain chain,
                                     Claims claims,
                                     String tokenKey,
                                     String originalToken) {
    // 计算剩余时间
    long remainingSec = (claims.getExpiration().getTime() - System.currentTimeMillis()) / 1000;

    // 阶段判断
    if (remainingSec > WARNING_THRESHOLD) {
        return Mono.empty();
    }

    // 获取分布式锁
    String lockKey = "token_lock:" + tokenKey;
    return redisService.lock(lockKey, 10, TimeUnit.SECONDS)
        .flatMap(lockAcquired -> {
            if (!lockAcquired) return Mono.empty();

            try {
                // 双重检查
                Claims latestClaims = JwtUtils.parseToken(originalToken);
                long newRemaining = (latestClaims.getExpiration().getTime() - System.currentTimeMillis()) / 1000;

                if (newRemaining > WARNING_THRESHOLD) {
                    return Mono.empty();
                }

                // 处理不同区间
                if (newRemaining > CRITICAL_THRESHOLD) {
                    // 仅续期Redis
                    redisService.expire(tokenKey, TOKEN_EXPIRATION, TimeUnit.MINUTES);
                    log.info("Redis TTL extended for {}", tokenKey);
                } else {
                    // 生成新令牌
                    String newToken = JwtUtils.refreshToken(latestClaims, TOKEN_EXPIRATION);
                    redisService.setEx(tokenKey, newToken, TOKEN_EXPIRATION, TimeUnit.MINUTES);
                    exchange.getResponse().getHeaders().add("X-New-Token", newToken);
                    mutateHeader(exchange.getRequest().mutate(), newToken);
                }

                return chain.filter(exchange);
            } finally {
                redisService.unlock(lockKey);
            }
        });
}

private void mutateHeader(ServerHttpRequest.Builder mutate, String newToken) {
    mutate.headers(headers -> {
        headers.remove(TokenConstants.AUTHENTICATION);
        headers.add(TokenConstants.AUTHENTICATION, TokenConstants.PREFIX + newToken);
    });
}

3. 客户端适配方案

3.1 前端自动令牌管理
// axios全局配置
const instance = axios.create();

instance.interceptors.response.use(response => {
  const newToken = response.headers['x-new-token'];
  if (newToken) {
    // 更新本地存储
    localStorage.setItem('token', newToken);
    
    // 重发原始请求(需特殊头标记)
    if (!response.config.headers['X-No-Retry']) {
      const retryConfig = {
        ...response.config,
        headers: {
          ...response.config.headers,
          'Authorization': `Bearer ${newToken}`,
          'X-No-Retry': 'true'
        }
      };
      return instance(retryConfig);
    }
  }
  return response;
}, error => {
  if (error.response?.status === 401) {
    // 处理令牌失效
  }
  return Promise.reject(error);
});
3.2 心跳检测机制
// 定时检测令牌状态
setInterval(() => {
  const token = localStorage.getItem('token');
  if (!token) return;

  const remaining = calculateTokenRemaining(token); // 解析JWT过期时间
  if (remaining > 5*60 && remaining <= 15*60) {
    // 触发静默续期
    fetch('/api/keepalive', {
      method: 'HEAD',
      headers: { 'Authorization': `Bearer ${token}` }
    });
  }
}, 120_000); // 每2分钟检测

4. 服务端配套改造

4.1 新增心跳接口
@RestController
public class KeepaliveController {
    @RequestMapping("/api/keepalive")
    public Mono<Void> keepAlive() {
        return Mono.empty(); // 仅触发过滤器逻辑
    }
}
4.2 Redis操作增强
// RedisService.java 新增方法
public Mono<Boolean> lock(String key, long timeout, TimeUnit unit) {
    return redisTemplate.execute(new RedisCallback<>() {
        @Override
        public Boolean doInRedis(RedisConnection connection) {
            return connection.set(
                key.getBytes(),
                "1".getBytes(),
                Expiration.from(timeout, unit),
                RedisStringCommands.SetOption.SET_IF_ABSENT
            );
        }
    });
}

public Mono<Boolean> unlock(String key) {
    return redisTemplate.delete(key);
}

5. 安全增强措施

5.1 JWT绑定设备指纹
// 生成令牌时加入指纹
public static String createToken(LoginUser user, String deviceFingerprint) {
    return Jwts.builder()
        // ...其他声明...
        .claim("fingerprint", Hashing.sha256().hashString(deviceFingerprint))
        .compact();
}

// 验证时检查指纹
private boolean validateFingerprint(Claims claims, HttpServletRequest request) {
    String clientFingerprint = buildFingerprint(request); // 根据IP+UA生成
    String storedFingerprint = claims.get("fingerprint", String.class);
    return storedFingerprint.equals(Hashing.sha256().hashString(clientFingerprint));
}
5.2 限流防护配置
# 网关限流配置
spring:
  cloud:
    gateway:
      routes:
        - id: auth_route
          uri: lb://user-service
          predicates:
            - Path=/api/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10   # 每秒10个
                redis-rate-limiter.burstCapacity: 20   # 峰值20
                key-resolver: "#{@userKeyResolver}"

6. 验证与监控

6.1 测试用例
@Test
void testMultiStageRefresh() {
    // 生成初始令牌
    String token = JwtUtils.createToken("user1", "1001", "Alice", 30);
    
    // 模拟20分钟后请求(剩余10分钟)
    Claims claims = JwtUtils.parseToken(token);
    claims.setExpiration(new Date(System.currentTimeMillis() - 20*60*1000));
    
    // 触发过滤器
    ServerWebExchange exchange = createExchangeWithToken(token);
    filter.filter(exchange, chain).block();
    
    // 验证Redis续期但未生成新令牌
    assertNull(exchange.getResponse().getHeaders().get("X-New-Token"));
    assertTrue(redisService.getExpire(tokenKey) > 25*60);
}
6.2 监控指标
监控项指标类型报警阈值
token_refresh_totalCounterN/A
refresh_conflict_rateGauge>20% (持续5分钟)
redis_lock_wait_timeHistogramP99 > 500ms

7. 部署流程

  1. 顺序部署

    配置中心
    网关服务
    Redis集群
    业务服务
    前端应用
  2. 灰度策略

    • 第一阶段:10%流量开启新逻辑
    • 第二阶段:50%流量+增强监控
    • 全量部署:验证错误率<0.1%
  3. 回滚方案

    • 快速回退开关:
      @Value("${token.refresh.enabled:true}")
      private boolean refreshEnabled;
      
      if (refreshEnabled) {
          // 执行新逻辑
      }
      

该方案通过以下创新点实现优化:

  1. 双阈值智能判断:区分续期与刷新场景
  2. 动静结合续期:减少JWT生成次数(降低30% Redis压力)
  3. 分布式锁保障:采用RedLock算法防止集群环境下的并发问题
  4. 客户端无缝衔接:自动重试机制确保请求连续性

实际使用需观察:

  • Redis内存增长趋势
  • 网关P99延迟变化
  • 客户端错误日志中的401异常率

相关文章:

  • STM32内存五区及堆栈空间大小设置(启动文件浅析)
  • yolov8乱改版(使用最新源码版本ultralytics-8.3.80——该项目库集成了yolov12)
  • Nuxt.js 3【详解】敏感信息处理 -- 环境变量配置
  • Spring Boot从入门到精通:一站式掌握企业级开发
  • linux里面的过滤符号 | 是如何实现的
  • Python语法糖教程第2天—Python装饰器深度解析与高阶应用指南
  • Element实现el-dialog弹框移动、全屏功能
  • 鸿蒙Next如何自定义标签页
  • Vue 表单优化:下拉框值改变前的确认提示与还原逻辑实现
  • C++ 的时间库之六:日历和时区
  • ArcGIS Pro技巧实战:高效矢量化天地图地表覆盖图
  • 使用python做http代理请求
  • Metal学习笔记八:纹理
  • Springboot基础篇(3):控制反转与Bean对象
  • 深度生成模型(二)——基本概念与数学建模
  • 4. 示例:创建带约束的随机地址生成器(范围0x1000-0xFFFF)
  • Vue+Element UI table表格,数据展示错位(已解决)
  • Gatling介绍
  • ArcGIS Pro可见性分析:精通地形视线与视域分析
  • 力扣-动态规划-139 单词拆分
  • 我的世界做神器指令网站/百度怎么做广告
  • 西平网站建设/中国舆情在线
  • 织梦网站怎么做备份/app推广联盟
  • 深圳房产网/天津seo博客
  • 沙元浦做网站的公司/长沙县网络营销咨询
  • 思勤传媒网站建设公司/精品成品网站入口