OAuth 2.0 定义授权委托流程,OIDC 在 OAuth 2.0 之上定义身份认证,JWT 是一种令牌编码格式,Session 用于维护应用侧会话状态。四者可以组合使用,但协议对象和安全边界不同。

目录

  1. OAuth 2.0 解决什么问题
  2. 常见授权方式与选择原则
  3. Access Token、Refresh Token 与 JWT
  4. 资源服务器如何验证令牌并落实权限
  5. OIDC 如何补齐身份认证
  6. 浏览器登录状态与架构模式

第一章:OAuth 2.0 解决什么问题

1.1 授权委托模型

第三方客户端直接持有用户账号密码,会破坏身份凭据与资源访问权限之间的边界:

  • 应用掌握了用户的长期凭证,一旦泄露,影响范围很大。
  • 授权范围无法按资源和操作粒度收敛。
  • 一旦需要撤销授权,往往只能通过改密码完成,代价高且影响范围大。

OAuth 2.0 由授权服务器签发 Access Token。令牌携带或关联授权范围、受众和有效期,并支持独立撤销;用户凭据只由授权服务器处理。

1.2 授权发生在哪里

常见的调用链路如下:

  1. 用户在客户端发起授权操作。若业务还需要确认用户身份,则由 OIDC 承担认证语义。
  2. 客户端跳转到授权服务器,让用户完成登录和授权确认。
  3. 授权服务器向客户端发放令牌。
  4. 客户端再带着令牌访问资源服务器上的 API。

OAuth 2.0 规定客户端如何获得访问凭证。业务 API 如何实现权限规则,浏览器怎样维持登录状态,需要由其他机制处理。

1.3 四个参与者

OAuth 2.0 授权码模式流程示意图

图 1:授权码模式的主链路。

  • 资源所有者(Resource Owner):通常是用户,即资源及相应授权的拥有者。
  • 客户端(Client):发起授权请求并使用令牌的应用,可以是 Web 应用、移动 App、单页应用,或后台服务。
  • 授权服务器(Authorization Server):负责对资源所有者进行认证、收集授权决定并发放令牌,也可能负责刷新令牌。
  • 资源服务器(Resource Server):真正提供 API 或资源的服务,它接收令牌并决定是否放行请求。

授权服务器和资源服务器可以部署在同一个产品中,职责仍要分清:

  • 授权服务器负责“发证”。
  • 资源服务器负责“验票并放行”。

后文涉及的 JWT 验签、OIDC 和 BFF,都建立在这条职责边界上。


第二章:常见授权方式与选择原则

OAuth 2.0 有多种授权方式,选择时主要看客户端类型和用户是否参与:

  • 有用户参与时,优先考虑授权码模式并启用 PKCE。
  • 服务调用服务且没有用户参与时,考虑客户端凭证模式。
  • 密码模式不得用于新旧系统;隐式模式也不应继续作为常规选择。

2.1 授权码模式加 PKCE:现代 OAuth 的主流做法

授权服务器先返回一个短期、一次性的授权码,客户端再用它换取令牌。PKCE 要求客户端为每次授权生成随机的 code_verifier,授权请求中只发送由它派生出的 code_challenge。交换令牌时,客户端必须提交原始 code_verifier。截获授权码的人缺少这个值,无法完成交换。

适用场景:

  • 有后端的 Web 应用
  • 移动端 App
  • 单页应用

流程如下:

  1. 客户端把用户重定向到授权服务器。
  2. 客户端同时发送 state、PKCE 的 code_challenge,以及预先登记的精确回调地址。
  3. 用户在授权服务器完成认证,并确认授权范围。
  4. 授权服务器通过回调地址返回授权码,客户端校验 state
  5. 客户端提交授权码和 code_verifier,向授权服务器换取 Access Token,必要时同时获得 Refresh Token。
  6. 客户端拿着 Access Token 调用资源服务器。

授权码短期有效且只能使用一次。PKCE 又将授权请求和令牌交换绑定到同一客户端实例。对于有后端的 Web 应用,令牌交换和存储还可以完全留在服务端。

公开客户端必须使用 PKCE,机密客户端也推荐使用。PKCE 应选择 S256 方法,并为每次授权生成独立的校验值。state 仍可用于关联应用状态;没有 PKCE 或等效机制时,还必须承担 CSRF 防护职责。

2.2 客户端凭证模式:服务调用服务

有些场景根本没有用户,例如定时任务调用内部 API、网关调用下游服务、后台服务访问权限中心。这时系统需要表达的是应用自身的身份,令牌也不对应某个终端用户。

客户端凭证模式时序图

图 2:客户端凭证模式的调用链路。

