初始代码
This commit is contained in:
287
yawei-mes/.tasks/2026-02-25_token滑动机制.md
Normal file
287
yawei-mes/.tasks/2026-02-25_token滑动机制.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# 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. **用户登录**
|
||||
- 创建Token(UUID)
|
||||
- 设置初始过期时间 = 当前时间 + 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. **安全可控**:双重过期机制,支持主动失效
|
||||
|
||||
**核心优势**:在保证安全性的前提下,最大化提升用户体验,避免频繁登录带来的困扰。
|
||||
Reference in New Issue
Block a user