JWT、Bearer、OAuth 2.0、OpenID Connect(OIDC)和单点登录(SSO)经常出现在同一张「认证方案」清单里,但这些术语分属不同层次。JWT 是声明格式,Bearer 是凭据使用方式,OAuth 2.0 处理委托授权,OIDC 在 OAuth 2.0 之上补充身份认证,而 SSO 描述跨应用免重复登录的体验。
层次混乱会直接进入实现。例如,把 Access Token 当成用户身份证明,把解码 JWT 当成完成校验,或者把 localStorage 与 Refresh Token 组合成默认方案。认证架构评审应先问清每个组件负责什么,再讨论具体框架和代码。
以下按 7 组边界分别说明这些概念的职责和相关安全条件。
1. Authentication 确认身份,Authorization 决定权限
Authentication 回答「请求者是谁」。系统验证密码、Passkey、一次性验证码、客户端证书或其他凭据,得到一个可供后续处理的 Principal。Authorization 接着判断这个 Principal 能否读取某条记录、调用某个接口或执行某项操作。
两者的先后顺序不能替代各自的职责。成功登录只说明身份已经确认,不代表可以访问所有资源;角色、Scope、资源归属和上下文策略仍要单独判断。
401 Unauthorized 的名称容易造成误解。按照 RFC 9110,请求缺少目标资源所需的有效认证凭据时返回 401,响应还必须包含适用的 WWW-Authenticate Challenge。凭据有效但不足以访问资源时,通常返回 403 Forbidden。因此,缺少登录状态属于认证失败,不应仅因为状态码名称里有 Unauthorized 就把它归为授权失败。
2. JWT 定义令牌格式,Bearer 定义使用方式
RFC 7519 将 JWT 定义为一种紧凑、URL-safe 的声明表示格式。JWT 可以承载 iss、sub、aud、exp 等 Claim,并通过 JWS 签名或完整性保护,也可以通过 JWE 加密。常见的三段式 JWS JWT 只做 Base64url 编码和签名,Payload 并不保密,不能放入密码、私钥或无需暴露的个人数据。
Bearer Token 的含义来自 RFC 6750:任何持有令牌的一方都能使用它,不需要同时证明自己持有某个密码学密钥。Authorization: Bearer ... 描述客户端如何向资源服务器提交凭据,令牌内容既可以是 JWT,也可以是没有可读结构的随机字符串。
由此可以拆开三个经常绑在一起的选择:
认证过程如何确认用户或服务身份;
后续请求如何携带凭据,例如 Cookie 或 Bearer Header;
凭据采用 Session ID、Opaque Token 还是 JWT。
JWT 的本地验签可以减少中心化查询,但验签成功只证明令牌由受信任的 Issuer 签发且内容未被篡改。资源服务器仍要校验允许的算法、签名、iss、aud、exp 和业务所需的 Claim。账号是否刚被冻结、权限是否已经收回、令牌是否被吊销,往往还需要短有效期、版本号、撤销列表或服务端查询来处理。
3. OAuth 2.0 处理委托授权,OIDC 提供登录语义
OAuth 2.0 允许 Client 代表 Resource Owner 访问受保护资源。Access Token 表示 Authorization Server 授予 Client 的访问权限,通常带有 Scope、Audience 和有效期。OAuth 2.0 核心规范没有定义「当前登录用户是谁」、登录事件如何表达,或者 Client 应如何验证用户身份。
OIDC 在 OAuth 2.0 授权流程中加入 openid Scope、ID Token、UserInfo Endpoint、Discovery 和身份相关校验规则。ID Token 是面向 Client 的 JWT,包含本次认证事件及 Subject 等 Claim;Access Token 面向 Resource Server,用于访问 API。两类 Token 的 Audience 和使用方不同,不能互换。
「使用 GitHub 登录」没有采用 OIDC,也不等于实现必然有缺陷。GitHub OAuth App 可以在取得 Access Token 后调用 /user API,按照 GitHub 的服务商专属契约取得用户资料并建立本地账号。这个做法不具备 OAuth 2.0 核心规范提供的通用身份认证语义。换用其他 Provider 时,需要重新核对身份标识、邮箱可信度、Token 替换攻击和账号绑定规则。
采用标准 OIDC 时,Client 仍不能只解码 ID Token。Authorization Code Flow 通常还要配合 PKCE,回调时验证 state,校验 ID Token 的签名、Issuer、Audience、有效期和 nonce。ID Token 用于 Client 建立登录状态,不应作为调用普通业务 API 的 Access Token。
4. Session 与 Token 的区别不等于 Cookie 与 Header
Session-based 架构把会话状态保存在服务端,浏览器通常只持有随机 Session ID。Token-based 架构仍可分为 Opaque Token 和 Self-contained Token:Opaque Token 需要查询或 Introspection,JWT 等自包含令牌可以在资源服务器本地验证。Token 因此不天然等于无状态,Cookie 也不天然等于 Session。
| 凭据形态 | 服务端如何验证 | 立即撤销 | 权限新鲜度 | 主要运行要求 |
| Session ID | 查询共享 Session Store | 较容易,删除会话即可 | 每次读取都可取得最新状态 | Store 可用性、Cookie 安全和 Session 固定防护 |
| Opaque Token | 本地查询或调用 Introspection Endpoint | 由 Authorization Server 统一控制 | 取决于查询结果与缓存 | 中心服务延迟、缓存和故障策略 |
| 自包含 JWT | 本地验证签名与 Claim | 较困难,常依赖短有效期或撤销状态 | Claim 在到期前可能过时 | 密钥分发、轮换、Audience 与 Claim 演进 |
Redis 是常见的共享 Session Store,因为它提供低延迟访问和过期能力,但它不是唯一正确选项。关系数据库或专门的 Session Service 也可以承担这项职责,选型要考虑持久性、故障恢复、容量和一致性要求。
多实例系统把 Session 存在单机内存或本地文件,会把会话绑定到具体实例。Sticky Session 可以暂时维持可用,但实例故障、扩缩容和滚动发布会变得更复杂。共享存储通常更适合需要横向扩展的系统;单实例内部工具则不必为形式上的「分布式」提前增加 Redis。
自包含 JWT 减少的是逐请求查询,不会自动解决注销、吊销、设备管理和权限变更。只要系统要求「立刻让某个账号或 Token 失效」,某种服务端状态就会重新出现。
5. Access Token 与 Refresh Token 承担不同风险
Access Token 高频出现在资源请求中,暴露面更大,因此应限制 Audience、Scope 和有效期。Refresh Token 只提交给 Authorization Server,用于换取新的 Access Token,通常寿命更长、权限价值更高。RFC 9700 要求公共 Client 使用 Sender-constrained Refresh Token 或 Refresh Token Rotation,以便限制或检测重放。
具体有效期没有统一的「15 分钟」或「30 天」标准答案。管理后台、支付操作、普通内容读取和机器间调用的风险不同,有效期应与撤销能力、重新认证成本、设备可信度和数据敏感度一起决定。
浏览器应用还要区分纯 SPA 与带后端的 Web 应用。长期凭据保存在 localStorage 时,同源恶意 JavaScript 可以直接读取并带走它。HttpOnly Cookie 会阻止 JavaScript 读取 Cookie,但不会阻止 XSS 以当前用户身份发起请求,也不会替代 CSRF 防护。
具备后端条件时,Backend For Frontend(BFF)可以让 OAuth Token 留在服务端,浏览器只持有带 Secure、HttpOnly 和合适 SameSite 属性的本地 Session Cookie。
纯 SPA 无法把 HttpOnly Cookie 当作可直接读取的 Refresh Token。需要由后端 Endpoint 接收 Cookie 并代理续期,或者按照浏览器 OAuth Client 的安全要求使用 Authorization Code Flow、PKCE、Rotation 和更严格的内容安全策略。收到 401 后也不应无条件刷新并无限重试,只有 invalid_token 等明确表示 Token 失效的结果才适合触发一次续期;权限不足应进入 403 处理。
6. API Key 简单,但不必是永久随机字符串
API Key 常用于识别调用应用、项目或自动化脚本,也可以映射到某个服务账号。它通常是 Opaque Credential,服务端根据 Key 查找主体、Scope、状态和配额。实现可以使用数据库、缓存、带前缀的 Key ID 或安全的摘要索引,并非每次请求都必须直接查询主数据库。
成熟的 API Key 至少需要这些管理能力:
使用密码学安全随机数生成,明文只展示一次,服务端保存可验证的摘要;
记录 Owner、Scope、创建时间、过期时间和最近使用时间;
支持多个 Key 并存,允许先创建新 Key、迁移调用方,再撤销旧 Key;
只通过 TLS 传输,优先使用
AuthorizationHeader 或约定的X-API-KeyHeader,避免放进 URL Query;日志、Tracing 和错误报告默认脱敏。
API Key 没有内置过期 Claim,不代表它无法过期。过期时间可以保存在服务端记录中,泄露后也应能立即撤销。JWT 有 exp Claim,同样不代表系统天然具备密钥轮换、吊销和泄露检测能力。
状态码也不应由「Header 是否存在」机械决定。按照 HTTP 语义,受保护资源缺少有效凭据时使用 401,请求格式本身有误时使用 400,凭据有效但权限不足时使用 403。自定义 API Key Scheme 应在接口契约中明确 Challenge、错误体和重试条件,而不是把缺少 Key 一律规定为 400 Bad Request。
7. SSO 描述登录体验,SAML 与 OIDC 提供协议
Single Sign-On 表示用户在一个身份域完成认证后,访问其他受信任应用时无需再次输入凭据。实现通常包含身份提供商(IdP)的全局会话,以及每个业务应用自己的本地会话。
SSO Cookie 通常只会发送给 IdP 配置的 Domain,其他业务应用不应跨域读取。应用发现没有本地会话时,把浏览器重定向到 IdP;IdP 根据自己的 Cookie 识别已有全局会话,再通过 SAML Response 或 OIDC Authorization Response 把结果送回应用。每个应用验证协议响应后,创建自己的 Session Cookie。
SAML 2.0 和 OIDC 都能支撑 SSO,但集成模型不同:
| 协议 | 主要产物 | 常见用途 | 实施时重点校验 |
| SAML 2.0 | XML Assertion、SAML Response 与 Metadata | 企业 Web SSO、成熟 SaaS 与既有身份系统 | 签名、Issuer、Audience、ACS URL、时间窗口与重放 |
| OIDC | ID Token、Authorization Code、Access Token 与 Discovery Metadata | Web、移动端和 API 生态中的联合登录 | Redirect URI、Issuer、Audience、签名、state、nonce 与 PKCE |
SSO 也不自动等于 Single Logout。IdP 全局会话、各应用本地会话和已经签发的 Access Token 可能拥有不同生命周期。退出一个应用时究竟只删除本地 Session,还是通知所有 Relying Party、吊销 Token 并结束 IdP 会话,需要在协议与产品层共同定义。
用一组问题结束认证方案评审
认证方案至少应写清 7 件事:主体是用户、设备还是服务;首次认证使用什么凭据;认证后建立 Session 还是签发 Token;Token 的 Issuer、Audience、Scope 和有效期是什么;资源服务器如何执行 Authorization;注销、吊销和轮换如何生效;跨应用登录是否需要 SSO,以及由哪个 IdP 和协议负责。
这组问题能把「使用 JWT 认证」拆成可验证的设计:JWT 承载什么 Claim、通过什么方式提交、由谁验证、服务端是否保留状态,以及权限变化多久生效。只有这些边界明确后,框架选型和代码实现才有可靠依据。