适用场景:

  • 微服务之间调用
  • 定时任务或批处理
  • 后台系统访问受保护 API

这类调用有几个明显特征:

  • 没有用户参与。
  • 令牌代表客户端自身,不对应某个终端用户。
  • 客户端必须向授权服务器证明自身身份,常见方式包括 client_secret、私钥签名或双向 TLS。
  • 一般不会签发 Refresh Token,因为客户端可以直接再次申请新的 Access Token。

2.3 密码模式:现行安全最佳实践明确禁用

密码模式允许客户端直接收集用户账号密码,再拿这些凭证向授权服务器换令牌。

问题出在边界上。用户密码再次暴露给客户端,相当于退回到把长期凭证交给应用的旧做法。

具体风险包括:

  • 客户端直接接触用户密码,泄露面扩大。
  • 无法保证用户真正只在授权服务器处登录。
  • 多因素认证、条件访问、无密码登录等现代认证能力很难自然接入。

RFC 9700 已明确规定不得使用资源所有者密码凭证模式。遗留系统也应制定迁移计划,改用授权码模式、设备授权或其他不要求客户端收集用户密码的机制。

2.4 隐式模式:曾用于浏览器,现在应尽量退出

隐式模式的初衷,是让纯前端应用不经过后端就能直接拿到 Access Token,以减少一次服务端交换。

隐式模式会把令牌直接放进浏览器上下文,泄露风险更高,也很难安全地使用刷新令牌。

RFC 9700 指出,客户端原则上不应使用隐式模式。现代浏览器应用通常改为:

  • 使用授权码模式和 PKCE。
  • 优先考虑 BFF,让令牌留在服务端。
  • 确需浏览器直接持有令牌时,缩短令牌生命周期并强化前端安全控制。

2.5 四种模式如何选

模式 是否有用户参与 令牌代表谁 当前建议
授权码模式加 PKCE 用户授权后的客户端访问权限 主流方案,优先选择
客户端凭证模式 客户端自身 服务间调用的标准方案
密码模式 用户 不得使用,应迁移
隐式模式 用户授权后的客户端访问权限 原则上不应使用

第三章:Access Token、Refresh Token 与 JWT

客户端拿到令牌后,还要弄清它的格式、验证方式和失效机制。JWT、资源服务器校验和 OIDC 的边界都与此有关。

3.1 Access Token 是什么

Access Token 是客户端调用资源服务器时携带的访问凭证。它表达客户端被授予的访问能力,资源服务器还要结合令牌受众、权限范围和具体资源规则作出最终授权决定。

Access Token 一般有以下属性:

  • 有效期有限,过期后需要重新获取。
  • 权限范围有限,常见表现就是 scope。
  • 只应发送给目标资源服务器,不等同于客户端自身的登录状态。

3.2 Refresh Token 是什么

当 Access Token 生命周期较短时,客户端如果每次都要求用户重新登录,体验会很差。为了解决这个问题,授权服务器可能会额外发放 Refresh Token。

Refresh Token 用于换取新的 Access Token,本身不直接用于访问 API。

有了 Refresh Token,Access Token 可以保持较短的有效期,用户也不用频繁重新登录。代价是 Refresh Token 本身成了更敏感的凭证,最好保存在服务端。向公开客户端签发时,应启用轮换,或通过 DPoP、双向 TLS 等方式绑定客户端,同时检测重复使用。

3.3 OAuth 2.0 并不规定 Access Token 必须是什么格式

OAuth 2.0 定义了授权框架和令牌使用方式,没有强制规定 Access Token 的格式。

现实中最常见的是两类:

不透明令牌(Opaque Token)

它是一串没有业务可读含义的随机值。资源服务器无法从令牌本身获得授权信息,通常需要调用令牌自省端点或查询受控的共享存储。

  • 资源服务器无法直接解码内容。
  • 便于服务端集中控制和吊销。
  • 校验依赖授权服务器或集中存储,也可以在风险允许的范围内短暂缓存自省结果。

JWT(JSON Web Token)

JWT 是一种结构化令牌,通常由 Header、Payload、Signature 三部分组成。它的价值在于资源服务器可以基于签名和声明进行本地校验,无需为每个请求回查授权服务器。JWT 只是令牌格式;JWT Access Token 仍需遵循授权服务器与资源服务器约定的声明语义,采用标准化配置时可以参考 RFC 9068。

  • 自包含,可以直接携带声明信息。
  • 适合分布式系统中的本地验签。
  • 已签发令牌无法仅靠删除服务端会话立即失效,通常还需短有效期、密钥轮换或拒绝列表等机制。

