OAuth 2.0 – 设备流(Device flow)为工程师(尤其是后端工程师)详解
Source: Stack Overflow Blog
第一次在酒店的电视上尝试登录 Netflix 时,我差点放弃。遥控器只有四个方向键和一个数字键盘,而我的密码长达 18 个字符并带有符号。几年后,同样的电视开始显示一个短码和一个 URL。我打开手机,输入该 URL,填写代码,便成功登录——无需遥控器的繁琐操作,也不需要在电视上输入密码。
这就是 OAuth 2.0 device authorization grant,通常称为 device flow。如果你曾运行过 aws sso login、gh auth login,或在 Xbox 上登录 Spotify,你已经使用过它。构建 CLI、物联网设备、智能电视应用或任何输入受限的客户端的后端时,最终都需要实现它。
设备流程概述
- 客户端(例如 CLI)向授权服务器请求 device code(设备码)和 user code(用户码)。
- 服务器返回这些码以及验证 URI 和轮询参数。
- 客户端向用户显示用户码和验证 URI。
- 用户在另一台设备上打开该 URI,输入用户码,进行身份验证并批准请求。
- 与此同时,客户端轮询 token endpoint,直至用户完成步骤或出现错误。
如果客户端正确处理这五种可能的轮询响应,流程即完成。
步骤式实现
步骤 1 – 请求设备码
POST /oauth/device_authorization HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
client_id=mycli-prod&scope=read:repos%20write:repos
典型响应
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.com/device",
"verification_uri_complete": "https://example.com/device?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}
重要字段:
| 字段 | 含义 |
|---|---|
device_code | 客户端保密的不可读字符串。 |
user_code | 向用户展示的短小可读代码。 |
verification_uri | 用户输入 user_code 的 URL。 |
expires_in | 设备码的有效期(通常为 15–30 分钟)。 |
interval | 最小轮询间隔(秒)。 |
步骤 2 – 向用户显示说明
在浏览器中打开此 URL:
https://example.com/device
输入以下代码:
WDJB-MJHT
等待确认...
用户在手机或笔记本电脑上打开该 URL,输入代码,登录(例如通过 SSO),并批准请求。
步骤 3 – 轮询令牌端点
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=mycli-prod
可能的响应
| 响应 | 含义 | 操作 |
|---|---|---|
authorization_pending | 用户尚未批准。 | 继续轮询。 |
slow_down | 轮询过快。 | 将间隔增加 ≥ 5 秒。 |
expired_token | 设备码已过期。 | 停止轮询,重新开始流程。 |
access_denied | 用户拒绝请求。 | 停止轮询,通知用户。 |
| 成功 (200) | 已授予令牌。 | 使用返回的令牌。 |
成功响应示例
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "v1.MRn7...",
"scope": "read:repos write:repos"
}
常见陷阱和最佳实践提示
-
将
user_code视为公开信息,而非机密
它由用户在另一台设备上输入,因此应简短但不可猜测。典型格式为 8 – 9 位字母数字字符,且不含易混淆的符号(例如不使用0、O、I、1)。GitHub 使用XXXX XXXX。 -
对验证端点进行速率限制
用户提交user_code的页面是暴力破解的目标。即使字母表已缩小,也可能同时存在数千个待处理的代码。请对每个 IP 或每个会话实施限制,并在失败后采用指数退避。 -
遵守
slow_down
某些客户端会忽略此指令,继续以原始间隔轮询,这可能触发滥用检测。务必遵守服务器请求的间隔增加。 -
在使用后立即使
user_code失效
原子地将代码标记为已消费。“检查‑后‑标记”的竞争条件会导致代码被重复使用,从而产生安全漏洞。 -
不要把设备授权流程与 PKCE 混淆
设备授权流程解决的是输入受限设备的问题。PKCE 适用于无法保守密钥的公共客户端(移动应用、SPA)。在适当的情况下,它们可以结合使用(设备授权 + PKCE)。
结束语
设备授权流程归结为 两个端点 和 五种响应情况。一旦你实现了可用的实现,你会惊讶于为什么仍有人让用户在 CLI 提示符中粘贴个人访问令牌。实现一次,你的用户会感激不已。