代表性面试主题

后端面试:如何设计 OAuth 2.0 Device Authorization Grant?

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

题干

请为没有合适浏览器或输入能力的电视设计 OAuth 2.0 Device Authorization Grant:设备显示用户码,用户用手机完成授权,设备轮询取得令牌。如何定义接口、状态机、轮询策略、过期与安全边界?

题干与适用场景

这是 OAuth 协议实现与安全设计题。RFC 8628 面向智能电视、媒体主机、打印机等无法方便输入或运行浏览器的设备:设备向授权服务器申请 device_codeuser_code,用户在另一台设备的浏览器中完成登录和授权,原设备再轮询令牌端点。设备通常是 public client,不能把长期客户端密钥藏在固件里。题目不要求用 Device Flow 替代能运行系统浏览器的原生应用授权码流程。

面试官考察点

  • 能否正确区分设备码、用户码、验证 URI、访问令牌和刷新令牌的生命周期。
  • 能否把 authorization_pendingslow_downexpired_tokenaccess_denied 映射到状态机。
  • 能否设计有界轮询、退避、幂等和并发控制,避免令牌端点被打爆。
  • 能否识别用户码钓鱼、设备冒认、暴力猜码和 public client 的风险。

回答前需要澄清的问题

先确认设备是否能显示 URI 和用户码、是否能发起 HTTPS、是否有可靠时钟,以及授权服务器是否支持 OIDC 登录。再确认设备需要访问哪些资源、令牌是否必须绑定设备、用户码有效期和轮询间隔由谁配置、是否允许取消。若设备已有系统浏览器,应优先考虑 RFC 8252 的授权码流程,而不是强行使用 Device Flow。

30 秒回答框架

我会实现两个端点:设备授权端点返回短生命周期 device_code、可人工输入的 user_codeverification_uri、过期时间和最小轮询间隔;令牌端点只接受 device code 并返回令牌或标准错误。服务器保存哈希后的设备码、状态、客户端和过期时间。设备按服务器给出的 interval 轮询,收到 authorization_pending 继续,slow_down 增加间隔,成功后停止;过期、拒绝或无效立即终止。用户页面显示正在授权的设备信息,降低钓鱼风险。

分步骤深入解答

1. 建模参与者和一次授权会话

参与者包括受限设备、授权服务器、用户浏览器和资源服务器。设备授权端点创建一次会话,生成高熵随机 device_code 与便于输入的 user_code,关联客户端、请求范围、创建时间、过期时间和轮询状态。数据库只保存 device code 的不可逆摘要,避免读库泄露即可直接换取令牌;用户码需要独立索引和严格尝试次数。

2. 设计设备授权响应

响应至少包含 device_codeuser_codeverification_uriexpires_inintervalverification_uri_complete 可以提供带用户码的便捷链接,但更应在设备屏幕和用户页面同时显示相同的短码,要求用户确认正在操作的设备。设备应安全显示码,避免日志、分析事件或截图泄露;服务器不应把访问令牌放进此响应。

3. 处理用户交互与授权绑定

用户打开验证 URI 后登录并看到客户端名称、请求范围和设备信息。授权页面必须让用户确认该设备在自己身边,防止攻击者诱导用户为另一台设备授权。批准后只把会话状态改为 AUTHORIZED,不直接把令牌通过浏览器回传给设备。拒绝操作写成终态 DENIED,并让设备在下一次轮询得到明确错误。

4. 定义令牌端点状态机

令牌端点用标准错误驱动设备状态:

text
poll(device_code):
  session = lookup(hash(device_code))
  if session is missing: return invalid_grant
  if now >= session.expiresAt: return expired_token
  if session.status == DENIED: return access_denied
  if session.status != AUTHORIZED: return authorization_pending
  atomically mark CONSUMED
  return issueAccessAndRefreshTokens(session)

同一 device code 的成功兑换必须原子化,避免两个设备线程拿到两组令牌。若设备码只允许一次兑换,重复请求返回 invalid_grant 或协议约定的已消费错误;不要在失败重试中重新创建授权会话。

5. 实施轮询退避和服务器保护

设备首次轮询不得早于响应中的 interval。收到 authorization_pending 时保持相同间隔;收到 slow_down 时至少增加 5 秒,再继续轮询。设备端还要设置总截止时间、网络超时和抖动,服务器端按 device code、客户端和 IP 限速。令牌端点应返回缓存禁止策略,避免代理重放状态响应;服务端高峰时宁可返回可重试错误,也不能为每次轮询创建昂贵后台任务。

6. 处理过期、取消和恢复

