SLIDING_TOKEN_README.md 9.1 KB

滑动过期Token机制使用说明

📋 功能概述

实现了滑动过期(Sliding Expiration)Token机制,确保用户在活跃使用系统时不会遇到token超时问题。

🎯 核心特性

  • 自动刷新:每次API请求时自动检查并刷新token
  • 滑动过期:只要用户保持活跃,token永远不会过期
  • 安全性:完全无活动时,token会在设定时间后过期
  • 透明化:前端无需特殊处理,自动更新token
  • 向后兼容:不影响现有API接口

⚙️ 配置项

config.ini 中添加以下配置:

# 后台管理Token配置
ADMIN_TOKEN_EXPIRE_MINUTES=10          # 访问token有效期(分钟)
ADMIN_REFRESH_TOKEN_EXPIRE_HOURS=24    # 刷新token有效期(小时)

🔧 工作原理

1. Token刷新时机

  • 当token使用时间超过总有效期的50%时自动刷新
  • 例如:10分钟有效期的token,在使用5分钟后会被刷新

2. 刷新流程

用户请求API → 验证token → 检查是否需要刷新 → 生成新token → 返回响应 + 新token

3. 前端处理

  • 检查响应头中的 X-New-Token
  • 如果存在,更新本地存储的token
  • 后续请求使用新token

🚀 使用方法

方法1:使用依赖注入(推荐)

from app.utils.auth_decorator import get_current_user_with_sliding_expiration

@router.get("/my-api")
async def my_api(
    user_info: dict = Depends(get_current_user_with_sliding_expiration)
):
    user = user_info["user"]          # 当前用户对象
    new_token = user_info["new_token"] # 新token(可能为None)
    
    # 构造响应
    response_data = {"message": "success", "data": {...}}
    
    # 如果token被刷新,添加到响应中
    if new_token:
        response_data["token_refreshed"] = True
        response_data["new_token"] = new_token
    
    return response_data

方法2:手动调用AuthService

from app.services.auth_service import AuthService

@router.get("/manual-api")
async def manual_api(
    credentials: HTTPAuthorizationCredentials = Depends(security),
    db: AsyncSession = Depends(get_db_connection)
):
    auth_service = AuthService(db)
    user, new_token = await auth_service.get_current_user(credentials.credentials)
    
    if not user:
        return {"code": 401, "message": "无效的访问令牌"}
    
    response_data = {"code": 0, "message": "success", "data": {...}}
    
    if new_token:
        response_data["token_refreshed"] = True
        response_data["new_token"] = new_token
    
    return response_data

方法3:使用中间件(全局)

在主应用文件中添加:

from app.middleware.token_refresh_middleware import TokenRefreshMiddleware

app.add_middleware(
    TokenRefreshMiddleware,
    exclude_paths=[
        "/auth/login",
        "/auth/captcha", 
        "/docs",
        "/openapi.json"
    ]
)

💻 前端集成

JavaScript示例

class ApiClient {
    constructor() {
        this.token = localStorage.getItem('access_token');
    }
    
    async request(url, options = {}) {
        const headers = {
            'Authorization': `Bearer ${this.token}`,
            ...options.headers
        };
        
        const response = await fetch(url, { ...options, headers });
        const data = await response.json();
        
        // 检查token刷新
        if (data.token_refreshed && data.new_token) {
            this.token = data.new_token;
            localStorage.setItem('access_token', data.new_token);
            console.log('Token已自动刷新');
        }
        
        // 也可以检查响应头
        const newToken = response.headers.get('X-New-Token');
        if (newToken) {
            this.token = newToken;
            localStorage.setItem('access_token', newToken);
            console.log('Token已通过响应头刷新');
        }
        
        return data;
    }
}

Vue.js示例

// axios拦截器
axios.interceptors.response.use(
    response => {
        // 检查响应数据中的token刷新
        if (response.data?.token_refreshed && response.data?.new_token) {
            const newToken = response.data.new_token;
            localStorage.setItem('access_token', newToken);
            axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
            console.log('Token已刷新');
        }
        
        // 检查响应头中的token刷新
        const headerToken = response.headers['x-new-token'];
        if (headerToken) {
            localStorage.setItem('access_token', headerToken);
            axios.defaults.headers.common['Authorization'] = `Bearer ${headerToken}`;
            console.log('Token已通过响应头刷新');
        }
        
        return response;
    },
    error => {
        if (error.response?.status === 401) {
            // token完全过期,跳转登录
            router.push('/login');
        }
        return Promise.reject(error);
    }
);