3.4 JWT 适合什么,不适合什么

资源服务器较多、授权信息短期内较稳定时,JWT Access Token 可以减少对中心服务的查询。遇到高频吊销、强实时控制或声明频繁变化的业务,不透明令牌反而更省事。客户端无论拿到哪种格式,都应把 Access Token 当作不可解释的字符串,避免依赖内部结构。

3.5 当授权码模式与 JWT 结合时,变化发生在哪里

换成 JWT 后,OAuth 授权流程保持不变,变化只发生在令牌校验阶段:

  1. 用户依然通过授权服务器完成登录和授权。
  2. 客户端依然通过授权码换取 Access Token。
  3. 如果这个 Access Token 恰好是 JWT,资源服务器就可以基于授权服务器公开的密钥材料进行本地校验。

JWT 改变的是验票方式。


第四章:资源服务器如何验证令牌并落实权限

资源服务器拿到令牌后,要做两类检查:

  • 确认这张令牌是真的、没过期、是发给自己的。
  • 确认这张令牌即使有效,也确实拥有访问当前资源的权限。

令牌有效,只说明票是真的。能否访问具体资源,还要看权限和资源归属。

4.1 验证不透明令牌

资源服务器无法读取不透明令牌中的授权信息,需要调用授权服务器的令牌自省接口,也就是 Introspection Endpoint。

Access Token 验证路径示意图

图 3:资源服务器对 Opaque Token 与 JWT 的验证路径。

资源服务器访问自省端点时需要完成认证,并通过 TLS 保护请求和响应。自省结果中常用的字段包括:

  • active:令牌是否当前有效
  • exp:是否已过期
  • scope:是否包含目标 API 所需范围
  • client_id:是谁申请的令牌
  • sub:如果代表用户,用户主体是谁

这种方式的优势是控制集中,适合高强度撤销和统一策略;代价是校验路径更依赖中心服务。

4.2 验证 JWT

如果 Access Token 是 JWT,资源服务器会从可信配置中取得授权服务器的 JWKS 地址,缓存公钥集合并在本地验签。令牌头部给出的任意密钥地址不能直接采信。

校验顺序可以这样安排:

  1. 检查令牌结构是否完整。
  2. 按允许列表检查签名算法,拒绝未配置的算法。
  3. 根据 kid 查找匹配公钥;未命中时按受控策略刷新 JWKS,避免每次未知 kid 都触发外部请求。
  4. 验证签名,确认内容未被篡改。
  5. 校验 issaudexp,并按需校验 nbf、令牌类型等声明。
  6. 解析 scopesubclient_id 等授权相关声明。

iss 确认签发者,aud 确认令牌是否发给当前资源服务器。

如果只验签、不校验 aud,资源服务器可能接受原本发给其他服务的令牌,这属于常见配置错误。校验实现还应限制允许的签名算法,不能直接采用令牌头部声明的任意算法。

4.3 令牌有效,不等于请求被授权

合法性校验通过后,再判断这个请求有没有权限:

  • Scope 校验:令牌获得的权限范围是否覆盖当前操作,例如 read:profilewrite:profile 不能混用。
  • 业务权限校验:角色、组织、租户、资源标签等业务维度是否满足要求。
  • 资源所有权校验:即使用户有读取订单的权限,也不代表能读取所有人的订单。

只看令牌中的角色就放行,很容易漏掉资源归属。例如,用户有读取订单的权限,也只能读取其有权查看的订单。

4.4 一条更完整的资源访问链路

一次受保护 API 的访问大致经过以下步骤:

  1. 客户端在请求头中附带 Authorization: Bearer <token>
  2. 资源服务器验证令牌合法性。
  3. 资源服务器提取主体身份和权限声明。
  4. 资源服务器根据业务规则校验 scope、角色和资源所有权。
  5. 校验全部通过后,才真正执行业务逻辑。

令牌只能提供认证和授权所需的部分信息,业务规则仍要由服务端执行。


第五章:OIDC 如何补齐身份认证

OAuth 2.0 让客户端获得访问资源的权限,却没有定义一套标准方法来证明当前用户的身份。登录场景还需要回答另一个问题:这个人是谁?OIDC 补上了这一层。

5.1 OIDC 解决身份认证问题

OIDC 建立在 OAuth 2.0 之上,为认证结果和用户身份声明规定了统一格式。OAuth 2.0 回答“你能访问什么”,OIDC 回答“你是谁”。

社交登录、统一登录和单点登录通常采用 OIDC。仅使用 OAuth 2.0,无法标准化表达认证结果,也不应通过解析 Access Token 来推断用户是否已登录。

