本文档介绍 Web 应用中常见的认证方案,包括 Session-Cookie、JWT 和 SSO 单点登录,帮助开发者理解各方案的原理、差异及适用场景。
1. 认证方案概述
Web 认证的核心问题是:HTTP 是无状态协议,服务端如何识别用户身份?
graph LR
A[用户] -->|请求| B[服务端]
B -->|你是谁| A
subgraph 解决方案
C[Session Cookie]
D[JWT Token]
E[OAuth SSO]
end
认证 vs 授权
| 概念 | 英文 | 解决的问题 | 类比 |
|---|---|---|---|
| 认证 | Authentication | 你是谁? | 身份证 |
| 授权 | Authorization | 你能做什么? | 门禁卡 |
2. Session-Cookie 方案
2.1 工作原理
Session-Cookie 是传统的有状态认证方案,用户登录信息存储在服务端。
sequenceDiagram
participant U as 用户
participant C as 客户端
participant S as 服务端
participant R as Redis/内存
U->>C: 输入账号密码
C->>S: POST /login
S->>S: 验证凭据
S->>R: 创建 Session<br/>存储用户信息
R-->>S: sessionId
S-->>C: Set-Cookie: sessionId=abc123
Note over C: Cookie 自动存储
U->>C: 请求数据
C->>S: GET /api/data<br/>Cookie: sessionId=abc123
S->>R: 查询 Session
R-->>S: 用户信息
S-->>C: 返回数据
2.2 代码示例
ts
// 服务端 - Express + express-session
import session from 'express-session';
import RedisStore from 'connect-redis';
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // 仅 HTTPS
httpOnly: true, // 防止 XSS 读取
maxAge: 24 * 60 * 60 * 1000 // 24 小时
}
}));
// 登录
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await validateUser(username, password);
if (user) {
req.session.userId = user.id;
req.session.role = user.role;
res.json({ success: true });
}
});
// 验证中间件
const authMiddleware = (req, res, next) => {
if (req.session.userId) {
next();
} else {
res.status(401).json({ error: 'Unauthorized' });
}
};
2.3 优缺点
| 优点 | 缺点 |
|---|---|
| 服务端完全控制,可随时踢人 | 需要 Session 存储(Redis/内存) |
| Session ID 较小 | 分布式需要共享 Session |
| 安全性较高(HttpOnly) | 有 CSRF 攻击风险 |
| 实现简单成熟 | 跨域处理复杂 |
| - | 移动端 Cookie 支持有限 |
3. JWT 方案
3.1 什么是 JWT
JWT(JSON Web Token)是一种无状态 的认证方案,用户信息编码在 Token 中,存储在客户端。
txt
JWT 结构:Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. <- Header (Base64)
eyJ1c2VySWQiOjEyMywiZXhwIjoxNjk5OTk5fQ. <- Payload (Base64)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw <- Signature
flowchart LR
subgraph Header
A[alg HS256<br/>typ JWT]
end
subgraph Payload
B[userId 123<br/>exp 1699999]
end
subgraph Signature
C[HMACSHA256]
end
A --> D[Base64]
B --> D
C --> D
D --> E[JWT Token]
3.2 工作原理
sequenceDiagram
participant U as 用户
participant C as 客户端
participant S as 服务端
U->>C: 输入账号密码
C->>S: POST /login
S->>S: 验证凭据
S->>S: 生成 JWT<br/>(包含用户信息 + 签名)
S-->>C: 返回 token
Note over C: 存储到 localStorage
U->>C: 请求数据
C->>C: 从 localStorage 读取 token
C->>S: GET /api/data<br/>Authorization: Bearer xxx
S->>S: 验证签名<br/>解析用户信息<br/>(无需查库)
S-->>C: 返回数据
3.3 Token 传递方式
| 方式 | 安全性 | 适用场景 |
|---|---|---|
| Authorization Header | 高 | 推荐方案,API 请求 |
| HttpOnly Cookie | 高 | 同域 Web 应用 |
| URL Query Parameter | 低 | 仅用于下载链接、邮件验证等特殊场景 |
ts
// 方式一:Authorization Header(推荐)
axios.get('/api/data', {
headers: {
'Authorization': `Bearer ${token}`
}
});
// 方式二:通过拦截器自动添加
axios.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});
3.4 Token 刷新机制
由于 JWT 无法主动失效,通常采用双 Token 机制:
sequenceDiagram
participant C as 客户端
participant S as 服务端
Note over C,S: 登录时获取双 Token
C->>S: POST /login
S-->>C: accessToken (15分钟)<br/>refreshToken (7天)
Note over C,S: Access Token 过期
C->>S: GET /api/data (accessToken 过期)
S-->>C: 401 Unauthorized
Note over C,S: 使用 Refresh Token 刷新
C->>S: POST /refresh (refreshToken)
S->>S: 验证 refreshToken
S-->>C: 新的 accessToken
C->>S: GET /api/data (新 accessToken)
S-->>C: 返回数据
ts
// 双 Token 刷新实现
interface TokenPair {
accessToken: string; // 短期,15分钟
refreshToken: string; // 长期,7天
}
// 响应拦截器 - 自动刷新
axios.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 401) {
const refreshToken = localStorage.getItem('refreshToken');
try {
const { data } = await axios.post('/auth/refresh', { refreshToken });
localStorage.setItem('accessToken', data.accessToken);
// 重试原请求
error.config.headers['Authorization'] = `Bearer ${data.accessToken}`;
return axios(error.config);
} catch {
// Refresh Token 也过期,跳转登录
window.location.href = '/login';
}
}
return Promise.reject(error);
}
);
3.5 优缺点
| 优点 | 缺点 |
|---|---|
| 无状态,天然支持分布式 | 无法主动失效(需黑名单) |
| 自包含用户信息,减少查库 | Token 较大(200+ 字节) |
| 跨域友好 | 续期机制较复杂 |
| 跨平台(Web/App/小程序) | 敏感信息不能放 Payload |
| 微服务架构友好 | XSS 可窃取(存 localStorage) |
4. Session vs JWT 对比
4.1 架构对比
flowchart TB
subgraph Session方案
A1[客户端] -->|Session ID| B1[服务端]
B1 -->|查询| C1[(Session 存储<br/>Redis/内存)]
end
subgraph JWT方案
A2[客户端] -->|JWT Token<br/>含用户信息| B2[服务端]
B2 -->|仅验证签名| B2
end
4.2 详细对比
| 特性 | Session-Cookie | JWT |
|---|---|---|
| 状态存储 | 服务端(Redis/内存) | 客户端(Token 自包含) |
| 扩展性 | 需要共享 Session 存储 | 天然支持分布式 |
| 服务端压力 | 每次请求查询 Session | 仅验证签名,无 IO |
| 注销/踢人 | 删除 Session 即可 | 较难(需黑名单机制) |
| 安全风险 | CSRF 攻击 | XSS 攻击 |
| 跨域支持 | 需配置 Cookie | 天然支持 |
| 移动端 | Cookie 处理麻烦 | 友好 |
| Token 大小 | ~6 字节(Session ID) | ~200+ 字节 |
| 实现复杂度 | 简单 | 中等(需处理刷新) |
4.3 分布式场景对比
graph TB
subgraph Session分布式问题
U1[用户] --> LB1[负载均衡]
LB1 --> S1[服务器 A<br/>存在 Session]
LB1 --> S2[服务器 B<br/>无 Session]
S1 -.-> R1[(共享 Redis)]
S2 -.-> R1
end
graph TB
subgraph JWT无此问题
U2[用户<br/>携带 Token] --> LB2[负载均衡]
LB2 --> S3[服务器 A]
LB2 --> S4[服务器 B]
S3 --> V1[本地验证 Token]
S4 --> V2[本地验证 Token]
V1 --> N1[无需共享存储]
V2 --> N1
end
5. SSO 单点登录
5.1 什么是 SSO
SSO(Single Sign-On)单点登录:一次登录,多处访问。
flowchart LR
subgraph 没有SSO
U1[用户] -->|登录| A1[系统 A]
U1 -->|再登录| B1[系统 B]
U1 -->|再登录| C1[系统 C]
end
graph TB
subgraph SSO单点登录
U2[用户] -->|登录一次| SSO[SSO认证中心]
SSO -->|自动通行| A2[系统 A]
SSO -->|自动通行| B2[系统 B]
SSO -->|自动通行| C2[系统 C]
end
5.2 常见场景
| 场景 | 说明 |
|---|---|
| Google 系 | 登录 Gmail 后,YouTube、Drive 自动登录 |
| 阿里系 | 登录淘宝后,天猫、支付宝自动登录 |
| 企业内网 | 登录 OA 后,邮箱、CRM、ERP 都能访问 |
| 微信生态 | 微信登录后,各小程序共享登录态 |
5.3 SSO 实现方案
方案一:共享 Cookie(同域)
适用于同一主域下的子系统。
flowchart TB
subgraph example.com子域
SSO[sso.example.com<br/>认证中心]
A[a.example.com<br/>系统 A]
B[b.example.com<br/>系统 B]
C[c.example.com<br/>系统 C]
end
Cookie[Cookie 设置为 .example.com<br/>所有子域共享]
SSO --> Cookie
A --> Cookie
B --> Cookie
C --> Cookie
ts
// 设置共享 Cookie
res.cookie('token', jwtToken, {
domain: '.example.com', // 主域名,所有子域共享
httpOnly: true,
secure: true
});
方案二:CAS 协议(跨域)
适用于不同域名的系统。
sequenceDiagram
participant U as 用户
participant A as 系统 A<br/>(app-a.com)
participant SSO as SSO 中心<br/>(sso.com)
Note over U,SSO: 首次访问系统 A
U->>A: 1. 访问系统 A
A->>A: 2. 检测未登录
A-->>U: 3. 重定向到 SSO
U->>SSO: 4. 跳转 SSO 登录页
U->>SSO: 5. 输入账号密码
SSO->>SSO: 6. 验证成功<br/>创建全局 Session
SSO-->>U: 7. 重定向回系统 A<br/>携带 ticket
U->>A: 8. 带 ticket 访问
A->>SSO: 9. 验证 ticket
SSO-->>A: 10. 返回用户信息
A->>A: 11. 创建局部 Session
A-->>U: 12. 登录成功
sequenceDiagram
participant U as 用户
participant B as 系统 B<br/>(app-b.com)
participant SSO as SSO 中心<br/>(sso.com)
Note over U,SSO: 访问系统 B(已在 SSO 登录)
U->>B: 1. 访问系统 B
B->>B: 2. 检测未登录
B-->>U: 3. 重定向到 SSO
U->>SSO: 4. 跳转 SSO
SSO->>SSO: 5. 检测已有全局 Session
SSO-->>U: 6. 直接返回 ticket<br/>(无需再登录)
U->>B: 7. 带 ticket 访问
B->>SSO: 8. 验证 ticket
SSO-->>B: 9. 返回用户信息
B-->>U: 10. 自动登录成功
方案三:OAuth 2.0 / OIDC
现代标准,适用于第三方登录和开放平台。
sequenceDiagram
participant U as 用户
participant App as 第三方应用
participant Auth as 授权服务器<br/>(如 GitHub)
participant API as 资源服务器
U->>App: 1. 点击"GitHub 登录"
App-->>U: 2. 重定向到 GitHub
U->>Auth: 3. 跳转 GitHub 授权页
U->>Auth: 4. 用户授权
Auth-->>U: 5. 重定向回 App<br/>携带 code
U->>App: 6. 带 code 访问
App->>Auth: 7. 用 code 换 token
Auth-->>App: 8. 返回 access_token
App->>API: 9. 用 token 获取用户信息
API-->>App: 10. 返回用户数据
App-->>U: 11. 登录成功
5.4 SSO 协议对比
| 协议 | 特点 | 适用场景 |
|---|---|---|
| 共享 Cookie | 简单直接 | 同域系统 |
| CAS | 经典企业方案 | 企业内部系统 |
| OAuth 2.0 | 授权协议 | 第三方登录、开放 API |
| OIDC | OAuth 2.0 + 身份层 | 现代标准方案 |
| SAML | XML 格式,企业级 | 传统企业、政府 |
5.5 SSO 与 JWT/Session 的关系
flowchart TB
subgraph SSO架构方案
SSO[SSO 单点登录]
end
subgraph 底层实现
JWT[JWT Token]
Session[Session Cookie]
end
SSO --> JWT
SSO --> Session
SSO --> Note[解决多系统共享登录]
JWT --> Note2[解决单系统用户识别]
Session --> Note2
6. 方案选型指南
6.1 决策流程图
flowchart TB
Start[开始选型] --> Q1{是否多系统?}
Q1 -->|是| Q2{是否同域?}
Q1 -->|否| Q3{是否分布式?}
Q2 -->|是| A1[共享 Cookie SSO]
Q2 -->|否| A2[CAS/OAuth SSO]
Q3 -->|是| Q4{需要即时踢人?}
Q3 -->|否| A3[Session-Cookie]
Q4 -->|是| A4[JWT + 黑名单]
Q4 -->|否| A5[纯 JWT]
6.2 场景推荐
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单体应用 | Session-Cookie | 简单可靠,支持即时踢人 |
| 微服务架构 | JWT | 无状态,服务独立验证 |
| 移动端 App | JWT | 无 Cookie 限制 |
| 企业内部多系统 | SSO (CAS) | 统一认证管理 |
| 接入第三方登录 | OAuth 2.0 / OIDC | 行业标准 |
| 开放 API 平台 | OAuth 2.0 + JWT | 授权灵活,Token 自包含 |
| 高安全要求 | Session + 短期 Token | 可控性强 |
6.3 安全建议
flowchart LR
subgraph 防御措施
A[HTTPS 传输]
B[HttpOnly Cookie]
C[SameSite Cookie]
D[CSRF Token]
E[XSS 防护]
F[Token 过期机制]
end
A --> Safe[安全认证]
B --> Safe
C --> Safe
D --> Safe
E --> Safe
F --> Safe
| 攻击类型 | Session 方案 | JWT 方案 |
|---|---|---|
| XSS | HttpOnly 防护 | 避免存 localStorage,或用 HttpOnly Cookie |
| CSRF | 需要 CSRF Token | 使用 Header 传 Token 天然防护 |
| 重放攻击 | Session 过期机制 | Token 过期 + Refresh Token |
| 中间人 | HTTPS | HTTPS |