Session、JWT、OAuth 2.0 与 OIDC
🔑 认证与授权是应用安全的核心。常见协议各有适用场景,混淦不同概念是最常见的错误。
Session vs JWT
| 维度 | Session | JWT |
|---|---|---|
| 存储 | 服务端(内存/Redis) | 客户端 |
| 缩放性 | 需要共享存储 | 无状态,易横向扩展 |
| 撤销 | 简单(删山下) | 复杂(黑名单/短 TTL) |
| 内容 | 无业务信息 | 可包含 Claims |
JWT 结构
Header.Payload.Signature
{
"alg": "RS256",
"typ": "JWT"
}
.
{
"sub": "user-123",
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"exp": 1700000000,
"iat": 1699996400
}
.
<签名>
JWT 安全要点
// ✅ 验证必须检查的字段
const payload = jwt.verify(token, publicKey, {
issuer: 'https://auth.example.com', // 验证发行方
audience: 'https://api.example.com', // 验证受众
algorithms: ['RS256'], // 禁止算法替换攻击
})
// ❌ 永不过期的 JWT 是危险信号
const token = jwt.sign(payload, secret, { expiresIn: '1h' })
OAuth 2.0 授权流程
授权码模式(首选)
用户应用 授权服务器
|
1. 请求授权(重定向到授权服务器)
↓ ?response_type=code&client_id=xxx&redirect_uri=xxx&state=xxx
2. 用户登录并同意授权
↓
3. 授权服务器返回 code
↓ redirect_uri?code=xxx&state=xxx
4. 用缺 code 换 token(服务端请求,不暴露秘钥)
POST /token
{ client_id, client_secret, code, redirect_uri }
5. 得到 access_token + refresh_token
state 参数防 CSRF
// 请求前生成并存储 state
const state = crypto.randomUUID()
session.set('oauth_state', state)
// 回调时验证 state
if (req.query.state !== session.get('oauth_state')) {
throw new Error('CSRF attack detected')
}
OIDC(OpenID Connect)
OIDC = OAuth 2.0 + 身份层
OAuth 2.0 解决“授权”:这个应用可以代替我操作资源
OIDC 解决“认证”:这个用户是谁
在 access_token 基础上增加 id_token(JWT)
id_token 包含用户信息:sub、email、name 等
常用属性端点
# OIDC 发现文档
curl https://accounts.google.com/.well-known/openid-configuration
# 返回:授权端点、token 端点、JWKS URI 等
Refresh Token 策略
// access_token 短期(1h)+ refresh_token 长期(30d)
if (isAccessTokenExpired()) {
const newTokens = await refreshAccessToken(refreshToken)
storeTokens(newTokens)
}
// Refresh Token Rotation:每次刷新都更换 refresh_token
// 旧的 refresh_token 立即失效,櫚止窃取后重放攻击
常见误区
- 把 OAuth 当作认证协议,实际上它是授权协议(需用 OIDC 做认证)
- JWT 不验证
aud字段,任意半信任的各方 JWT 都能被其他服务接受 - Access Token 过长(10h+)且无法撤销,密钥泄露后无法应急