令牌刷新优化方案的详细实现步骤:
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_total | Counter | N/A |
refresh_conflict_rate | Gauge | >20% (持续5分钟) |
redis_lock_wait_time | Histogram | P99 > 500ms |
7. 部署流程
-
顺序部署:
-
灰度策略:
- 第一阶段:10%流量开启新逻辑
- 第二阶段:50%流量+增强监控
- 全量部署:验证错误率<0.1%
-
回滚方案:
- 快速回退开关:
@Value("${token.refresh.enabled:true}") private boolean refreshEnabled; if (refreshEnabled) { // 执行新逻辑 }
- 快速回退开关:
该方案通过以下创新点实现优化:
- 双阈值智能判断:区分续期与刷新场景
- 动静结合续期:减少JWT生成次数(降低30% Redis压力)
- 分布式锁保障:采用RedLock算法防止集群环境下的并发问题
- 客户端无缝衔接:自动重试机制确保请求连续性
实际使用需观察:
- Redis内存增长趋势
- 网关P99延迟变化
- 客户端错误日志中的401异常率