后端面试:如何设计 OAuth 2.0 Step-Up Authentication Challenge?
题干与适用场景
一个资源服务器允许普通 access token 读取资料,但转账、修改收款账户和导出敏感数据必须具备更高认证等级。客户端未满足要求时,应得到可执行的 challenge,而不是直接返回模糊的 403。请基于 RFC 9470 设计资源服务器、授权服务器和客户端之间的 Step-Up 流程。
RFC 9470 为 Bearer challenge 定义了 insufficientuserauthentication 错误,并使用 acr_values 或相关认证上下文表达所需等级。题目考察 API 错误、令牌 claim、重新授权、重试幂等和风险降级的完整闭环。
面试官考察点
- 能否区分资源授权不足、用户认证等级不足和令牌过期。
- 能否在 WWW-Authenticate challenge 中表达认证上下文而不泄露风控细节。
- 能否让客户端依据 challenge 重新授权,并防止无限循环和参数降级。
- 能否在资源服务器验证
acr、amr、issuer、audience、scope 和时间 claim。 - 能否处理转账重试、并发、审计、回滚和用户体验。
回答前需要澄清的问题
- 哪些资源操作需要更高等级,要求的是
acr、具体amr,还是一次性交易确认? - access token 是 JWT 本地验证还是 introspection?资源服务器能否知道认证上下文?
- 客户端是 Web、移动端还是服务端应用?是否启用 PKCE、prompt 和 max_age?
- challenge 是否允许只提升一次并绑定交易金额、收款人和 nonce?
- 认证服务暂时不可用时,哪些读操作可继续,哪些写操作必须失败关闭?
30 秒回答框架
资源服务器先验证 token 的 issuer、audience、签名、过期时间和 scope;若 scope 足够但认证上下文不足,返回 401 与 WWW-Authenticate challenge,声明需要的 acr_values 和资源。客户端保存原始意图与 state,通过授权端点重新认证,拿到绑定同一 audience、scope 和上下文的 token,再重试原请求。服务端限制 challenge TTL、重试次数和交易绑定;高风险操作不静默降级。
分步骤深入解答
1. 建立认证等级和资源策略
把资源操作映射到认证策略,例如普通读取要求基础等级,修改收款账户要求抗钓鱼认证,转账还要交易确认。策略应按 endpoint、方法、租户、金额和风险信号计算,不把“写请求”简单等同于一个固定等级。
授权服务器定义可识别的 acr 值和允许的认证方法集合。资源服务器只接受注册表中的值,不能让客户端任意声明更高等级;amr 作为证据,不能替代策略对 acr 的判断。
2. 设计 challenge 响应
当 token 有效但用户认证不足时,资源服务器返回 401,并在 WWW-Authenticate 中使用 Bearer challenge 的 error="insufficientuserauthentication"。challenge 可包含要求的 acr_values、资源标识和错误 URI,但不能放入账户号、风控分数或内部规则。
如果 token 没有认证上下文,或上下文无法被可信验证,也按不足处理。token 过期、issuer 不可信、audience 不匹配和 scope 缺失要使用相应错误,不把所有失败伪装成 step-up。
3. 让客户端重新授权
客户端解析 challenge 后,将原始目标、所需 acr_values、资源参数和 PKCE 绑定到新的 authorization request。Web 客户端使用 state,OIDC 使用 nonce;移动端要避免把 challenge 直接当作可执行 URL。
重新授权必须经过授权服务器的用户认证和同意。prompt、max_age 或认证策略只表达请求,最终等级由授权服务器根据实际认证结果写入 token。客户端不能在本地把 token 标记为“已升级”。
4. 验证新 token 与原请求
资源服务器验证新 token 的签名、issuer、audience、scope、exp、iat、acr 和必要的 amr。如果使用 introspection,响应必须来自可信授权服务器并包含足够上下文。不要只比较 scope,因为权限范围和认证强度是不同维度。
高风险操作把 challenge nonce、交易摘要或授权请求 ID 与 token 或服务端状态关联,避免用户完成一次高等级认证后把 token 换用于另一笔交易。token audience 要精确绑定目标 API。
5. 防止循环、重放与降级
服务端给 challenge 一个短 TTL 和唯一 ID,记录原始请求摘要、租户、资源、要求等级和重试次数。客户端最多重试一次或有限次数;同一 challenge 成功后标记消费,失败和过期都终止。
拒绝客户端把 acr_values 改成更低等级,拒绝把资源换成不同 audience,也不因 step-up 超时就回退到普通 token。对重复转账使用幂等键,避免认证重试造成重复副作用。
6. 处理可用性与错误边界
授权服务器或 introspection 不可用时,普通只读请求可依据短期缓存继续;写入、支付和权限变更默认失败关闭。challenge 错误不应暴露内部认证方法或账户状态,日志使用 challenge ID、issuer、结果和延迟。
指标包括上下文不足比例、challenge 完成率、循环次数、过期、重放、PKCE 失败、按 acr 的拒绝率和业务副作用重复率。告警按资源和租户切分,区分策略错误与攻击。
7. 灰度和回滚
先对单个 API 和租户启用,比较 401、完成率、延迟、客服反馈和高风险操作拒绝率。旧客户端不理解 challenge 时,应返回文档化错误并逐步升级 SDK,不能为所有客户端静默放开普通 token。
回滚只关闭尚未启用的资源策略,保留已有交易的 challenge 状态和审计。发生 token 上下文泄露、重复副作用或 challenge 循环时暂停放量,撤销相关策略并重新验证令牌绑定。
高质量示范回答
我会先为每类操作定义最小认证等级。资源服务器验证 token 后,如果 scope 足够但 acr 或 amr 不满足,就返回 401 和 insufficientuserauthentication challenge,携带最小化的 acr_values、资源和错误 URI。客户端保存原始意图,通过授权端点使用 state、nonce 和 PKCE 重新认证,授权服务器根据真实认证结果签发新 token。
资源服务器再次验证新 token 的 issuer、audience、scope、时间、acr 和必要的 amr,并把 challenge ID 或交易摘要绑定到服务端状态。challenge 使用短 TTL、一次性消费和有限重试,禁止降低等级、替换 audience 或无条件回退。高风险写操作在认证服务故障时失败关闭,转账使用幂等键防止重试副作用。
常见错误
- 用 403 或泛化 401 代替可执行的
insufficientuserauthenticationchallenge。 - 只检查 scope,不检查 issuer、audience、acr、amr 和时间 claim。
- 让客户端自行声明更高 acr,或把
acr_values降级后继续。 - 把 challenge 直接当成未经验证的授权 URL,忽略 state、nonce 和 PKCE。
- 认证完成后无限重试,或没有交易绑定导致 token 跨操作复用。
- 认证服务故障时对转账和权限变更静默降级。
- 日志记录账户、风控分数或完整 token,暴露敏感信息。
追问及应对
scope 足够但 acr 不足,为什么仍返回 401?
scope 表示令牌可访问什么,acr 表示用户以什么认证上下文完成流程。资源可能同时要求两者;认证不足时客户端需要重新授权。
challenge 应该包含哪些字段?
只包含客户端可执行所需的认证等级、资源标识、错误 URI 和短期 challenge 标识。账户、金额、内部风险分数和认证方法细节应留在服务端。
客户端能否直接调用 token endpoint 请求升级 token?
不能绕过授权服务器的用户认证和同意。客户端应按照 challenge 发起授权请求,最终 acr 由授权服务器依据实际认证写入 token。
如何避免升级后的 token 被用于另一笔转账?
绑定 audience、资源、challenge ID 或交易摘要,并在服务端保存一次性状态。交易本身使用幂等键,避免重试产生重复副作用。
introspection 暂时不可用怎么办?
按资源风险分级。普通读取可使用短缓存;支付、权限变更等操作在无法确认认证上下文时失败关闭并告警。
如何兼容不理解 challenge 的旧客户端?
先提供文档化错误和 SDK 更新,按租户灰度启用。高风险操作不因旧客户端而放宽要求,迁移完成后再强制策略。