跳到主要内容

Session、JWT、OAuth 2.0 与 OIDC

🔑 认证与授权是应用安全的核心。常见协议各有适用场景,混淦不同概念是最常见的错误。

Session vs JWT

维度SessionJWT
存储服务端(内存/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+)且无法撤销,密钥泄露后无法应急