JWT 认证与紧急失效设计方案

JWT 认证与紧急失效设计方案

一、设计目标

本方案用于解决基于 JWT 的登录认证体系中的以下问题:

  1. 用户正常登录与 API 鉴权
  2. Access Token 过期与自动续期
  3. 用户主动退出登录
  4. 单设备下线
  5. 全设备下线
  6. 密码修改后旧 Token 立即失效
  7. 密码泄露后的紧急失效
  8. 管理员强制踢用户下线
  9. Refresh Token 被盗后的风险控制
  10. 多设备同时登录

核心设计:

Access JWT + Refresh Token + Redis Session + Token Version


二、整体架构

                         ┌──────────────┐
                         │    Client    │
                         └──────┬───────┘
                                │
                         Access Token
                                │
                                ▼
                         ┌──────────────┐
                         │ API Gateway  │
                         │ / Middleware │
                         └──────┬───────┘
                                │
                    ┌───────────┴───────────┐
                    │                       │
              JWT 签名/exp验证          Redis Session
                    │                       │
                    │                 Session 是否有效
                    │                       │
                    └───────────┬───────────┘
                                │
                         Token Version
                                │
                         ┌──────┴──────┐
                         │             │
                       正常           失效
                         │             │
                         ▼             ▼
                       API           401

三、Token 设计

3.1 Access Token

Access Token 使用 JWT。

建议有效期:

15~30 分钟

JWT Payload:

{
    "iss": "your-app",
    "sub": "10001",
    "jti": "550e8400-e29b-41d4-a716-446655440000",
    "sid": "session_xxxxx",
    "token_version": 5,
    "iat": 1790000000,
    "exp": 1790001800
}

字段说明:

字段 作用
iss Token 签发者
sub 用户 ID
jti JWT 唯一 ID
sid 登录 Session ID
token_version Token 版本
iat 签发时间
exp 过期时间

四、Refresh Token

Refresh Token 用于获取新的 Access Token。

建议:

Access Token:15~30 分钟

Refresh Token:7~30 天

Refresh Token 不建议做成长期有效的 JWT。

更推荐:

Refresh Token
       ↓
随机高强度字符串
       ↓
Redis / DB 保存 Token Hash

例如:

refresh_token = 随机 256 bit 字符串

数据库/Redis 保存:

token_hash
user_id
session_id
device
expires_at
revoked

五、Redis Session

每一次登录创建一个 Session。

例如:

auth:session:{sid}

内容:

{
    "uid": 10001,
    "status": "active",
    "device": "mac",
    "login_ip": "xxx.xxx.xxx.xxx",
    "created_at": 1790000000,
    "last_active_at": 1790001000
}

TTL:

Refresh Token 有效期

例如:

auth:session:session_xxxxx

TTL = 30 天

六、Token Version

用户表增加:

ALTER TABLE user
ADD COLUMN token_version INT NOT NULL DEFAULT 1;

例如:

user_id       token_version
10001         5
10002         2
10003         8

JWT 签发时记录:

JWT.token_version = 5

服务端当前版本:

user.token_version = 5

两者一致:

允许访问

不一致:

401 Unauthorized

七、正常登录流程

用户输入账号密码
        │
        ▼
验证密码
        │
        ▼
读取 token_version
        │
        ▼
创建 sid
        │
        ▼
Redis 创建 Session
        │
        ▼
生成 Access JWT
        │
        ▼
生成 Refresh Token
        │
        ▼
返回客户端

返回:

{
    "access_token": "xxxxx",
    "refresh_token": "xxxxx",
    "expires_in": 1800
}

八、正常 API 请求

客户端:

Authorization: Bearer eyJ...

服务端依次执行:

① JWT 格式检查
       ↓
② JWT 签名验证
       ↓
③ exp 验证
       ↓
④ 获取 uid
       ↓
⑤ 获取 sid
       ↓
⑥ 检查 Redis Session
       ↓
⑦ 检查 token_version
       ↓
⑧ 放行

建议顺序:

JWT
 ↓
签名
 ↓
exp
 ↓
sid
 ↓
Redis Session
 ↓
token_version
 ↓
API

九、用户主动退出登录

单设备退出

客户端:

POST /logout

服务端获取:

sid

然后:

DELETE auth:session:{sid}

同时:

Refresh Token revoke

结果:

当前设备立即下线

其他设备不受影响。

例如:

Mac       Session A  active
iPhone    Session B  active
iPad      Session C  active

Mac 点击退出:

Session A → revoked

Session B → active
Session C → active

十、全部设备退出

例如用户点击:

退出所有设备

执行:

UPDATE user
SET token_version = token_version + 1
WHERE id = 10001;

同时删除/吊销该用户所有 Refresh Token 和 Session。

结果:

Mac       → 401
iPhone    → 401
iPad      → 401
Windows   → 401

所有旧 JWT 立即失效。


十一、密码修改

这是整个系统最重要的安全流程之一。

用户修改密码:

旧密码验证
       ↓
设置新密码
       ↓
token_version + 1
       ↓
吊销所有 Refresh Token
       ↓
删除所有 Session
       ↓
要求重新登录

SQL:

UPDATE user
SET
    password = :password,
    token_version = token_version + 1
WHERE id = :user_id;

然后:

DELETE auth:session:{sid}

或者删除该用户的所有 Session。


十二、密码被盗后的紧急处理

假设发生:

用户密码泄露。

即使攻击者已经拿到了:

账号 + 密码

甚至已经登录并取得:

