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)攻击。

认证协议分层定位与信任边界 会话层:服务端如何记住已登录用户 授权层:第三方如何获得受限访问权限 身份联邦层:跨域/跨组织确认“用户是谁” Session 服务端状态 · 可即时吊销 JWT 无状态 · 短过期+刷新令牌 Session + JWT 混合 网关校验JWT · 中心管会话 OAuth2 授权码+PKCE · 授权非认证 OIDC id_token · 明确用户身份 OAuth2 + OIDC 现代Web/移动端主流组合 SAML 2.0 XML断言 · 企业IdP/SP OIDC 联邦 JSON/JWT · 更轻量现代 SAML → OIDC 迁移 保留IdP · 新增OIDC接入 共同安全底线:固定签名算法 · 校验 iss/aud/exp/nonce · redirect_uri 精确白名单 · 令牌不落 URL · 敏感信息不入 JWT payload OAuth2 是授权框架,不能单独用于登录;OIDC 才是认证层协议

二、协议交互流程拆解

以生产最常用的 OIDC 授权码 + PKCE 为例,完整交互如下:

  1. 客户端生成 code_verifier(43~128 位随机串),计算 code_challenge = BASE64URL(SHA256(code_verifier)),同时生成 state 与 nonce。
  2. 浏览器重定向到授权端点:/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。
  3. 用户在 IdP 完成登录与授权,IdP 回调 redirect_uri 并携带 code 与 state。
  4. 客户端后端用 code + code_verifier 调用 /token,换取 access_token、id_token、refresh_token。
  5. 客户端校验 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,无历史包袱OIDCJSON 轻量,移动端友好
高安全后台,需强吊销Session 或 短JWT+中心化黑名单兼顾安全与扩展

四、真实案例:JWT 无法吊销引发的越权

某 SaaS 平台使用 JWT 作为唯一会话凭证,有效期设为 24 小时,未实现刷新令牌与黑名单。运维人员离职后,其账号在后台被禁用,但该员工此前签发的 JWT 仍在有效期内,可继续调用管理接口,导致数据被导出。根因是“禁用账号”只改了数据库状态,而 JWT 校验完全无状态,不查库。

治理方案:将 JWT 有效期从 24h 缩短至 15min,引入 Refresh Token(7 天,存服务端可吊销);在网关增加 jti 黑名单校验,登出或禁用时写入 Redis,TTL 与令牌剩余有效期一致。改造后,禁用账号最迟 15 分钟内失效,登出即时生效。

常见误区:把 OAuth2 当登录协议使用,仅凭 access_token 判断用户身份。正确做法是使用 OIDC 的 id_token 或调用 /userinfo 获取身份,access_token 只代表授权范围,不保证“是谁”。
落地建议:对外统一用 OIDC 做身份入口,内部服务间用短 JWT 传递身份上下文,敏感操作强制二次校验;SAML 仅在必须对接既有企业 IdP 时保留,并规划向 OIDC 迁移。