时序图示例:带 PKCE 的 OAuth 2.0 授权码流程

用一个大多数工程师都会遇到的流程学习时序图表示法:浏览器应用通过 OAuth 2.0 授权码流程和 PKCE 让用户登录,然后调用 API。

· 创始人兼开发者

发布于

时序图示例:带 PKCE 的 OAuth 2.0 授权码流程

架构图展示谁和谁通信,却无法展示通信的先后顺序,也看不出谁在等谁;而对很多问题来说,顺序就是全部。登录流程就是典型例子:同样的五条消息,顺序一变就是安全漏洞。

本文用一个大多数工程师迟早都会遇到的流程来讲解时序图表示法:一个单页应用通过带 PKCE 的 OAuth 2.0 授权码流程让用户登录,然后调用 API。

时序图展示什么

时间向下流动。每个参与方都有一条生命线,即一个下方带虚线的方框;每条消息是一支从发送方指向接收方的水平箭头。从上往下读,你得到事件的顺序;横向读,你能看出每一步有谁参与。

根据 UML 2.5.1 规范,表示法如下:

元素 画法 含义
生命线 下方带虚线的方框 参与其中的系统、服务或组件
参与者 火柴人 一个人或一个外部角色
同步消息 实线,实心箭头 一次调用;发送方等待回应
异步消息 实线,空心箭头 发送方不等待回应的消息
返回消息 虚线 对之前某条消息的回应
组合片段 带操作符的方框:alt、opt、loop、par 按条件、重复或并行执行的部分
注释 折角的方框 一条说明,例如某个参与方所做的校验

流程:带 PKCE 的授权码模式

单页应用是公共客户端:它发给浏览器的一切都可以被读取,所以无法保管客户端密钥。RFC 7636 定义的 PKCE 依然能保护授权码:应用可以证明来兑换授权码的,正是发起这次流程的同一个实例。OAuth 2.0 安全最佳当前实践 RFC 9700 要求公共客户端使用 PKCE,建议其他所有客户端也使用,并指出客户端不应使用隐式授权(implicit grant)。

步骤如下:

  1. 用户点击“登录”。
  2. 应用生成 code verifier 和 challenge。 code_verifier 是一个 43 到 128 个字符的随机字符串。code_challenge 是 BASE64URL(SHA256(code_verifier)),即 S256 方法。应用还会保存一个随机的 state。
  3. 应用带着 challenge 把浏览器重定向到授权端点:
GET /authorize?response_type=code
    &client_id=orders-spa
    &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
    &scope=orders.read
    &state=af0ifjsldkj
    &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
    &code_challenge_method=S256 HTTP/1.1
Host: auth.example.com
  1. 授权服务器让用户登录并请求授权同意。
  2. 它重定向回来,跳转到 redirect_uri,附带一个短期有效的 code 和同一个 state。
  3. 应用校验 state,与它保存的值进行比对。
  4. 应用在令牌端点兑换授权码,并发送它保存的 verifier:
POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&client_id=orders-spa&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  1. 授权服务器对 verifier 做哈希,并把结果与第 3 步的 challenge 比较。如果一致,就返回访问令牌;如果它会签发刷新令牌,也一并返回。
  2. 应用调用 API,携带 Authorization: Bearer <access token>。
  3. API 校验令牌,例如签名、签发者、受众、过期时间和权限范围,然后作出回应。

上面的 verifier 和 challenge 是 RFC 7636 中的示例值,你可以自己验证 S256 的计算结果。

图

有四条生命线的时序图:用户、浏览器应用、授权服务器和 API。十四条带编号的消息从上往下排列,返回消息为虚线,注释展示 state 和 verifier 的校验,一个 opt 片段涵盖收到 401 后的刷新。

9 个组件 · 14 个连接

在 Archboard 中打开
图表内容
  • User
  • Browser app
  • Authorization server
  • API
  • [access token expired]
  • 1 Sign in
  • 2 GET /authorize + code_challenge, state
  • 3 Sign-in and consent page
  • 4 Credentials, consent
  • 5 Redirect: code, state
  • 6 POST /token + code, code_verifier
  • 7 access_token (+ refresh_token)
  • 8 GET /orders, Bearer access_token
  • 9 200 OK
  • 10 GET /orders, expired token
  • 11 401 invalid_token
  • 12 POST /token + refresh_token
  • 13 New access and refresh tokens
  • 14 Retry with the new token
用 UML 时序图表示带 PKCE 的授权码流程:浏览器应用拿到授权码,用 code verifier 把它换成令牌,调用 API,并在 opt 片段中刷新过期的访问令牌。