5.2 OIDC 通过什么告诉客户端“用户是谁”

OIDC 引入了一个专门面向客户端的令牌:ID Token。

OIDC 中 ID Token 与 Access Token 的职责边界示意图

图 4:OIDC 登录后,ID Token 与 Access Token 分别流向哪里。

ID Token 用来让客户端确认一次认证事件已经发生,并获得该用户的身份标识。它面向发起登录的客户端,API 调用仍应使用 Access Token。

OIDC 授权请求必须包含 openid scope。采用授权码流程时,客户端先收到授权码,再从令牌端点同时获得 ID Token 和 Access Token,授权服务器还可能签发 Refresh Token。

常见声明包括:

  • sub:用户在该身份系统中的唯一标识
  • iss:签发者
  • aud:接收者,也就是哪个客户端应该接收这枚 ID Token
  • nonce:在请求中使用时,用于把认证响应与发起请求绑定,并辅助检测重放
  • iatexp:签发时间和过期时间
  • auth_time:用户完成认证的时间

ID Token 是经过签名的 JWT,也可以在签名后进一步加密。客户端必须把它当作来自特定签发者、面向特定客户端的认证结果来验证。

客户端要校验签名、签名算法、issaudexp。请求中带有 nonce 时,响应必须返回同一个值;aud 为多值时还要检查 azp。如果请求使用了 max_age 或指定认证上下文,也要校验 auth_timeacr 等声明。生产系统最好把这些细节交给成熟的 OIDC 库。

5.3 ID Token 和 Access Token 的边界

这两个令牌经常被混用,但职责完全不同。

项目 ID Token Access Token
面向谁 客户端 资源服务器
解决什么问题 证明用户身份 证明访问权限
是否用于调用 API 不应作为 API 调用凭证
常见内容 用户身份声明 scope、aud、主体信息等访问相关声明

拿 ID Token 调用后端 API 是常见错误,原因很直接:

  • ID Token 的受众是客户端,不一定是资源服务器。
  • 它表达的是认证结果,不直接表达 API 授权结果。
  • 资源服务器应验证并消费 Access Token,不应把 ID Token 当作 Bearer Token 使用。

5.4 OIDC 的配套能力

OIDC 还定义了几项常用能力:

  • UserInfo Endpoint:用于获取标准化的用户资料声明
  • Discovery Metadata:通常通过 /.well-known/openid-configuration 暴露授权端点、令牌端点、JWKS 地址和能力信息
  • Standard Claims:例如 nameemailpicture 等标准身份字段

有了这些约定,客户端接入不同身份提供方时,不必为每家重写一套发现和用户信息协议。


第六章:浏览器登录状态与架构模式

协议选对了,令牌放错地方照样会出问题。浏览器中的登录状态由谁维护,直接影响 XSS、CSRF 和令牌泄露的风险。

6.1 令牌与登录状态需要分别理解

OAuth 令牌的职责,是让客户端去访问资源服务器。

浏览器里的“登录状态”则通常是应用自己维护的会话状态,例如:

  • 一个 Session ID
  • 一个 HttpOnly Cookie
  • 服务端会话存储中的登录上下文

两者的分工如下:

  • Token 负责 API 访问授权。
  • Session Cookie 或同类机制负责应用内的浏览器会话连续性。

两者可以协同工作,但不能简单等同。

6.2 架构一:传统 Web 应用

这是最经典、也最容易控制安全边界的一类架构。

  1. 浏览器跳转到授权服务器完成登录。
  2. 服务端拿到授权码并换取 Access Token,必要时同时获得 Refresh Token。
  3. 服务端创建本地 Session,把令牌或关联信息保存在服务端存储中。
  4. 服务端通过 Set-Cookie 把 Session ID 写回浏览器。
  5. 浏览器后续只携带 Cookie,请求到达服务端后,再由服务端决定是否代用户调用下游 API。

浏览器看不到 Access Token。HttpOnly 可以阻止 JavaScript 直接读取会话 Cookie,令牌刷新、会话续期和统一登出也都留在服务端处理。

HttpOnly 无法阻止恶意脚本借助当前浏览器发起已认证请求,因此仍需防治 XSS。Cookie 会被浏览器自动携带,服务端还要结合 SameSite、CSRF Token、Origin 校验等措施防护 CSRF。

6.3 架构二:SPA 直接持有 Token

纯前端应用为了减少自建后端,常见做法是前端直接获取 Access Token,然后用这个令牌调用 API。

