3. 认证协议选型与生产落地
认证协议选型不是“哪个更先进”的问题,而是“谁在什么位置、以什么信任关系、承担什么职责”的问题。Session、JWT、OAuth2、OIDC、SAML 五者常被并列讨论,但它们并不在同一层:Session 与 JWT 主要解决“服务端如何记住已登录用户”,OAuth2 解决“第三方如何获得受限授权”,OIDC 在 OAuth2 之上补齐“用户身份认证”,SAML 则是面向企业联邦的单点登录断言协议。选型前必须先分清“认证”与“授权”的边界,否则极易把 JWT 当成会话、把 OAuth2 当成登录。
一、五类协议的定位与安全边界
Session(服务端会话):用户登录成功后,服务端生成 Session ID 并写入 Cookie,真实状态保存在服务端存储(内存、Redis、数据库)。优点是吊销即时、状态可控;缺点是横向扩展需要共享存储,跨域与移动端支持较弱。安全边界在于 Session ID 必须 HttpOnly、Secure、SameSite,并防会话固定攻击。
JWT(JSON Web Token):把声明(sub、exp、iat、scope 等)签名后交给客户端保存,服务端无状态校验。优点是跨服务、跨域、移动端友好;缺点是签发后难以即时吊销,必须依赖短过期时间加刷新令牌。安全边界在于:算法必须固定为 RS256/ES256 等非对称算法,禁止接受 alg:none;必须校验 iss、aud、exp;不得把敏感信息放入 payload(仅 Base64 编码,非加密)。
OAuth2:授权框架,不是认证协议。核心角色为资源所有者、客户端、授权服务器、资源服务器。授权码模式(Authorization Code + PKCE)是生产首选。安全边界:公共客户端必须用 PKCE,禁止隐式模式;redirect_uri 必须白名单精确匹配;state 防 CSRF;令牌只能通过后端换取,不得暴露在前端 URL。
OIDC:在 OAuth2 之上增加 id_token(JWT 格式)与 /userinfo 端点,明确回答“用户是谁”。安全边界:必须校验 nonce、iss、aud、签名与 at_hash;id_token 用于身份,access_token 用于访问资源,二者不可混用。
SAML 2.0:基于 XML 的企业联邦 SSO,IdP 签发 Assertion,SP 消费断言。适合已有 AD/ADFS、需要跨组织单点登录的场景。安全边界:必须校验签名、NotBefore/NotOnOrAfter、Audience、InResponseTo,并防 XML 签名包装(XSW)攻击。
二、协议交互流程拆解
以生产最常用的 OIDC 授权码 + PKCE 为例,完整交互如下:
- 客户端生成
code_verifier(43~128 位随机串),计算code_challenge = BASE64URL(SHA256(code_verifier)),同时生成state与nonce。 - 浏览器重定向到授权端点:
/authorize?response_type=code&client_id=web-app&redirect_uri=https://app.example.com/cb&scope=openid%20profile&state=xyz&nonce=abc&code_challenge=...&code_challenge_method=S256。 - 用户在 IdP 完成登录与授权,IdP 回调
redirect_uri并携带code与state。 - 客户端后端用
code + code_verifier调用/token,换取access_token、id_token、refresh_token。 - 客户端校验
id_token签名、iss、aud、exp、nonce,得到用户身份;access_token仅用于调用资源接口。
Session 流程则简单得多:登录成功后服务端写 Set-Cookie: SID=...; HttpOnly; Secure; SameSite=Lax,后续请求携带 Cookie,服务端查存储校验。JWT 流程是登录后签发令牌,客户端在 Authorization: Bearer <token> 中携带,服务端本地验签。SAML 流程为 SP 发起 AuthnRequest → IdP 认证 → 返回 SAML Response(含签名 Assertion)→ SP 校验后建立本地会话。
三、选型决策清单
| 场景 | 推荐协议 | 关键理由 |
|---|---|---|
| 单体 Web,同域,需即时踢人 | Session | 吊销简单,实现成本低 |
| 前后端分离 / 移动端 / 多服务 | JWT + Refresh Token | 无状态、跨域友好 |
| 第三方登录、开放平台 | OAuth2 + OIDC | 授权与身份分离,标准成熟 |
| 企业内部 AD/ADFS 单点登录 | SAML 2.0 | 与既有 IdP 兼容 |
| 新建企业 SSO,无历史包袱 | OIDC | JSON 轻量,移动端友好 |
| 高安全后台,需强吊销 | Session 或 短JWT+中心化黑名单 | 兼顾安全与扩展 |
四、真实案例:JWT 无法吊销引发的越权
某 SaaS 平台使用 JWT 作为唯一会话凭证,有效期设为 24 小时,未实现刷新令牌与黑名单。运维人员离职后,其账号在后台被禁用,但该员工此前签发的 JWT 仍在有效期内,可继续调用管理接口,导致数据被导出。根因是“禁用账号”只改了数据库状态,而 JWT 校验完全无状态,不查库。
治理方案:将 JWT 有效期从 24h 缩短至 15min,引入 Refresh Token(7 天,存服务端可吊销);在网关增加 jti 黑名单校验,登出或禁用时写入 Redis,TTL 与令牌剩余有效期一致。改造后,禁用账号最迟 15 分钟内失效,登出即时生效。
access_token 判断用户身份。正确做法是使用 OIDC 的 id_token 或调用 /userinfo 获取身份,access_token 只代表授权范围,不保证“是谁”。