Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

身份与安全 / Identity & Security

1. 认证鉴权——JWT 过期了,用户怎么办 / Authentication, Authorization, and JWT Expiration

2. 安全加固——你的 API 被黑客盯上了 / API Security Hardening Against Real Attacks

3. JWT 完全指南 / A Complete Guide to JSON Web Tokens

4. Session + Cookie 完全指南 / A Complete Guide to Sessions and Cookies

本页目录

认证鉴权——JWT 过期了,用户怎么办 / Authentication, Authorization, and JWT Expiration ​

📅 创建时间:2026-05-08 🏷️ 标签:#JWT #Session #OAuth2 #Token #幂等性 #接口安全 📚 前置知识:[[00-backend-overview]] [[06-network]](HTTP 基础) 📚 相关知识:[[/03-web/06-databases-and-data-access/05-redis]](Token 存储) [[10-cache-strategy]](缓存策略)


场景:用户正在支付,JWT 过期了 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  用户在支付页面点了"确认支付",请求发出。                │
│                                                             │
│  服务端:401 Unauthorized                                   │
│  前端:用户的钱已经扣了,但页面显示"登录过期"!         │
│                                                             │
│  用户投诉:钱扣了,但订单没创建。                        │
│                                                             │
│  你被叫去排查。                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12

这一章,我们理解认证鉴权的原理和常见陷阱。


第1节:Session vs Token——两种身份验证方式 ​

Session 模式 ​

┌─────────────────────────────────────────────────────────────┐
│                 Session 认证流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户登录:                                              │
│  浏览器 ──▶ POST /login(用户名+密码)───▶ 服务端      │
│  服务端验证通过 ──▶ 创建 Session ──▶ 存 Redis ──▶ 返回 SessionID │
│                                                             │
│  后续请求:                                              │
│  浏览器 ──▶ Cookie: session_id=xxx ──▶ 服务端            │
│  服务端 ──▶ 查 Redis ──▶ 获取用户信息 ──▶ 处理请求      │
│                                                             │
│  优点:                                                   │
│  ✅ 可随时撤回(删 Redis 中的 Session 即可)              │
│  ✅ Session 内容存在服务端,不怕泄露                     │
│  ✅ 适合复杂业务(Session 可存大量数据)                 │
│                                                             │
│  缺点:                                                   │
│  ❌ 依赖 Redis(Redis 挂了 = 全部用户登出)              │
│  ❌ 不适合分布式(Session 共享需要额外配置)              │
│  ❌ 移动端不友好(Cookie 不方便)                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

JWT 模式 ​

┌─────────────────────────────────────────────────────────────┐
│                 JWT 认证流程                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户登录:                                              │
│  浏览器 ──▶ POST /login(用户名+密码)───▶ 服务端      │
│  服务端验证通过 ──▶ 生成 JWT ──▶ 返回给浏览器            │
│                                                             │
│  JWT 结构:                                              │
│  Header.Payload.Signature                                │
│  eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.      │
│  [Base64]  [Base64]  [HMAC-SHA256]                        │
│                                                             │
│  后续请求:                                              │
│  浏览器 ──▶ Header: Authorization: Bearer <jwt> ──▶ 服务端│
│  服务端验证 JWT 签名 ──▶ 解析 Payload ──▶ 获取用户信息  │
│                                                             │
│  优点:                                                   │
│  ✅ 无状态(服务端不存任何东西)                          │
│  ✅ 不依赖 Redis                                          │
│  ✅ 天然适合分布式                                        │
│  ✅ 移动端友好                                           │
│                                                             │
│  缺点:                                                   │
│  ❌ 无法撤回(发出去后,只能等它过期)                   │
│  ❌ Payload 是 Base64 编码,可被解码(不能存敏感信息)  │
│  ❌ Token 越长,请求体积越大                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

JWT 内部结构 ​

python
import jwt
import datetime

# JWT 组成
payload = {
    "sub": "user_1001",      # 用户 ID
    "name": "张三",           # 用户名
    "role": "admin",         # 角色
    "iat": 1715145600,       # 签发时间
    "exp": 1715152800,       # 过期时间(2小时后)
}

# 签名
token = jwt.encode(payload, "secret_key", algorithm="HS256")
# Header + Payload + HMAC-SHA256(Header.Payload, secret)

# 解码(验证签名 + 检查过期)
decoded = jwt.decode(token, "secret_key", algorithms=["HS256"])
print(decoded["sub"])  # user_1001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

第2节:JWT 的三个致命问题 ​

问题一:Token 过期了怎么办 ​

