令牌刷新优化方案的详细实现步骤:
1. 令牌服务层改造
1.1 JWT工具类增强
java">// 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 新增阈值常量
java">// 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 智能刷新逻辑实现
java">// 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) {// 仅续期RedisredisService.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 前端自动令牌管理
javascript">// 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 心跳检测机制
javascript">// 定时检测令牌状态
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 新增心跳接口
java">@RestController
public class KeepaliveController {@RequestMapping("/api/keepalive")public Mono<Void> keepAlive() {return Mono.empty(); // 仅触发过滤器逻辑}
}
4.2 Redis操作增强
java">// RedisService.java 新增方法
public Mono<Boolean> lock(String key, long timeout, TimeUnit unit) {return redisTemplate.execute(new RedisCallback<>() {@Overridepublic 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绑定设备指纹
java">// 生成令牌时加入指纹
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_routeuri: lb://user-servicepredicates:- Path=/api/**filters:- name: RequestRateLimiterargs:redis-rate-limiter.replenishRate: 10 # 每秒10个redis-rate-limiter.burstCapacity: 20 # 峰值20key-resolver: "#{@userKeyResolver}"
6. 验证与监控
6.1 测试用例
java">@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_total | Counter | N/A |
refresh_conflict_rate | Gauge | >20% (持续5分钟) |
redis_lock_wait_time | Histogram | P99 > 500ms |
7. 部署流程
-
顺序部署:
-
灰度策略:
- 第一阶段:10%流量开启新逻辑
- 第二阶段:50%流量+增强监控
- 全量部署:验证错误率<0.1%
-
回滚方案:
- 快速回退开关:
java">@Value("${token.refresh.enabled:true}") private boolean refreshEnabled;if (refreshEnabled) {// 执行新逻辑 }
- 快速回退开关:
该方案通过以下创新点实现优化:
- 双阈值智能判断:区分续期与刷新场景
- 动静结合续期:减少JWT生成次数(降低30% Redis压力)
- 分布式锁保障:采用RedLock算法防止集群环境下的并发问题
- 客户端无缝衔接:自动重试机制确保请求连续性
实际使用需观察:
- Redis内存增长趋势
- 网关P99延迟变化
- 客户端错误日志中的401异常率