Appearance
认证与授权
本章导读:无状态约束把"证明你是谁"压到了每个请求上,HTTP 为此提供了一整套标准化机制(RFC 7235 认证框架 + 注册方案)。本章讲清
Authorization/401/WWW-Authenticate的协议契约,对比 Basic / Bearer(JWT) / OAuth 2.0 / API Key 四种方案的适用边界,并给出会话与令牌生命周期设计。
1. 协议层契约(RFC 7235 / RFC 9110 §11)
挑战-应答模型:
客户端 → GET /articles/42 (无凭证)
服务器 → 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token",
error_description="令牌已过期"
客户端 → GET /articles/42
Authorization: Bearer eyJhbGciOi…
服务器 → 200 OK规范要点:
Authorization: <scheme> <credentials>——scheme 大小写不敏感,但惯用 Bearer/Basic;401响应必须携带WWW-Authenticate(RFC 7235 MUST);403与401分工:认证失败 401,认证通过但无权 403;- 认证头部没有统一标准时不要自创裸头部(
X-Auth: …违反自描述原则),用注册 scheme 或文档化的Authorization扩展。
2. 四种主流方案
2.1 API Key(最简单)
GET /v1/articles?key=sk-live-3f9a… ← 查询参数(不推荐:进日志/Referer)
GET /v1/articles
X-Api-Key: sk-live-3f9a… ← 惯用头部(虽然 X- 已不推荐,但生态既成事实)
Authorization: ApiKey sk-live-3f9a… ← IANA 较新登记的 ApiKey 认证方案(客户端支持度先验证)适合:服务间调用、低频开放接口。身份粒度=一个 key 一个主体,无用户级权限模型。key 必须:仅 HTTPS、可吊销、可轮换、按环境隔离、限 scopes。
2.2 Basic(RFC 7617)
Authorization: Basic base64(user:pass)——不是加密是编码,仅限 HTTPS + 引导脚本/内网工具;现代用户端 API 基本淘汰,OAuth 的 client credentials 流程内部仍会用到。
2.3 Bearer Token(RFC 6750 + JWT RFC 7519)
http
POST /v1/auth/login
{ "username": "ma_gua", "password": "……" }
200 OK
{
"accessToken": "eyJhbGciOiJSUzI1NiIs…", ← 短时(15min)
"tokenType": "Bearer",
"refreshToken": "dGhpcyBpcyBhIHJlZnJl…", ← 长时(30d),仅用于换新
"expiresIn": 900
}
GET /v1/articles/42
Authorization: Bearer eyJhbGciOiJSUzI1NiIs…JWT 三段式(header.payload.signature)携带声明(claims):
| 声明 | 含义 | 校验点 |
|---|---|---|
iss | 签发者 | 白名单 |
sub | 主体(用户 ID) | — |
aud | 受众(哪个 API 能用) | 必须校验,防跨服务挪用 |
exp/iat/nbf | 时间窗 | 时钟偏移容差 ≤60s |
scope/roles | 权限 | 授权依据 |
jti | 令牌唯一 ID | 吊销黑名单的锚点 |
取舍:JWT 自包含 vs 不透明令牌(Opaque Token + introspection)
| JWT | 不透明令牌(如 at_8f3d…) | |
|---|---|---|
| 校验成本 | 本地验签,零回源 | 每请求问认证服务(或缓存) |
| 即时吊销 | 难(需黑名单) | 易(存活性即吊销) |
| 体积 | 大(数百字节,每请求都带) | 小 |
| 泄露面 | payload 可见敏感声明 | 无内容可偷看 |
经验:高 QPS、跨服务、可接受"短 TTL + 黑名单兜底"→ JWT;强合规、需秒级踢人 → 不透明令牌。
2.4 OAuth 2.0 / 2.1(RFC 6749 家族)
四个角色的委托授权框架——"第三方 App 访问我的文章,但不给我的密码":
授权码流程(Authorization Code + PKCE,2026 年唯一推荐的用户态流程):
1 App → 授权服务器: /authorize?response_type=code&code_challenge=…(浏览器登录)
2 用户同意 → 回调 App: ?code=…
3 App → 授权服务器: POST /token code + code_verifier → access_token + refresh_token
4 App → 资源服务器(API): Authorization: Bearer <access_token>要点:
- 机密客户端(后端服务):授权码 + client_secret,或 client_credentials(服务间);
- 公共客户端(SPA/移动):必须 PKCE(RFC 7636);
- OAuth 管"授权"(scope),不管"认证"——拿 OIDC(OpenID Connect)做登录身份;
- 细粒度持续授权演进方向:FGA(关系型权限,如 OpenFGA/Zanzibar 模型)、UMA(基于 OAuth 的用户管理授权)——REST 侧以
403 + problem type表达即可。
3. 会话资源的 REST 建模
"登录态"其实可以名词化:
http
POST /v1/sessions ← 登录:创建会话资源 → 201 + Location: /v1/sessions/cur
GET /v1/sessions/cur ← 当前会话(/me 的同款思路)
DELETE /v1/sessions/cur ← 登出:销毁会话 → 204
DELETE /v1/sessions/{id} ← 管理员踢线
GET /v1/users/me/sessions ← 我的设备列表(每行带 revoke 链接)收益:登出/踢线/设备管理全部资源化,审计与权限模型一致;refresh token 的撤销即 DELETE /v1/sessions/{id}。
4. 授权:403 的正确姿势
http
PATCH /v1/articles/42
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/permission-denied",
"title": "无权编辑该文章",
"status": 403,
"detail": "需要 role=author 或 editor",
"requiredScopes": ["articles:write"]
}- 模型选择:RBAC(角色)→ ABAC(属性策略)→ ReBAC(关系,Zanzibar 系);API 层表现一致:缺什么声明清楚(防客户端瞎猜),必要时给"申请权限"的链接(HATEOAS 式
rel="request-access"); - 未认证访问私有资源返回 401;已认证无权限返回 403;防枚举可统一 404——三选一策略写进规范并全局一致;
- 权限校验放统一拦截层(网关/中间件声明式规则)+ 资源级细校验(如"只能改自己的草稿")。
5. 安全基线清单
- [ ] 全站 HTTPS(HSTS:
Strict-Transport-Security: max-age=31536000; includeSubDomains) - [ ] 令牌:access 短 TTL(≤15min)、refresh 旋转(rotation)+ 重用检测
- [ ] JWT 验签:算法固定白名单(拒绝
alg: none与算法混淆攻击)、校验iss/aud/exp - [ ] 密钥托管 KMS/Secrets Manager + 定期轮换(JWKS 端点支持 kid 双活轮换)
- [ ] 登录接口防暴力:限流 + 验证码 + 失败锁定;凭据永不回显
- [ ] 敏感操作二次认证(step-up:401 +
WWW-Authenticate: Bearer step_up="…") 或 MFA 资源建模 - [ ] 令牌不写 URL(查询参数会进日志/Referer/历史记录);Cookie 场景
Secure/HttpOnly/SameSite=Lax|Strict
6. 本章小结
- 协议层:
Authorization+401/WWW-Authenticate是框架,方案(Basic/Bearer/ApiKey)可插拔。 - Bearer+JWT 是 API 认证默认解;OAuth 2.0 解决第三方委托;两者是"框架与流程"关系不是竞争关系。
- 把"会话/令牌"建模为资源(
POST /sessions、DELETE /sessions/{id}),登出、踢线、设备管理一色 REST。 - 授权失败的 problem 响应要携带"缺什么",让客户端可自助修复。
7. 下一步
批量怎么写?部分成功怎么办? → 批量操作设计