3.5 从密码到token,一个有关授权的故事
从把用户密码交给每个第三方应用走到授权码、PKCE、范围和期限约束的令牌访问,沿 OAuth 授权边界诊断重放与泄露。
学习目标
- 能沿授权请求、用户同意、授权码回调、后端换令牌和资源访问追踪一次 OAuth 授权码流程
- 能解释授权码、PKCE、redirect URI、scope、令牌期限和资源服务器各自承担的安全边界
- 能在正常、边界和故障场景中回答:授权码被截获、重放或 redirect URI 不匹配时,哪一步应拒绝
3.5 从密码到token,一个有关授权的故事
本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 3.5 从密码到token,一个有关授权的故事。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。
先想象你把房间钥匙交给一个代办人,让他替你取一次文件。更安全的做法是:你在门卫处确认代办人的身份和任务范围,门卫给他一张只能用一次、只能去指定窗口的取件单;代办人拿取件单换一张短期通行证,而不是拿到你的总钥匙。
这一章解决的是“第三方应用怎样代表用户获得有限访问,而不接触用户密码”。如果客户端直接收密码,密码会在更多地方复制、缓存和泄露;如果只发一个没有范围和期限的通行证,窃取后又能长期横向访问。授权码流程把登录、同意、交换和资源访问拆开,让每个边界都能被拒绝和审计。
三个会让授权流程失真的陷阱
五个目录节点到授权证据
3.5 从密码到token,一个有关授权的故事
总合同是:客户端发起带 state、scope、redirect URI 和 PKCE challenge 的授权请求;授权服务器让资源所有者登录并同意;回调只带一次性授权码;后端以同一客户端和 verifier 换取有范围/期限的令牌;资源服务器验证令牌后才返回受保护资源。
我把密码献给你
↡第三方应用把用户登录凭据直接收走的危险做法,也是授权码流程要消除的根本问题。描述的是凭据边界错误。客户端不应知道用户密码,也不应模拟授权服务器登录;用户登录、同意、撤销和多因素校验都留在授权服务器一侧。
token
↡代表某次授权结果的凭据,通常带有受众、范围、期限和撤销语义,资源服务器据此判断是否允许访问。不是万能钥匙。检查它的受众、签发者、范围、过期时间和传输方式;访问令牌泄露后的影响由短期限、最小 scope、绑定或撤销策略限制。
授权码 token
↡先用短期一次性授权码把浏览器回调与后端令牌交换隔开,再由客户端后端获得访问令牌的流程组合。把“用户同意”与“资源访问”分成两次交付。授权码本身不应直接调用资源 API;交换时必须再次校验客户端身份、redirect URI 和 PKCE verifier。
后记
↡从故事回到工程结论:授权是凭据、范围、期限、受众、撤销和审计共同组成的边界。提醒我们,令牌格式不是安全策略的全部。系统还要保护 state、回调、浏览器存储、日志、刷新令牌和撤销接口;每个应用只获得完成任务所需的最小权限。
最小授权码合同
authorize(client_id, redirect_uri, scope, state, code_challenge)
user_consents()
code = callback(redirect_uri, state)
token = exchange(code, client_id, redirect_uri, code_verifier)
resource = api_call(token, required_scope)合同要求授权码与原始请求绑定,交换时验证 state、客户端、回调地址和 PKCE;令牌请求与资源请求分开记录。实现中不要把 code、access token 或 refresh token 写入普通日志,也不要把开放重定向当成“方便配置”。
五步复核一次授权流程
1. 发起授权并固定绑定值
记录客户端 ID、精确 redirect URI、scope、state 和 code challenge。基线只请求一个资源范围;边界场景改动回调地址,预期是授权服务器拒绝未注册地址。
Lab
OAuth 授权绑定实验
只改变回调或授权码绑定,观察令牌是否签发以及资源服务器是否可见。
回调、state 和 PKCE 匹配,按最小 scope 访问
consent → one-time code → verifier match → token(scope=read) → API 200
判定
accept:密码不离开授权服务器,资源范围可解释
当前样本:正常授权;保存 state、redirect URI、code 状态、verifier、scope 和资源响应。
正常、边界与故障证据矩阵
| 样本 | 只改变的变量 | 预期判定 | 必存证据 |
|---|---|---|---|
| 正常 | client、回调、state、PKCE 和 scope 匹配 | 一次换 token,资源按范围返回 | 绑定摘要、授权码状态、令牌声明 |
| 边界 | scope 扩大、令牌临近过期或回调变化 | 要求重新同意/刷新或拒绝 | 同意内容、过期时间、拒绝原因 |
| 故障 | code 重放、verifier 错或 token 受众错 | 交换/资源访问失败,不泄露资源 | 首个失败检查、请求 ID、响应状态 |
故障诊断:先找哪条绑定断了
- 授权请求侧:查 client、预注册回调、scope、state 和 challenge;开放重定向应直接判为配置错误。
- 回调侧:查 state、错误参数、授权码生命周期和地址清理;回调成功不等于令牌已签发。
- 交换侧:查 client 认证、redirect URI、code verifier、一次性状态和过期时间;只要一个绑定不符就拒绝。
- 资源侧:查令牌签发者、受众、scope、期限、撤销和传输;资源服务器不能只解码而不验证。
如果用户同意后没有 token,先分离回调失败与交换失败;如果 code 能被第二次兑换,检查一次性状态和并发处理;如果 token 有效但资源被拒绝,查受众/范围/资源服务器策略。每次只改变一个绑定并重放基线。
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 我把密码献给你
第三方应用直接收集用户密码的危险做法,授权码流程要消除它。
- token
代表授权结果、带范围和期限等约束的资源访问凭据。
- 授权码 token
先交付一次性授权码,再由后端绑定客户端和 PKCE 换取令牌的流程。
- 后记
把授权回收到凭据、范围、期限、受众、撤销和审计共同构成的工程边界。
练习
练习
问题 1: 为什么授权码不能直接当访问令牌调用资源 API?
问题 2: 授权码被截获时,state 和 PKCE 分别解决什么问题?
问题 3: 修改本页“OAuth 授权绑定实验”的故障场景,使它显示一次 redirect URI 不匹配和一次 code 重放,并说明重置后应恢复哪些状态。
本页小结
- 授权码流程让授权服务器持有密码,客户端只接收一次性 code。
- redirect URI、state 和 PKCE 把浏览器回调绑定到原始请求。
- token 需要范围、受众、期限、传输和撤销边界。
- 资源服务器必须验证令牌,而不是只解码其内容。
读完后的自测问题是:当授权码被截获或 redirect URI 被替换时,你能否指出交换端应检查的绑定、拒绝结果和必须保留的审计证据?