这张图的读法如下:

  • 四个参与方排成一行:作为参与者的用户,然后是浏览器应用、授权服务器和 API 的生命线。每条消息都是位于自己高度的一支水平箭头,按顺序编号。
  • 消息 2 到 7 是授权码流程。注释展示了保证其安全的两项校验:应用在重定向后比对 state,授权服务器把哈希后的 verifier 与 challenge 进行比较。
  • 消息 8 和 9 是 API 调用。返回消息是虚线,箭头为空心。
  • opt 片段涵盖访问令牌过期的情况:一个 401、一次刷新和一次重试。

不只画正常路径,也要画失败路径

大多数时序图都停在“200 OK”。有意思的 bug 都藏在分支里,所以至少要把每天都会发生的那个分支画出来。如果图中有多个互斥的分支、每个分支各带一个守卫条件,就用 alt;如果只有一个部分、仅在守卫条件为真时执行,就用 opt。这里的刷新是一个 opt:只有访问令牌过期时才会发生。

刷新分支有它自己的规则。在浏览器应用中,刷新令牌通常是 bearer 令牌,因此 RFC 9700 以及 2026 年 8 月发布的浏览器应用最佳当前实践 RFC 10017 要求授权服务器在每次使用时轮换刷新令牌或将其绑定到客户端,并限制其有效期。消息 13 返回一个新的刷新令牌,正是这个原因。

RFC 10017 还按安全性给浏览器应用的架构排了序。把令牌保存在服务器端的 backend-for-frontend 最安全;像本图这样由浏览器应用自己充当 OAuth 客户端的方式最简单,也最容易受到恶意 JavaScript 的攻击。如果你选择了浏览器模式,图就是写明原因的好地方。

时序图的常见错误

  • 生命线太多。 超过六七条,标签就会挤在一起,读者也会跟丢。把流程拆开,或者把内部服务归到一条生命线后面。
  • 返回消息没有标签。 “响应”什么也没说明。写出状态码或内容,例如“302 带 code”“401 invalid_token”。
  • 细节层次混杂。 一支箭头上写 HTTP 参数,下一支写“用户很满意”,会让两类读者都困惑。选定一个层次并保持一致。
  • 只有正常路径。 超时、重试和过期的凭据,恰恰是图最能体现价值的地方。
  • 列出所有参数。 只展示读者必须核对的参数,例如 state、code_challenge 和 code_verifier,其余的交给 API 参考文档。

什么时候用,什么时候不用

协议、握手、重试、超时、竞态条件和事故时间线,凡是顺序和等待很重要的场景,都适合用时序图。静态结构用架构图或 C4 图;面向大量消费者的事件扇出,用事件驱动架构图;检查数据在哪里跨越信任边界的安全评审,用威胁建模中的数据流图。

在 Archboard 中构建

  1. 使用 Google 或 GitHub 登录,并新建一张图。
  2. 从时序节点包中添加一个 参与者 表示用户,再为浏览器应用、授权服务器和 API 添加三个 生命线 节点,排成一行、高度相同。
  3. 把每条消息画成两条生命线之间的一条直线连线,从上往下排列,并加上编号标签。返回消息设为虚线。
  4. 在消息 10 到 14 周围添加一个 组合片段,把操作符设为 opt,标签设为“[access token expired]”。
  5. 添加 UML 节点包中的 注释 节点,写明各项校验:state、哈希后的 verifier 和令牌校验。

这个示例共用 5 个图表节点,在 Free 套餐的限制之内。Archboard 自己的 MCP 服务器也用这个流程让 AI 应用登录,即带 PKCE(S256)的授权码授权;请参阅如何用 AI 智能体绘制架构图。

常见问题

PKCE 只适用于移动应用吗? 不是。它最初为原生应用设计,但 RFC 9700 要求包括单页应用在内的所有公共客户端都使用 PKCE,并建议机密客户端也使用。

单页应用应该用隐式流程吗? 不应该。RFC 9700 指出客户端不应使用隐式授权。请使用带 PKCE 的授权码流程。

OAuth 2.1 已经是正式标准了吗? 截至撰写本文时(2026 年 10 月)还不是。它仍是一份 IETF Internet-Draft,整合了 OAuth 2.0 及其安全实践,其中包括 PKCE。

alt 和 opt 有什么区别? alt 包含两个或更多互斥的分支,每个分支各带一个守卫条件。opt 只包含一个部分,仅在其守卫条件为真时执行。

一张时序图能放多少条消息? 以人们查看时的尺寸仍然清晰为限,通常是 15 到 20 条。更长的流程应按阶段拆成多张图。

免费开始 — 1 个席位,无需信用卡。

相关文章

← 返回博客

一分钟打开画布

使用 Google 或 GitHub 登录。单席位免费,团队加入后再升级。