Access JWT
Refresh Token

管理员可以执行:

【紧急下线】

后台执行:

① 修改/重置密码
② token_version + 1
③ 吊销所有 Refresh Token
④ 删除所有 Redis Session
⑤ 记录安全事件

核心操作:

UPDATE user
SET
    token_version = token_version + 1
WHERE id = :user_id;

Redis:

删除:

auth:session:{uid}:*

Refresh Token:

全部 revoked

十三、为什么 Token Version 可以让旧 JWT 立即失效?

例如当前:

数据库:

token_version = 5

JWT:

{
    "uid": 10001,
    "token_version": 5
}

正常访问:

JWT = 5
DB  = 5

允许

发生密码泄露:

DB token_version:

5 → 6

攻击者手里的 JWT:

JWT = 5
DB  = 6

结果:

5 != 6

401 Unauthorized

不需要等待:

exp

所以即使 JWT 原本还有 25 分钟有效期,也可以立即失效。


十四、Redis 优化 Token Version

如果每一次 API 请求都查询 MySQL:

API
 ↓
MySQL
 ↓
token_version

高并发情况下不理想。

可以把 Token Version 放 Redis:

auth:version:{uid}

例如:

auth:version:10001 = 6

API:

JWT.token_version
        ↓
Redis GET auth:version:10001
        ↓
比较

密码泄露:

Redis INCR auth:version:10001

这样可以避免每次请求查询数据库。


十五、Refresh Token 轮换

Refresh Token 建议采用 Rotation。

例如:

Refresh Token A
       ↓
换取 Access Token
       ↓
Refresh Token B

然后:

Refresh Token A → revoked
Refresh Token B → active

如果攻击者再次使用已经 revoked 的 A:

检测到 Refresh Token 重用
       ↓
认为可能发生 Token Theft
       ↓
吊销整个 Session

进一步可以直接:

token_version + 1

让该用户全部登录失效。


十六、多设备登录

推荐每台设备一个 Session:

user 10001

Session A
Mac

Session B
iPhone

Session C
iPad

Redis:

auth:session:A
auth:session:B
auth:session:C

这样可以实现:

当前设备退出

删除 Session A

指定设备退出

删除 Session B

全部设备退出

token_version + 1
+
删除 A/B/C

十七、管理员强制踢人

管理员后台:

用户管理
   ↓
用户 10001
   ↓
强制下线

执行:

token_version + 1

并:

删除该用户所有 Session

结果:

所有设备立即失效

十八、推荐 Redis Key 设计

auth:version:{uid}

auth:session:{sid}

auth:refresh:{refresh_token_hash}

auth:user:sessions:{uid}

例如:

auth:version:10001

auth:session:550e8400...

auth:refresh:8f2c...

auth:user:sessions:10001

其中:

auth:user:sessions:{uid}

可以使用 Redis Set 保存用户当前所有 Session:

SADD auth:user:sessions:10001 sid_A
SADD auth:user:sessions:10001 sid_B
SADD auth:user:sessions:10001 sid_C

全部下线:

SMEMBERS auth:user:sessions:10001

然后逐个删除 Session。


十九、接口设计

建议:

POST /auth/login

POST /auth/refresh

POST /auth/logout

POST /auth/logout-all

POST /auth/change-password

GET  /auth/sessions

DELETE /auth/sessions/{sid}

管理员:

POST /admin/users/{uid}/force-logout

POST /admin/users/{uid}/reset-password

二十、错误码

建议统一:

40101 TOKEN_INVALID
40102 TOKEN_EXPIRED
40103 SESSION_REVOKED
40104 TOKEN_VERSION_MISMATCH
40105 REFRESH_TOKEN_INVALID
40106 REFRESH_TOKEN_REUSED

例如:

{
    "code": 40104,
    "message": "登录状态已失效,请重新登录"
}

客户端收到:

401
 ↓
清除 Access Token
 ↓
清除 Refresh Token
 ↓
跳转登录页

二十一、最核心的安全策略

建议形成下面的规则:

正常退出
    ↓
删除当前 Session

退出所有设备
    ↓
token_version + 1
    ↓
吊销所有 Session

修改密码
    ↓
token_version + 1
    ↓
吊销所有 Session

密码泄露
    ↓
重置密码
    ↓
token_version + 1
    ↓
吊销所有 Session
    ↓
吊销所有 Refresh Token

Refresh Token 重用
    ↓
认为可能被盗
    ↓
吊销整个 Session

二十二、最终推荐方案

最终认证模型:

                 ┌─────────────────┐
                 │      登录       │
                 └────────┬────────┘
                          │
                ┌─────────┴─────────┐
                ▼                   ▼
          Access JWT          Refresh Token
           15~30 min            7~30 天
                │                   │
                │                   │
                ▼                   ▼
          API Authentication    Token Rotation
                │
                ▼
          Redis Session
                │
                ▼
          Token Version
                │
        ┌───────┴────────┐
        ▼                ▼
      一致              不一致
        │                │
       放行             401

一句话总结:

JWT 负责身份认证,Redis Session 负责登录状态,Token Version 负责全局紧急失效,Refresh Token 负责续期;密码修改、密码泄露、管理员强制下线时统一执行“版本递增 + Session/Refresh Token 吊销”。

这个设计比单纯使用 JWT 更适合需要真正下线能力的 PHP/TP6 后端系统。

posted @ 2026-10-13 14:01  刘俊涛的博客  阅读(29)  评论(1)    收藏  举报