后端面试:如何安全设计 OAuth 2.0 Pushed Authorization Requests?
题干与适用场景
你负责一个 OAuth 2.0 授权服务器。移动端和企业 Web 客户端的授权请求包含细粒度 scope、资源指示器和支付上下文,团队希望避免把完整参数放进浏览器 URL。请设计 Pushed Authorization Request(PAR)端点,并说明客户端认证、request_uri 的生成与绑定、过期、一次性使用、重放、redirect URI 校验、PKCE、错误码、限流和回滚策略。
这道题适合后端、身份平台和支付平台岗位。RFC 9126 定义 PAR:客户端先直接把授权请求推送到授权服务器,换取一个供后续浏览器授权请求引用的 request_uri。高质量回答还要说明 PAR 不能取代授权服务器对后续请求的完整校验。
面试官在考察什么
- 能否画出客户端、PAR 端点、浏览器授权端点和令牌端点之间的边界。
- 能否区分客户端认证、用户认证、授权请求完整性和 token 绑定。
- 能否处理
request_uri猜测、交换、重放、过期和 redirect URI 攻击。 - 能否把 PKCE、state、nonce、JAR 与 PAR 放到正确的层次。
- 能否用 TTL、幂等、限流、审计和灰度发布把安全设计落地。
RFC 9700 汇总了 OAuth 2.0 的现行安全最佳实践;Amazon 的 SDE II 面试准备材料则强调系统设计中的可靠性、准确性、效率、可扩展性与安全性。本题要求把标准条款转成可运行的服务边界。
回答前需要澄清的问题
- 客户端是 confidential 还是 public?移动端能否安全保存 client secret,还是必须使用 PKCE 与平台绑定能力?
- 请求包含哪些敏感数据?是否需要 JAR 签名或加密,哪些字段必须在 PAR 阶段验证?
request_uri的可用时间、允许刷新次数、单客户端并发量和全局限流目标是什么?- redirect URI 是预注册固定值,还是企业租户需要动态注册?
- 授权服务器是否强制所有客户端只能经 PAR 发送授权参数?旧客户端如何迁移?
- 授权页面刷新、浏览器回退、用户取消和网络重试是否需要可重复读取同一个请求?
30 秒回答框架
客户端通过 HTTPS POST 把授权参数发送到 PAR 端点,先完成客户端认证并校验 redirect URI、scope、资源和 PKCE 参数。服务端生成高熵、短 TTL、绑定客户端的 requesturi,返回 JSON;浏览器随后只携带 clientid 与 request_uri 访问授权端点。授权端点仍需重新校验请求、state/nonce、用户同意和 PKCE,消费后删除或标记引用。通过限流、审计、一次性策略、兼容开关与灰度指标控制风险。
分步骤深入解答
1. 先定义两跳协议
第一跳是客户端到 PAR 端点的直接 HTTPS POST,正文使用 application/x-www-form-urlencoded。这里可以安全地完成客户端认证,校验 client_id、response type、redirect URI、scope、资源指示器、state、nonce 和 PKCE 参数。RFC 9126 要求 PAR 端点使用 HTTPS,并允许使用授权端点支持的扩展。
第二跳是用户代理到授权端点的请求,通常只带 clientid 和 requesturi。授权服务器依据引用取回第一跳保存的请求,而不是信任浏览器重新提交的敏感参数。这样可以减少查询字符串泄露、长度限制和用户代理篡改的风险。
2. 设计 request_uri 存储模型
生成 request_uri 时使用密码学安全随机值,不能使用递增 ID 或可预测的业务键。服务端保存引用指向的完整授权请求、客户端 ID、创建时间、过期时间、消费状态和哈希审计字段;引用本身应绑定到创建它的客户端。
TTL 通常很短,并以 expiresin 返回。过期引用返回 invalidrequest,未知引用不能暴露“存在与否”的细节。一次性消费最安全;若产品必须支持刷新,可允许同一浏览器在短窗口内读取,但要记录次数、绑定 state/nonce 并设置上限。
3. 校验客户端与授权请求
PAR 端点要按 token endpoint 的规则认证客户端,例如 mTLS 或 privatekeyjwt。认证只证明“哪个客户端提交了请求”,不代表用户已登录或同意 scope。服务端应在 PAR 阶段校验客户端注册的 redirect URI、允许的 scope、资源和 response type,并在授权阶段再次校验能延迟到用户上下文才能完成的规则。
若客户端要使用签名 Request Object,服务端应按 JAR 规则验证签名、issuer、audience、过期和关键字段。PAR 是把请求安全推送到服务器的传输与引用机制,JAR 是请求对象的签名/加密机制,两者可以组合,不能互相替代。
4. 把 PKCE、state 与 nonce 放在正确位置
PAR 仍应携带 codechallenge 和方法;授权码兑换时令牌端点必须验证 codeverifier。state 用于绑定客户端会话并防 CSRF,OIDC 的 nonce 用于绑定认证请求与 ID token。服务端不能因为有 request_uri 就删除这些参数或把它们当作同一种保护。
5. 防止交换、重放与开放重定向
攻击者若把自己获得的 request_uri 替换到另一个授权请求中,可能改变 scope 或认证等级。服务端应把引用绑定到客户端,并要求授权请求中的客户端上下文匹配;客户端使用唯一 state、PKCE,OIDC 使用 nonce。redirect URI 严格匹配注册值,避免 PAR 端点变成开放重定向入口。
重复使用已消费引用时返回明确但不泄露内部状态的错误。记录客户端、引用哈希、时间、IP 风险信号和结果,便于发现猜测和重放。不要把完整授权请求、client assertion 或敏感资源参数写入普通访问日志。
6. 设计错误码、限流和可用性
参数非法、redirect URI 不匹配或签名失败返回 invalid_request 等 OAuth 错误;方法不对可返回 405,过大请求可返回 413,超过配额可返回 429。限流按客户端、租户、IP 和全局资源分别设置,避免攻击者用 PAR 存储拖垮授权服务器。
PAR 端点故障时,是否允许已迁移客户端回退到普通授权请求必须由安全策略决定。对支付或高风险 scope,宁可明确失败,也不要静默降级。旧客户端迁移可以先按客户端元数据开启 PAR,再逐步把 requirepushedauthorization_requests 设为强制。
7. 做数据生命周期与可观测性
引用数据只保留到 TTL、消费或审计所需期限。使用加密存储、访问控制和数据最小化,避免把敏感授权上下文复制到多个缓存。指标包括 PAR 成功率、验证失败率、过期率、重复消费率、429 比例、授权端点引用找不到比例和 token 兑换的 PKCE 失败率。
灰度时同时比较普通流程和 PAR 流程的授权完成率、首屏时延、移动端回退率和安全告警。发布前用自动化测试覆盖并发消费、引用交换、redirect URI 变体、浏览器刷新、请求超限和旧客户端兼容。
高质量示范回答
我会把流程拆成两跳。客户端先通过 HTTPS POST 调用 PAR 端点,完成客户端认证,并校验 redirect URI、scope、资源、response type、PKCE、state 和 nonce。服务器生成高熵、短 TTL、绑定客户端的 requesturi,只返回引用和 expiresin。浏览器随后访问授权端点,只提交 clientid 与 requesturi;服务器取回原请求并再次执行授权、用户会话、state/nonce 与 PKCE 校验。
引用存储包含完整请求、客户端绑定、过期和消费状态,默认一次性消费;刷新场景用短窗口、次数上限和审计。JAR 负责 Request Object 的签名或加密,PAR 负责直接推送和引用,两者可以叠加。redirect URI 严格匹配,防止引用交换和开放重定向;错误返回遵循 OAuth,按客户端、IP、租户限流。
上线采用按客户端元数据的灰度,先保留旧流程,监控成功率、时延、过期、重复消费、429 和 PKCE 失败。高风险客户端不静默回退;发生引用存储故障或异常重放时停止放量并撤销强制策略。这样既降低 URL 泄露与参数篡改,也保持了 OAuth 原有的用户授权和代码交换边界。
常见错误
- 只说“把参数放到 POST”,却没有客户端认证、引用绑定和过期策略。
- 把
request_uri当作访问令牌,或把它设计成可猜的数据库自增 ID。 - 认为 PAR 自动防重放,忽略一次性消费、TTL、state、nonce 和 PKCE。
- 把 PAR、JAR、PKCE 混成同一个功能,无法说明各自保护的边界。
- 只在 PAR 阶段验证 redirect URI,授权阶段完全信任浏览器参数。
- 忽略 413、429、并发消费、缓存泄露和敏感日志问题。
- 遇到 PAR 故障就无条件降级到普通授权,扩大高风险 scope 的攻击面。
追问及应对
request_uri 应该保存多久?
按业务风险设置短 TTL,并在响应中返回 expires_in。消费或过期后删除或标记不可用;审计只保留哈希、客户端和结果等最小字段。
用户刷新授权页面,能否重复使用引用?
默认一次性消费。若必须支持刷新,设置短时间窗口、次数上限,并让 state、nonce 与客户端会话匹配,同时监控重复读取;不要无限放开重复使用。
PAR 已经认证客户端,为什么还需要 PKCE?
客户端认证确认提交请求的客户端身份,PKCE 绑定授权码兑换者。对 public client 或移动端,PKCE 仍是防止授权码被截获后兑换的重要控制。
PAR 能否替代 JAR?
不能。PAR 解决请求直接推送、隐藏浏览器参数和引用生命周期;JAR 解决 Request Object 的签名或加密。需要不可抵赖或更强完整性时可以组合使用。
如何处理旧客户端?
用授权服务器元数据和客户端策略逐步启用,先观测支持率,再对目标客户端设置 requirepushedauthorization_requests。保留有明确审计和风险边界的旧流程,避免无条件全局切换。
PAR 端点遭遇大量请求怎么办?
按客户端、租户、IP 和全局资源限流,限制正文大小和存储 TTL,返回 429,并监控失败率与存储水位。高风险客户端不因流量压力静默降级到更弱的流程。