python
# 错误做法:让用户重新登录
# 用户体验极差:支付到一半被踢出去

# 正确做法:Refresh Token
# Access Token:短期有效(15 分钟)
# Refresh Token:长期有效(7 天)

def login(username, password):
    user = verify(username, password)

    # Access Token:用于 API 调用,过期快
    access_token = jwt.encode({
        "sub": user.id,
        "exp": datetime.datetime.now() + datetime.timedelta(minutes=15)
    }, SECRET, algorithm="HS256")

    # Refresh Token:用于获取新的 Access Token,过期慢
    refresh_token = jwt.encode({
        "sub": user.id,
        "type": "refresh",
        "exp": datetime.datetime.now() + datetime.timedelta(days=7)
    }, REFRESH_SECRET, algorithm="HS256")

    return access_token, refresh_token

def refresh_access_token(refresh_token):
    # 验证 Refresh Token
    payload = jwt.decode(refresh_token, REFRESH_SECRET, algorithms=["HS256"])

    # 检查类型
    if payload.get("type") != "refresh":
        raise AuthError("Invalid token type")

    # 生成新的 Access Token
    new_access_token = jwt.encode({
        "sub": payload["sub"],
        "exp": datetime.datetime.now() + datetime.timedelta(minutes=15)
    }, SECRET, algorithm="HS256")

    return new_access_token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

问题二:Refresh Token 泄露了怎么办 ​

用户 A 登录 → 获取了 access_token + refresh_token
黑客 B 拿到了 refresh_token

风险:B 可以用 refresh_token 获取新的 access_token
1
2
3
4

解决方案:Refresh Token 轮换 + 追踪

python
# 方案:Refresh Token 只能使用一次
# 用户用 refresh_token 换取新的 access_token 时,
# 同时颁发新的 refresh_token,旧的一律作废

refresh_tokens = {}  # {token: user_id} 存在 Redis

def use_refresh_token(old_refresh_token):
    user_id = refresh_tokens.get(old_refresh_token)
    if not user_id:
        raise AuthError("Token 已失效,可能被盗用")

    # 删除旧 token(只能用一次)
    del refresh_tokens[old_refresh_token]

    # 颁发新的 token 对
    return generate_tokens(user_id)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

问题三:如何强制用户登出(JWT 无法撤回) ​

python
# 方案 1:黑名单(Redis)
blacklist = set()  # 存在 Redis

def logout(token):
    # 把 token 的 exp 加入黑名单
    payload = jwt.decode(token, options={"verify_exp": False})
    blacklist.add(payload["exp"])
    # 每次验证 JWT 时,检查 exp 是否在黑名单中

# 方案 2:Token 版本号
user_token_version = {}  # 存在 Redis: {user_id: version}

def verify_token(token):
    payload = jwt.decode(token, SECRET, algorithms=["HS256"])
    user_id = payload["sub"]

    # 检查版本号
    if payload.get("version") != user_token_version.get(user_id, 0):
        raise AuthError("Token 已失效")

    return payload

def force_logout(user_id):
    # 用户被封禁 → 版本号 +1 → 所有旧 token 失效
    user_token_version[user_id] += 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

第3节:OAuth2——第三方授权 ​

场景:你的网站想让用户用微信登录 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  你(Client)想访问用户的微信好友列表(Protected Resource)│
│                                                             │
│  问题:用户不想把微信密码给你                            │
│  解决:OAuth2,让微信(Authorization Server)授权          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8

OAuth2 四种授权模式 ​

┌─────────────────────────────────────────────────────────────┐
│              OAuth2 四种授权模式                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 授权码模式(Authorization Code)- 最安全,推荐!     │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  用户点击"微信登录"                               │  │
│  │  重定向到微信授权页面                             │  │
│  │  用户授权                                          │  │
│  │  微信重定向回你的网站 + 授权码(code)            │  │
│  │  你的后端用 code + client_secret 换 Access Token │  │
│  │  用 Access Token 访问用户数据                      │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  2. 隐式授权(Implicit)- 不安全,已不推荐              │
│  直接在 URL 返回 Access Token(可能被浏览器历史记录)    │
│                                                             │
│  3. 密码凭证(Resource Owner Password Credentials)       │
│  用户直接给你用户名+密码,换 Access Token               │
│  适用:用户高度信任你的应用                             │
│                                                             │
│  4. 客户端凭证(Client Credentials)                     │
│  没有用户参与,服务端用 client_id + client_secret        │
│  适用:服务端之间的 API 调用                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

第4节:接口幂等性——用户手抖点了两次下单 ​

