代表性面试主题

后端面试:如何安全设计 OAuth 2.0 Pushed Authorization Requests?

后端困难
Offer.cc 编辑团队发布 更新

题干

你要为包含细粒度权限和敏感交易上下文的 OAuth 客户端引入 PAR。请设计端点、request_uri、客户端认证、过期与重放防护,并说明与普通授权请求、JAR 和 PKCE 的边界。

题干与适用场景

你负责一个 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 面试准备材料则强调系统设计中的可靠性、准确性、效率、可扩展性与安全性。本题要求把标准条款转成可运行的服务边界。

回答前需要澄清的问题

  1. 客户端是 confidential 还是 public?移动端能否安全保存 client secret,还是必须使用 PKCE 与平台绑定能力?
  2. 请求包含哪些敏感数据?是否需要 JAR 签名或加密,哪些字段必须在 PAR 阶段验证?
  3. request_uri 的可用时间、允许刷新次数、单客户端并发量和全局限流目标是什么?
  4. redirect URI 是预注册固定值,还是企业租户需要动态注册?
  5. 授权服务器是否强制所有客户端只能经 PAR 发送授权参数?旧客户端如何迁移?
  6. 授权页面刷新、浏览器回退、用户取消和网络重试是否需要可重复读取同一个请求?

30 秒回答框架

客户端通过 HTTPS POST 把授权参数发送到 PAR 端点,先完成客户端认证并校验 redirect URI、scope、资源和 PKCE 参数。服务端生成高熵、短 TTL、绑定客户端的 request_uri,返回 JSON;浏览器随后只携带 client_idrequest_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,并允许使用授权端点支持的扩展。

第二跳是用户代理到授权端点的请求,通常只带 client_idrequest_uri。授权服务器依据引用取回第一跳保存的请求,而不是信任浏览器重新提交的敏感参数。这样可以减少查询字符串泄露、长度限制和用户代理篡改的风险。

2. 设计 request_uri 存储模型

生成 request_uri 时使用密码学安全随机值,不能使用递增 ID 或可预测的业务键。服务端保存引用指向的完整授权请求、客户端 ID、创建时间、过期时间、消费状态和哈希审计字段;引用本身应绑定到创建它的客户端。

TTL 通常很短,并以 expires_in 返回。过期引用返回 invalid_request,未知引用不能暴露“存在与否”的细节。一次性消费最安全;若产品必须支持刷新,可允许同一浏览器在短窗口内读取,但要记录次数、绑定 state/nonce 并设置上限。

3. 校验客户端与授权请求

PAR 端点要按 token endpoint 的规则认证客户端,例如 mTLS 或 private_key_jwt。认证只证明“哪个客户端提交了请求”,不代表用户已登录或同意 scope。服务端应在 PAR 阶段校验客户端注册的 redirect URI、允许的 scope、资源和 response type,并在授权阶段再次校验能延迟到用户上下文才能完成的规则。

若客户端要使用签名 Request Object,服务端应按 JAR 规则验证签名、issuer、audience、过期和关键字段。PAR 是把请求安全推送到服务器的传输与引用机制,JAR 是请求对象的签名/加密机制,两者可以组合,不能互相替代。

4. 把 PKCE、state 与 nonce 放在正确位置

PAR 仍应携带 code_challenge 和方法;授权码兑换时令牌端点必须验证 code_verifierstate 用于绑定客户端会话并防 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,再逐步把 require_pushed_authorization_requests 设为强制。

7. 做数据生命周期与可观测性

引用数据只保留到 TTL、消费或审计所需期限。使用加密存储、访问控制和数据最小化,避免把敏感授权上下文复制到多个缓存。指标包括 PAR 成功率、验证失败率、过期率、重复消费率、429 比例、授权端点引用找不到比例和 token 兑换的 PKCE 失败率。

灰度时同时比较普通流程和 PAR 流程的授权完成率、首屏时延、移动端回退率和安全告警。发布前用自动化测试覆盖并发消费、引用交换、redirect URI 变体、浏览器刷新、请求超限和旧客户端兼容。

高质量示范回答

我会把流程拆成两跳。客户端先通过 HTTPS POST 调用 PAR 端点,完成客户端认证,并校验 redirect URI、scope、资源、response type、PKCE、state 和 nonce。服务器生成高熵、短 TTL、绑定客户端的 request_uri,只返回引用和 expires_in。浏览器随后访问授权端点,只提交 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 的签名或加密。需要不可抵赖或更强完整性时可以组合使用。

如何处理旧客户端?

用授权服务器元数据和客户端策略逐步启用,先观测支持率,再对目标客户端设置 require_pushed_authorization_requests。保留有明确审计和风险边界的旧流程,避免无条件全局切换。

PAR 端点遭遇大量请求怎么办?

按客户端、租户、IP 和全局资源限流,限制正文大小和存储 TTL,返回 429,并监控失败率与存储水位。高风险客户端不因流量压力静默降级到更弱的流程。

公开来源

同类题目