📊 响应格式

普通响应

{
    "code": 0,
    "message": "success",
    "data": {...}
}

Token刷新时的响应

{
    "code": 0,
    "message": "success", 
    "data": {...},
    "token_refreshed": true,
    "new_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}

响应头:

X-New-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
X-Token-Refreshed: true

🔍 测试验证

运行测试脚本

cd LQAdminPlatform
python test_sliding_token.py

测试结果示例

🚀 开始测试滑动过期Token机制
============================================================

🧪 测试4: 配置加载
✅ 后台管理Token过期时间: 10 分钟
✅ 通用Token过期时间: 30 分钟
✅ 刷新Token过期时间: 24 小时
🎯 实际使用的过期时间: 10 分钟

🧪 测试1: Token创建
✅ Token创建成功: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
✅ Token验证成功: 用户=testuser
   过期时间: 2026-01-30 04:06:08+00:00

🎉 所有测试完成!

🧪 API测试示例

测试滑动过期API

# 1. 登录获取token
curl -X POST "http://localhost:8000/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "password"}'

# 2. 使用token调用API(支持滑动过期)
curl -X GET "http://localhost:8000/system/info" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"

# 3. 检查响应中的token_refreshed字段

预期响应

{
    "code": 0,
    "message": "获取系统信息成功",
    "data": {
        "system_name": "LQ后台管理系统",
        "version": "1.0.0",
        "current_time": "2026-01-30T11:56:08.325863+00:00",
        "current_user": {
            "id": "user123",
            "username": "admin",
            "email": "admin@example.com",
            "is_superuser": true
        }
    },
    "token_refreshed": true,
    "new_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}

🔍 调试和监控

日志输出

INFO - Token已刷新,用户: admin
INFO - 系统信息API - Token已刷新,用户: admin

检查token状态

from app.services.jwt_token import verify_and_refresh_token

payload, new_token = verify_and_refresh_token(current_token)
if new_token:
    print("Token需要刷新")
else:
    print("Token仍然有效")

⚠️ 注意事项

  1. 配置优先级:优先使用 ADMIN_TOKEN_EXPIRE_MINUTES,回退到 ACCESS_TOKEN_EXPIRE_MINUTES
  2. 数据库更新:每次token刷新都会更新数据库中的token记录
  3. 并发处理:多个并发请求可能同时触发token刷新,这是正常的
  4. 安全考虑:完全无活动的用户仍会在设定时间后被强制登出
  5. 前端处理:建议同时检查响应数据和响应头中的新token

🧪 测试场景

测试1:正常使用

  1. 用户登录获得10分钟有效期token
  2. 5分钟后发起API请求
  3. 系统自动刷新token,返回新的10分钟有效期token
  4. 用户可以继续使用系统

测试2:长时间无活动

  1. 用户登录后10分钟内无任何操作
  2. 10分钟后发起API请求
  3. 系统返回401错误,要求重新登录

测试3:频繁操作

  1. 用户每分钟都有API请求
  2. 系统在第5分钟时刷新token
  3. 用户可以无限期使用系统(只要保持活跃)

📈 性能影响

  • CPU开销:每次请求增加token验证和可能的刷新操作
  • 数据库开销:token刷新时需要更新数据库记录
  • 网络开销:响应中增加新token信息
  • 整体影响:轻微,用户体验显著提升

🔄 升级指南

从旧版本升级

  1. 更新配置文件,添加新的token配置项
  2. 重启应用服务
  3. 前端代码添加token自动更新逻辑
  4. 测试验证功能正常

回滚方案

如需回滚到旧的token机制:

  1. 移除中间件配置
  2. 使用原有的 verify_token 函数
  3. 恢复原有的API装饰器

总结:滑动过期Token机制大大提升了用户体验,用户在正常使用过程中永远不会遇到token超时问题,同时保持了系统的安全性。配置简单,使用方便,前端集成透明。