跳至正文

2026 年 8 月 9 日 · 阅读时长 11 分钟

7 个身份认证核心概念:JWT、OAuth 2.0 与 SSO

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 可以承载 isssubaudexp 等 Claim,并通过 JWS 签名或完整性保护,也可以通过 JWE 加密。常见的三段式 JWS JWT 只做 Base64url 编码和签名,Payload 并不保密,不能放入密码、私钥或无需暴露的个人数据。

Bearer Token 的含义来自 RFC 6750:任何持有令牌的一方都能使用它,不需要同时证明自己持有某个密码学密钥。Authorization: Bearer ... 描述客户端如何向资源服务器提交凭据,令牌内容既可以是 JWT,也可以是没有可读结构的随机字符串。

由此可以拆开三个经常绑在一起的选择:

  1. 认证过程如何确认用户或服务身份;

  2. 后续请求如何携带凭据,例如 Cookie 或 Bearer Header;

  3. 凭据采用 Session ID、Opaque Token 还是 JWT。

JWT 的本地验签可以减少中心化查询,但验签成功只证明令牌由受信任的 Issuer 签发且内容未被篡改。资源服务器仍要校验允许的算法、签名、issaudexp 和业务所需的 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。

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 留在服务端,浏览器只持有带 SecureHttpOnly 和合适 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 传输,优先使用 Authorization Header 或约定的 X-API-Key Header,避免放进 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.0XML Assertion、SAML Response 与 Metadata企业 Web SSO、成熟 SaaS 与既有身份系统签名、Issuer、Audience、ACS URL、时间窗口与重放
OIDCID Token、Authorization Code、Access Token 与 Discovery MetadataWeb、移动端和 API 生态中的联合登录Redirect URI、Issuer、Audience、签名、statenonce 与 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、通过什么方式提交、由谁验证、服务端是否保留状态,以及权限变化多久生效。只有这些边界明确后,框架选型和代码实现才有可靠依据。

参考资料

CO

我是 Cooper,一名生活在中国的开发者,多年来一直在家远程工作。

主要使用 Go、PHP 和 Rust,从事移动端、Web、跨境支付与资金系统开发。目前主要采用 Vibe Coding 构建产品,并以真实运行结果完成验证与交付。