后端面试:如何设计 OAuth 2.0 的 JWT-Secured Authorization Request?
题干与适用场景
你负责一个 OAuth 2.0 授权服务器。客户端授权请求包含高价值 scope、资源指示器和交易上下文,团队希望让授权服务器能够验证请求来源、完整性,必要时还要隐藏参数。请设计 JWT-Secured Authorization Request(JAR):Request Object 的格式、签名与加密、传递方式、生命周期、密钥轮换和失败处理。
这道题适合身份平台、支付和后端岗位。RFC 9101 定义把授权参数放入 JWT,可用 JWS 获得完整性和来源认证,用 JWE 获得机密性。回答还要区分 JAR 与 PAR:前者保护请求对象,后者把请求直接推送到授权服务器并返回引用。
面试官考察点
- 能否把授权端点、客户端、密钥目录和令牌端点的责任分开。
- 能否验证 JWT 的 issuer、audience、签名算法、时间窗口和关键 OAuth 参数。
- 能否处理请求对象与外层参数不一致、重放、压缩放大和密钥轮换。
- 能否说明 JAR、PAR、PKCE、state 和 nonce 各自解决的威胁。
- 能否将规范要求落成可观测、可灰度和可回滚的服务。
回答前需要澄清的问题
- 客户端是 public 还是 confidential?Request Object 由客户端签名,还是由后端代签?
- 需要只保证完整性,还是还要让浏览器和中间代理看不到 scope、资源和交易字段?
- 授权服务器是否允许外层参数覆盖 JWT 内的参数?不同客户端的
request与request_uri策略是什么? - 是 OIDC 登录、支付授权,还是普通 OAuth?是否要求 nonce、等级提升和审计不可抵赖?
- 密钥来自静态注册、JWKS URL 还是硬件安全模块?轮换和撤销的目标时间是多少?
30 秒回答框架
我会先确定 Request Object 的签发者和信任锚点,再验证 iss、aud、clientid、redirecturi、response_type、scope、时间和随机数。仅需要完整性时使用受允许算法的 JWS;还要保密时再使用 JWE。授权服务器把 JWT 中的参数作为规范定义的请求来源,拒绝外层冲突,限制大小与有效期,并记录 jti 防重放。JAR 保护请求对象,PAR 保护传输和引用,PKCE 绑定授权码兑换者,三者可组合。
分步骤深入解答
1. 定义 Request Object 信任边界
客户端把授权参数编码为 JWT,通过 request 参数直接提交,或先经 PAR 端点提交后用 request_uri 引用。授权服务器必须根据客户端注册信息决定可信签发者和算法,不能仅按 JWT 头部的 kid 或 jku 动态下载任意密钥。
JWT 的 iss、aud、clientid、redirecturi、response_type、scope 和资源字段要与客户端注册策略及授权请求上下文匹配。外层参数不能悄悄改变 JWT 已保护的值;冲突时拒绝并返回通用错误。
2. 选择 JWS、JWE 与算法策略
只要求完整性和来源认证时使用 JWS;授权请求包含不应暴露给浏览器或代理的字段时使用 JWE。算法采用白名单,禁止接受 none 或未批准的算法切换,并检查 JWT 头、嵌套顺序和最大字节数。
JWE 的接收方密钥来自授权服务器注册信息;JWS 的验证密钥来自客户端注册的公钥或经过可信目录发布的 JWKS。解密、验签和声明校验失败都应在同一业务错误边界内处理,避免通过错误细节泄露密钥或请求存在性。
3. 校验声明与时间窗口
必须验证 iss、aud、exp 和适用的 nbf、iat。exp 使用短窗口,服务器时钟允许小幅 skew,但不能为了兼容而无限延长。jti 或等价唯一标识用于审计和重放检测;同一请求对象被第二次消费时拒绝或要求重新生成。
授权服务器仍需执行 redirect URI 精确匹配、客户端允许的 response type 和 scope 检查。JAR 的签名不代表用户已经登录或同意,用户会话、同意页、认证等级和风控决策仍属于授权端点。
4. 处理外层参数与请求来源
实现时先解析请求来源,再建立规范规定的参数优先级。若 request 与普通查询参数同时出现,不能让未签名的外层值覆盖已签名值;不一致、重复或缺失关键字段时返回 invalid_request。
采用 PAR 时,客户端把 Request Object 直接发送给授权服务器,服务器返回短 TTL 的 request_uri。浏览器随后只携带引用,减少 URL 泄露和长度限制。PAR 不会替代 JAR 的签名或加密,JAR 也不会自动提供引用生命周期。
5. 防重放、交换与资源消耗
jti 与客户端 ID 组成去重键,放在带 TTL 的存储中;高风险流程可要求一次性消费。限制 JWT 大小、嵌套深度、解密 CPU 和 JWKS 缓存刷新频率,防止压缩或加密输入造成资源消耗攻击。
Request Object 要绑定客户端和 redirect URI。结合 PKCE、state 与 OIDC nonce,可分别降低授权码截获、会话 CSRF 和认证响应替换风险。不要把完整 JWT、交易字段或客户端断言写入普通日志。
6. 设计密钥目录与轮换
每个客户端的签名验证密钥和授权服务器的加密密钥都要有版本、用途和状态。JWKS 缓存应有明确 TTL,轮换时先发布新 key,再让签发端切换,保留旧 key 到最长 token/request TTL 之后,最后撤销旧 key。
kid 找不到时可以一次受控刷新目录,但不能对每个请求无限重试外部 URL。密钥撤销、目录不可用和算法不匹配都应有指标与告警;高风险客户端在无法验证时失败关闭。
7. 可观测、灰度与回滚
记录请求对象哈希、客户端、算法、验证结果、jti 哈希和延迟,不记录密文或完整 token。指标包括签名失败、解密失败、声明冲突、过期、重复 jti、JWKS 刷新和授权完成率。
先按客户端启用 JAR,比较普通流程与 JAR 的成功率、首屏延迟和错误分布。发现密钥目录或兼容性问题时暂停放量、撤销强制策略并保留明确的旧流程边界;不要在高风险 scope 上无条件静默降级。
高质量示范回答
我会把 JAR 当作授权请求的完整性与机密性层。客户端或受信后端按注册策略生成 Request Object,授权服务器只接受白名单算法和可信密钥,验证 iss、aud、clientid、redirecturi、response type、scope、iat/exp 与 jti,并拒绝外层参数覆盖 JWT 内的受保护值。需要保密时使用 JWE,需要来源认证时使用 JWS。
请求对象设置短有效期并绑定客户端,jti 以 TTL 去重;限制大小、嵌套和解密资源,日志只记录哈希和结果。密钥目录使用版本化 JWKS,轮换时重叠发布,旧 key 保留到最长有效期结束。JAR 可与 PAR 组合:JAR 保护对象,PAR 隐藏浏览器参数并管理引用;PKCE、state、nonce 仍分别绑定授权码和会话。
上线先按客户端灰度,监控验证失败、jti 重放、JWKS 刷新、授权完成率和延迟。密钥目录异常或高风险请求无法验证时失败关闭,回滚只恢复明确批准的旧策略,不把未签名外层参数当作替代方案。
常见错误
- 只说“JWT 签名很安全”,却没有校验 issuer、audience、算法、时间和 redirect URI。
- 允许
kid或jku触发任意网络取钥,造成信任边界和 SSRF 风险。 - 让外层查询参数覆盖 JWT 内的 scope 或 redirect URI,破坏签名保护。
- 把 JAR、PAR 和 PKCE 当成同一种机制,无法解释请求、传输和授权码边界。
- 忽略
jti、短 TTL、JWT 大小和解密资源上限,留下重放或 DoS 入口。 - 轮换 key 时立即删除旧 key,导致仍在有效期内的请求全部失败。
- 验证失败后无条件回退普通授权流程,扩大高风险 scope 的攻击面。
追问及应对
客户端签名和授权服务器签名有什么区别?
客户端签名让服务器确认请求来自该客户端并防止参数被改写;授权服务器签名通常用于向下游传递服务器确认。信任锚点、密钥用途和轮换责任不同,不能共用一套无边界的 key。
JAR 已经签名,为什么还要 PKCE?
JAR 保护授权请求内容,PKCE 保护授权码兑换者。授权码在浏览器或系统回调中被截获时,仍需要 code_verifier 才能兑换。
是否必须把所有参数都放进 JWT?
应把需要完整性保护、来源认证或保密的参数放入 Request Object。规范允许的外层参数必须有明确优先级;关键字段出现冲突或缺失时拒绝,不能依赖实现约定。
JWKS 暂时不可用时怎么办?
使用短期缓存和受控刷新;缓存中没有可验证 key 时对高风险请求失败关闭并告警。不要为了可用性接受未知 key、跳过签名或无限重试外部目录。
如何兼容只支持普通授权请求的旧客户端?
按客户端元数据灰度启用,先观察验证和完成率,再对迁移完成的客户端要求 JAR。普通流程的保留范围、风险 scope 和截止时间要显式配置并审计。
如何证明没有把敏感 JWT 写进日志?
在网关、授权服务和错误追踪中统一做字段脱敏,只保留请求对象哈希、jti 哈希、客户端和结果;用日志采样检查与自动化回归测试验证 token、密文和交易字段不会出现。