这种架构接入路径短,但令牌管理落在前端,浏览器运行环境里的风险会直接影响令牌。

  1. 浏览器完成授权流程并获取 Access Token。
  2. 前端拿到 Access Token;授权服务器可能在满足轮换或发送者约束等安全条件时签发 Refresh Token。
  3. 前端优先将令牌保存在内存中,避免把长期凭证写入 LocalStorage。
  4. 前端请求 API 时在请求头里附带 Bearer Token。

主要风险来自浏览器中的令牌暴露:

  • 令牌存在于浏览器执行环境中,XSS 风险更直接。
  • 如果保存在 LocalStorage,任何成功执行的同源恶意脚本都可能读取令牌,且持久化会扩大影响时间。
  • 刷新令牌的安全使用会更复杂,往往需要额外保护策略。

SPA 直接持有 Token,需要严格的 CSP、输入输出治理、依赖供应链控制和更短的令牌生命周期,并使用授权码模式加 PKCE。PKCE 能保护授权码交换,挡不住已经进入页面上下文的恶意脚本窃取现有令牌。

6.4 架构三:BFF 模式

BFF 全称 Backend For Frontend。OAuth 交互和令牌管理由这个后端组件负责,浏览器只保留会话 Cookie。

OAuth 与 BFF 架构示意图

图 5:BFF 模式下,浏览器只持有会话标识,令牌保留在服务端。

  1. 浏览器只和 BFF 交互。
  2. BFF 作为 OAuth 客户端,发起授权码流程并与授权服务器交换令牌。
  3. BFF 把 Access Token、Refresh Token 保存在服务端存储中。
  4. BFF 通过 HttpOnly Cookie 向浏览器维护会话。
  5. 浏览器请求业务接口时先到 BFF,再由 BFF 代理或聚合调用下游资源服务器。

BFF 让浏览器脚本接触不到 Access Token,降低了令牌被窃取并带离当前设备的风险。令牌刷新、吊销和审计集中在服务端,前端仍然可以保持独立开发。

BFF 仍需把会话 Cookie 当作高价值凭证保护,并落实 CSRF 防护、会话固定防护、严格的跨域策略和下游请求白名单。BFF 能降低令牌外泄风险,却无法让 XSS 失去借助用户会话执行操作的能力。

6.5 BFF 与 Token-Mediating Backend 的区别

有些系统也会在后端参与 OAuth 流程,但最终仍把令牌传回前端使用。这类模式通常被称为 Token-Mediating Backend。

它和 BFF 的关键区别在于令牌是否最终到达浏览器:

  • Token-Mediating Backend:后端帮忙换令牌,但前端最终仍持有 Token。
  • BFF:令牌始终留在后端,浏览器只持有会话标识。

从安全边界看,两者的风险模型并不相同。只要令牌最终到达浏览器,前端运行环境带来的风险就仍然存在。

6.6 三种模式该怎么选

架构模式 浏览器脚本是否接触 Token 主要安全边界 适用场景
传统 Web 应用 服务端会话、Cookie 与 CSRF 防护 服务端渲染、后台管理系统、强安全场景
SPA 直接持有 Token 前端运行环境、XSS 与令牌生命周期 纯前端部署、需要直接访问下游 API
BFF 服务端令牌、Cookie、CSRF 与代理边界 前后端分离且重视安全治理的新系统

如果系统涉及敏感数据、组织权限、财务信息或后台管理能力,BFF 通常比“前端直接存 Token”更稳妥。

6.7 几个容易出问题的实现细节

无论选哪种架构,下列细节都值得单独检查:

  • Cookie 应设置 HttpOnlySecure,并结合部署场景设置合适的 SameSite 和作用域。
  • 使用 Cookie 维护会话时,应落实 CSRF 防护,不能只依赖 HttpOnly
  • Refresh Token 应尽量留在服务端;向公开客户端签发时,应使用轮换或发送者约束机制。
  • 资源服务器必须校验 issaud、有效期和算法,不能只验签。
  • 不要把 ID Token 当作访问业务 API 的凭证。
  • 不要只做 scope 校验而忽略资源所有权和租户边界。

结语

把这些概念放回一条请求链就容易理解了:客户端通过 OAuth 2.0 授权码流程获得令牌,用 OIDC 的 ID Token 建立用户身份,再用 Access Token 访问 API。资源服务器负责验证令牌并执行权限规则,应用自己的登录状态则由 Session 或 BFF 维持。JWT 只是其中一种令牌格式,可以选,也可以不用。

我的建议很明确。有用户参与的新系统优先采用授权码模式加 PKCE;浏览器端处理敏感业务时,优先评估 BFF。SPA 直接持有 Token 仍然可行,但要接受更高的前端安全成本。最终选择取决于数据敏感度、部署方式和团队能否长期维护这些安全控制。


参考资料