正常 JWT 请求流程

sequenceDiagram
    participant User as 用户
    participant Frontend as 前端(浏览器/App)
    participant Auth as 认证服务器
    participant Backend as 后端API服务器

    Note over User,Backend: 1. 登录阶段
    User->>Frontend: 输入用户名/密码
    Frontend->>Auth: POST /login (凭证)
    Auth->>Auth: 验证凭证
    Auth-->>Frontend: 返回 access_token + refresh_token
    Frontend->>Frontend: 存储token(内存/localStorage)
    Frontend-->>User: 登录成功

    Note over User,Backend: 2. 正常请求阶段
    User->>Frontend: 请求受保护资源
    Frontend->>Backend: GET /api/resource
Authorization: Bearer access_token Backend->>Backend: 验证access_token签名和过期时间 Backend-->>Frontend: 返回请求的资源 Frontend-->>User: 展示数据 Note over User,Backend: 3. Access Token过期 User->>Frontend: 继续请求 Frontend->>Backend: GET /api/resource
Authorization: Bearer access_token(已过期) Backend-->>Frontend: 401 Unauthorized (token过期) Frontend->>Frontend: 检测到401,触发刷新逻辑 Note over Frontend,Auth: 4. 刷新Token阶段 Frontend->>Auth: POST /refresh
refresh_token Auth->>Auth: 验证refresh_token Auth-->>Frontend: 返回新的 access_token(可选的新refresh_token) Frontend->>Frontend: 更新存储的access_token Frontend->>Backend: 重试原请求(新access_token) Backend-->>Frontend: 返回请求的资源 Frontend-->>User: 展示数据

为什么不能只靠JWT

  1. refresh token 发出去后后端不保存,会变成 不可控的长期通行证
  2. 无法主动踢人下线
  3. 无法注销单点设备
  4. 无法判断 token 是否被盗用(攻击者拿到了 refresh token 可以一直刷新 access token)
  5. 无法实现会话管理

使用 refresh tokens 表后的流程图

sequenceDiagram
    participant User as 用户
    participant Frontend as 前端
    participant Auth as 认证服务器
    participant DB as 数据库(refresh_tokens表)
    participant Backend as 后端API服务器

    Note over User,DB: 1. 登录阶段(签发token + 入库)
    User->>Frontend: 输入用户名/密码
    Frontend->>Auth: POST /login
    Auth->>Auth: 验证凭证
    Auth->>DB: 生成唯一refresh_token_id
INSERT INTO refresh_tokens
(user_id, token_hash, expires_at,
device_info, ip_address, revoked) DB-->>Auth: 插入成功 Auth->>Auth: 生成access_token + refresh_token
(refresh_token包含id引用) Auth-->>Frontend: 返回access_token + refresh_token Frontend->>Frontend: 存储token Frontend-->>User: 登录成功 Note over User,Backend: 2. 正常请求(与之前相同) User->>Frontend: 请求资源 Frontend->>Backend: GET /api/resource
Authorization: Bearer access_token Backend->>Backend: 验证access_token Backend-->>Frontend: 返回资源 Note over Frontend,DB: 3. Access Token过期 → 刷新 Frontend->>Backend: GET /api/resource (access_token过期) Backend-->>Frontend: 401 Unauthorized Frontend->>Auth: POST /refresh
refresh_token Note over Auth,DB: 4. 刷新验证(多步校验) Auth->>Auth: 解析refresh_token,提取token_id Auth->>DB: SELECT * FROM refresh_tokens
WHERE id = token_id DB-->>Auth: 返回记录 Auth->>Auth: 校验清单: Note over Auth: ✅ token_hash是否匹配
✅ 是否过期 (expires_at > now())
✅ 是否被撤销 (revoked = false)
✅ 用户是否仍有效
✅ 设备信息是否一致(可选) alt 校验全部通过 Auth->>DB: UPDATE refresh_tokens
SET last_used_at = now(),
last_used_ip = current_ip
WHERE id = token_id Auth->>Auth: 生成新access_token
(可选:延长refresh_token有效期) Auth-->>Frontend: 返回新access_token Frontend->>Frontend: 更新access_token Frontend->>Backend: 重试原请求(新token) Backend-->>Frontend: 返回资源 else 校验失败 Auth-->>Frontend: 401/403 (刷新失败) Frontend->>Frontend: 清除所有token Frontend->>User: 跳转登录页 end Note over User,DB: 5. 主动登出 User->>Frontend: 点击登出 Frontend->>Auth: POST /logout
refresh_token Auth->>DB: UPDATE refresh_tokens
SET revoked = true
WHERE id = token_id Auth-->>Frontend: 登出成功 Frontend->>Frontend: 清除本地token Frontend-->>User: 已登出 Note over User,DB: 6. 安全场景:密码修改 User->>Frontend: 修改密码 Frontend->>Auth: POST /change-password Auth->>DB: UPDATE refresh_tokens
SET revoked = true
WHERE user_id = current_user_id Note over DB: 撤销该用户所有refresh_token
(强制所有设备重新登录) Auth-->>Frontend: 密码修改成功 Frontend->>Frontend: 清除本地token Frontend-->>User: 请重新登录