场景:用户网不好,连点了两下"确认支付" ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  用户点了两次"确认支付"。                                │
│                                                             │
│  请求 1: POST /order/create {user:1, amount:100}         │
│  请求 2: POST /order/create {user:1, amount:100}         │
│                                                             │
│  结果:创建了两个订单,用户被扣了两次钱。                │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10

幂等性方案 ​

┌─────────────────────────────────────────────────────────────┐
│              接口幂等性方案对比                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  方案 1:唯一订单号(幂等键)                            │
│  客户端生成唯一 ID(UUID / 雪花 ID)                   │
│  服务端存入 Redis,带过期时间                            │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  请求 1: POST /order  idempotency_key=abc123      │  │
│  │  请求 2: POST /order  idempotency_key=abc123      │  │
│  │                                                     │  │
│  │  服务端:                                           │  │
│  │  if redis.setnx("idempotent:abc123", 1, ex=300): │  │
│  │      create_order()                                │  │
│  │  else:                                             │  │
│  │      return existing_order()  # 返回已创建的订单   │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  方案 2:数据库唯一索引                                  │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  CREATE TABLE orders (                              │  │
│  │      id BIGINT PRIMARY KEY,                        │  │
│  │      idempotency_key VARCHAR(64) UNIQUE,           │  │
│  │      ...                                            │  │
│  │  );                                                │  │
│  │                                                     │  │
│  │  INSERT ... ON CONFLICT (idempotency_key) DO NOTHING│  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  方案 3:状态机幂等                                     │
│  订单状态:pending → paid → shipped → completed          │
│  只有 pending 才能 → paid                                 │
│  重复请求:已 paid 的订单再请求 paid → 返回原订单        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

第5节:接口安全基础 ​

┌─────────────────────────────────────────────────────────────┐
│              接口安全 Checklist                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 参数校验                                              │
│  ✅ 永远不要相信前端数据                                  │
│  ✅ 校验类型、长度、范围、正则                            │
│  ✅ SQL 注入:用参数化查询,不要拼接 SQL                  │
│  ✅ XSS:转义用户输入                                    │
│  ✅ CSRF:使用 CSRF Token 或 SameSite Cookie             │
│                                                             │
│  2. 认证鉴权                                              │
│  ✅ JWT 验证:检查签名、过期时间、版本号                  │
│  ✅ 权限检查:Admin 接口只能 Admin 用户访问               │
│  ✅ 频率限制:防止暴力破解(配合 [[05-middleware]])      │
│                                                             │
│  3. 数据保护                                              │
│  ✅ 敏感数据加密存储(密码加盐 hash)                    │
│  ✅ 日志脱敏(手机号 138****5678)                       │
│  ✅ 响应过滤(不要返回不该返回的字段)                    │
│                                                             │
│  4. HTTPS                                                │
│  ✅ 强制 HTTPS(HSTS)                                    │
│  ✅ TLS 1.3(比 1.2 快 30%)                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

升华:JWT vs Session 的选择 ​

┌─────────────────────────────────────────────────────────────┐
│              JWT vs Session 选择指南                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  选 JWT:                                                 │
│  ✅ 纯 API 服务,无状态                                    │
│  ✅ 跨域认证(单点登录)                                  │
│  ✅ 移动端 / SPA / 微前端                                 │
│  ✅ 不想依赖 Redis                                         │
│                                                             │
│  选 Session:                                             │
│  ✅ 需要快速撤回权限(封禁用户)                          │
│  ✅ 需要存大量用户数据                                    │
│  ✅ 团队对安全要求极高(Token 泄露风险)                   │
│                                                             │
│  实际中:                                                │
│  很多公司用 JWT Access Token + Redis 存储 Refresh Token    │
│  → 兼顾无状态(快速验证)+ 可撤回(Refresh Token 管理)  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ JWT 库的具体 API(PyJWT / node-jsonwebtoken)
✅ OAuth2 各授权模式的参数配置
✅ CSRF Token 的具体实现代码

必须理解:
🔴 JWT 的三个组成部分(Header/Payload/Signature)
🔴 HS256 vs RS256 的区别(对称 vs 非对称)
🔴 JWT 无法主动撤回的问题及解决方案
🔴 Refresh Token 轮换的原理
🔴 接口幂等性的三种实现方案
1
2
3
4
5
6
7
8
9
10
11

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇← Web 开发 / Web Development
下一篇2. 安全加固——你的 API 被黑客盯上了 / API Security Hardening Against Real Attacks

持续记录,持续成长

Copyright © Tidenflow