API 安全
OAuth2
四种授权模式
| 模式 | 适用场景 | 安全性 |
|---|---|---|
| 授权码(Authorization Code) | Web 服务端应用 | 最高 |
| 隐式(Implicit) | 纯前端 SPA(已废弃,推荐 PKCE) | 低 |
| 密码(Password) | 第一方应用(不推荐) | 中 |
| 客户端凭证(Client Credentials) | 服务间调用 | 高 |
授权码流程(推荐)
用户 → 客户端
│
├─ 1. 跳转授权服务器
│ ?response_type=code
│ &client_id=CLIENT_ID
│ &redirect_uri=https://client.com/callback
│ &scope=openid+profile
│ &state=随机值
│
├─ 2. 用户登录 + 授权
│
├─ 3. 回调 redirect_uri
│ ?code=AUTHORIZATION_CODE&state=xxx
│
├─ 4. 服务端用 code 交换 token
│ POST /oauth/token
│ grant_type=authorization_code
│ code=AUTHORIZATION_CODE
│ client_id=CLIENT_ID
│ client_secret=CLIENT_SECRET
│
└─ 5. 返回 { access_token, refresh_token, id_token }PKCE(Proof Key for Code Exchange)
SPA 或移动端使用授权码模式时,必须配合 PKCE:
javascript
// 客户端生成 code_verifier + code_challenge
const codeVerifier = generateRandomString(64);
const codeChallenge = await sha256(codeVerifier);
// 授权请求带上 code_challenge
window.location.href =
`https://auth.example.com/authorize?response_type=code` +
`&client_id=APP_ID` +
`&code_challenge=${codeChallenge}` +
`&code_challenge_method=S256`;
// Token 交换时带上 code_verifier
POST /oauth/token
grant_type=authorization_code
code=AUTHORIZATION_CODE
code_verifier=${codeVerifier}JWT 最佳实践
结构
Header: { "alg": "RS256", "typ": "JWT" }
Payload: { "sub": "user123", "role": "admin", "iat": 1700000000, "exp": 1700003600 }
Signature: RSASHA256(base64(Header) + "." + base64(Payload), privateKey)签名算法选型
| 算法 | 类型 | 推荐度 |
|---|---|---|
| RS256 | 非对称(RSA) | 推荐(服务端签名,客户端验签) |
| ES256 | 非对称(ECDSA) | 推荐(性能优于 RSA) |
| HS256 | 对称(HMAC) | 不推荐(需共享密钥) |
| none | 无签名 | 禁止 |
安全实践
java
public class JwtUtil {
private static final long ACCESS_EXPIRE = 3600_000; // 1h
private static final long REFRESH_EXPIRE = 604_800_000; // 7d
public static String generateAccessToken(String userId, String role) {
return Jwts.builder()
.subject(userId)
.claim("role", role)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + ACCESS_EXPIRE))
.signWith(getPrivateKey())
.compact();
}
public static String generateRefreshToken(String userId) {
return Jwts.builder()
.subject(userId)
.id(UUID.randomUUID().toString()) // jti,用于吊销
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + REFRESH_EXPIRE))
.signWith(getPrivateKey())
.compact();
}
}Token 刷新
json
// 请求
POST /api/auth/refresh
{ "refreshToken": "eyJhbGci..." }
// 响应
{
"accessToken": "eyJhbGci...",
"expiresIn": 3600
}安全清单
| 要求 | 说明 |
|---|---|
| 使用 HTTPS | 所有 API 必须在 TLS 之上 |
| Token 短期有效 | Access Token 1h,Refresh Token 7d |
| Token 吊销 | 维护黑名单 / 版本号 |
| 速率限制 | 按用户/IP 限流 |
| 最小权限 | 仅授予必需的作用域 |
| 输入校验 | 所有参数严格校验类型和范围 |
| 错误信息最小化 | 避免泄漏「用户不存在」等敏感信息 |