设备必须把 expired_tokenaccess_denied 和永久 invalid_grant 视为终态,清理本地 device code,不再继续轮询。用户可以在设备上选择取消,服务器将会话标记为 CANCELLED,后续请求不应恢复。网络断开时保留截止时间并从同一 device code 恢复,不能自动重新申请多个会话导致用户授权错设备。设备重启后若本地没有安全保存会话状态,应重新开始并通知用户。

7. 处理 public client 与反钓鱼安全

设备端通常无法保护客户端密钥,应按 public client 对待;访问范围最小化,刷新令牌采用轮换和撤销策略。用户码要足够短以便输入,又要有足够熵抵抗在线猜测;验证端点对错误次数和 IP 组合限速。页面显示设备名称、型号或一次性短码,并建议用户确认设备屏幕上的码,降低远程钓鱼。日志只记录会话 ID、结果和哈希后的标识,不能记录原始 device code 或令牌。

8. 可观测性和测试

指标包括设备授权创建率、授权完成率、平均完成时间、每个会话轮询次数、slow_down 比例、过期与拒绝比例,以及令牌兑换冲突。测试覆盖重复兑换、超时、时钟偏差、网络重试、恶意猜码、并发轮询、用户拒绝、服务器重启和服务端限流。端到端测试必须确认用户批准的设备与最终令牌会话一致,不能只验证两个 HTTP 端点分别返回 200。

高质量示范回答

我会按 RFC 8628 建立一次性会话:设备授权端点返回高熵 device_code、短用户码、验证 URI、过期时间和最小轮询间隔,服务端保存哈希后的设备码和明确状态。用户在手机浏览器登录后看到客户端、范围和设备信息并批准;令牌端点只接受 device code,authorization_pending 继续轮询,slow_down 增加至少 5 秒间隔,expired_tokenaccess_denied 和永久无效立即终止。成功兑换在事务中原子消费 device code,避免并发发放两组令牌。设备按 public client 处理,最小化范围、轮换刷新令牌、限制猜码并要求用户核对设备屏幕上的短码。指标和测试覆盖全流程一致性、退避、恢复和钓鱼边界。

常见错误

  • 把 device code 当作用户可输入的短码,导致日志和 UI 暴露高价值凭证。
  • 忽略 slow_down,固定高频轮询令牌端点。
  • 把所有错误都当作继续重试,过期或拒绝后仍然轮询。
  • 用设备端静态客户端密钥假装它是 confidential client。
  • 用户批准后直接把令牌放入浏览器 URL 或页面脚本。
  • 并发轮询未原子消费 device code,发放多组刷新令牌。
  • 只测试轮询接口,不验证用户批准的设备和最终令牌是否绑定。

追问及应对

如何选择用户码长度和有效期?

在可输入性、在线猜测成本和用户完成时间之间权衡。使用字符集避免易混淆字符,服务器限制每个码的失败次数和总尝试速率;有效期要覆盖用户拿手机、登录和确认设备的时间,但不能无限延长钓鱼窗口。最终数值应由威胁模型和实际完成时间校准。

如何避免恶意客户端占满授权会话?

按客户端注册、IP、设备指纹和账号维度限制创建速率,限制每个主体的未完成会话数,并让过期会话异步清理。创建接口先验证客户端和 scope,再生成会话;不能把垃圾 device code 的清理压力转移给令牌端点。

用户批准后设备离线,之后如何恢复?

会话状态保留到过期时间,设备恢复网络后可继续使用原 device code 轮询。服务器应返回成功令牌或明确终态;设备端为令牌兑换请求设置幂等处理,避免网络响应丢失后重复消费。超过有效期则重新开始授权并显示新的短码。

verification_uri_complete 有什么风险?

它把用户码放入链接,减少输入但增加链接泄露和远程钓鱼风险。页面仍应显示短码并要求用户确认设备屏幕上的相同值;不要在分析、Referer、历史记录或第三方跳转中暴露完整链接。可按设备能力和威胁模型决定是否提供。

为什么不让手机把访问令牌直接传给设备?

跨设备传输令牌会扩大浏览器、二维码、剪贴板和中间页面的暴露面,也难以保证令牌交给的是正确设备。Device Flow 让设备用自己的 device code 在 TLS 连接中兑换令牌,用户页面只改变服务器状态;若产品需要近场绑定,应另加经过认证的安全通道。

如何监控轮询系统是否退避正确?

记录会话级轮询次数、时间间隔分布、slow_down 后的实际间隔、终态耗时和按客户端聚合的失败率。用受控测试注入重复轮询,验证服务端返回 slow_down 后客户端至少增加 5 秒且不会产生同步风暴;把异常高频客户端隔离,不用全局放宽间隔。

公开来源

同类题目