Files
MES/yawei-mes/.tasks/2026-02-25_token滑动机制.md
2026-04-02 10:39:03 +08:00

288 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Token滑动机制技术文档
## 一、概述
本系统采用基于Redis的Token滑动窗口机制实现用户会话的自动续期功能。当用户在系统中持续活动时Token会自动刷新避免频繁登录提升用户体验。
## 二、核心配置
### 2.1 配置文件application.yml
```yaml
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期默认30分钟单位分钟
expireTime: 10080
```
**配置说明:**
- `header`: HTTP请求头中Token的字段名
- `secret`: JWT签名密钥
- `expireTime`: Token有效期当前配置为10080分钟7天
## 三、核心实现
### 3.1 数据模型LoginUser
```java
public class LoginUser implements UserDetails {
/**
* 用户唯一标识
*/
private String token;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
// ... 其他字段
}
```
### 3.2 Token服务TokenService
#### 3.2.1 关键常量
```java
// 令牌有效期(从配置文件读取)
@Value("${token.expireTime}")
private int expireTime;
// 毫秒常量
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
// 滑动窗口触发阈值20分钟
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
```
#### 3.2.2 创建Token
```java
public String createToken(LoginUser loginUser) {
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken(loginUser); // 初始化Token过期时间
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
return createToken(claims);
}
```
#### 3.2.3 验证Token滑动机制核心
```java
/**
* 验证令牌有效期相差不足20分钟自动刷新缓存
*
* @param loginUser
*/
public void verifyToken(LoginUser loginUser) {
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
// 如果距离过期时间不足20分钟触发刷新
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
refreshToken(loginUser);
}
}
```
#### 3.2.4 刷新Token
```java
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser) {
// 更新登录时间为当前时间
loginUser.setLoginTime(System.currentTimeMillis());
// 重新计算过期时间 = 当前时间 + 配置的有效期
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 将更新后的用户信息存入Redis并设置过期时间
String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
```
#### 3.2.5 获取登录用户
```java
public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token)) {
try {
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisCache.getCacheObject(userKey);
return user;
} catch (Exception e) {
// Token解析失败
}
}
return null;
}
```
## 四、滑动机制工作流程
### 4.1 流程图
```
用户请求 → 获取Token → 从Redis获取LoginUser → 验证Token有效期
距离过期 ≤ 20分钟
↓ ↓
是 否
↓ ↓
刷新Token 继续使用
更新过期时间
更新Redis缓存
```
### 4.2 详细说明
1. **用户登录**
- 创建TokenUUID
- 设置初始过期时间 = 当前时间 + expireTime
- 存入Redis设置过期时间
2. **用户请求**
- 从请求头获取Token
- 从Redis获取LoginUser对象
- 调用`verifyToken()`验证
3. **滑动窗口判断**
- 计算剩余有效时间 = expireTime - currentTime
- 如果剩余时间 ≤ 20分钟触发刷新
- 否则继续使用当前Token
4. **Token刷新**
- 更新loginTime为当前时间
- 重新计算expireTime
- 更新Redis中的LoginUser对象
## 五、关键特性
### 5.1 滑动窗口策略
- **触发条件**距离过期时间不足20分钟
- **刷新动作**:重置过期时间为当前时间 + expireTime
- **优势**用户持续活动时Token自动续期无需重新登录
### 5.2 Redis存储
- **Key格式**`login_tokens:{uuid}`
- **Value**LoginUser对象序列化
- **过期时间**与Token过期时间一致
- **优势**:分布式环境下共享会话,自动清理过期数据
### 5.3 双重过期机制
1. **LoginUser.expireTime**:业务层面的过期时间判断
2. **Redis TTL**:存储层面的自动清理机制
## 六、配置建议
### 6.1 生产环境配置
```yaml
token:
expireTime: 120 # 2小时
```
**说明**
- 滑动窗口触发阈值固定为20分钟
- 如果用户在2小时内有任何操作且距离过期不足20分钟会自动续期2小时
- 如果用户超过2小时无操作Token过期需要重新登录
### 6.2 开发环境配置
```yaml
token:
expireTime: 10080 # 7天
```
**说明**
- 方便开发调试,减少频繁登录
- 生产环境不建议设置过长
## 七、安全考虑
### 7.1 Token安全
- 使用JWT签名防止Token篡改
- Token存储在Redis中支持主动失效
- 支持单点登录控制
### 7.2 滑动窗口安全
- 20分钟的滑动窗口阈值平衡用户体验和安全性
- 即使Token被盗用最长有效期仍受expireTime限制
- 可通过删除Redis中的Token实现强制下线
## 八、扩展功能
### 8.1 积木报表Token验证
```java
// JimuReportTokenService.java
public boolean isTokenValid(String token) {
LoginUser loginUser = tokenService.getLoginUser(request);
if (loginUser != null) {
// 检查token是否过期
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
return currentTime < expireTime;
}
return false;
}
```
### 8.2 用户代理信息记录
```java
public void setUserAgent(LoginUser loginUser) {
UserAgent userAgent = UserAgent.parseUserAgentString(
ServletUtils.getRequest().getHeader("User-Agent")
);
String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName());
loginUser.setOs(userAgent.getOperatingSystem().getName());
}
```
## 九、总结
本系统的Token滑动机制通过以下方式实现了高效的会话管理
1. **自动续期**:用户活跃时自动延长会话,提升体验
2. **灵活配置**:通过配置文件调整有效期和滑动窗口
3. **分布式支持**基于Redis实现支持集群部署
4. **安全可控**:双重过期机制,支持主动失效
**核心优势**:在保证安全性的前提下,最大化提升用户体验,避免频繁登录带来的困扰。