JWT 认证与紧急失效设计方案
JWT 认证与紧急失效设计方案
一、设计目标
本方案用于解决基于 JWT 的登录认证体系中的以下问题:
- 用户正常登录与 API 鉴权
- Access Token 过期与自动续期
- 用户主动退出登录
- 单设备下线
- 全设备下线
- 密码修改后旧 Token 立即失效
- 密码泄露后的紧急失效
- 管理员强制踢用户下线
- Refresh Token 被盗后的风险控制
- 多设备同时登录
核心设计:
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 后端系统。
你要保守你心,胜过保守一切。
本文来自博客园,作者:刘俊涛的博客,转载请注明原文链接:https://www.cnblogs.com/lovebing/p/23285222

浙公网安备 33010602011771号