OAuth 2.0 设备授权流详解
TL;DR · AI 摘要
OAuth 2.0 设备授权流详解,尤其适合后端工程师,通过简单流程实现无密码登录。
核心要点
- 设备授权流适用于 CLI、IoT 设备等场景,无需手动输入密码。
- 需正确处理五种响应类型,包括授权等待、速率限制、过期等。
- 用户代码应短而易读,但不可猜测,推荐使用 8-9 个字符。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- OAuth 2.0 设备授权流
- 适用场景
- CLI
- IoT 设备
- 核心步骤
- 请求代码
- 用户验证
- 获取令牌
- 错误处理
- 授权等待
- 速率限制
- 过期
金句 / Highlights
值得收藏与分享的关键句。
设备授权流适用于 CLI、IoT 设备等场景,无需手动输入密码。
用户代码应短而易读,但不可猜测,推荐使用 8-9 个字符。
需正确处理五种响应类型,包括授权等待、速率限制、过期等。
标题:OAuth 2.0 – 设备授权流详解,尤其是面向后端工程师
来源链接:https://stackoverflow.blog/2026/05/11/oauth-2-0-device-flow-explained-for-engineers-especially-for-backend-engineers/
Markdown 内容:
第一次尝试在酒店电视上登录 Netflix 时,我差点放弃了。遥控器只有四个方向键和一个数字键盘。我的密码有18个字符,包含符号。设计登录屏幕的人要么自己从未使用过它,要么认为受苦能塑造性格。
几年后,同样的电视开始做些不同的事情。它们向我们展示了一个短码和一个 URL。我在手机上打开浏览器,输入 URL 和短码,然后就成功登录了。没有遥控器的杂耍,电视上也没有输入密码。
这就是 OAuth 2.0 的设备授权流程。大多数人只是简单地称之为“设备流”。
如果我们运行 aws sso login、gh auth login,或者在 Xbox 上登录 Spotify,我们已经用过这个功能。如果你正在为命令行工具、物联网设备、智能电视应用或其他需要输入密码不方便或不安全的场景构建后端,迟早会需要实现它。
以下是逐步说明:
步骤1:假设我们在构建一个名为 mycli 的命令行工具。用户运行:
$ mycli login下面是在后台发生的事情。
步骤 1:CLI 请求获取验证码
CLI 向授权服务器发送 POST 请求:
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 是一个长且不透明的字符串。CLI 会将其私密保存。
• user_code 是一个短且人类友好的字符串。CLI 会在屏幕上显示它。
• verification_uri 是用户输入验证码的地方。
• expires_in 是交换的有效时间。通常为15到30分钟。
• interval 是 CLI 被允许多久轮询一次以获取令牌。稍后我们会提到这一点。
步骤 2:CLI 告诉用户该做什么
你的 CLI 会打印类似以下内容:
打开此 URL 在您的浏览器中:
https://example.com/device
输入此验证码:
WDJB-MJHT
等待确认...用户会在他们的手机或笔记本电脑上打开 URL,输入验证码,并通过单点登录正常登录并批准请求。
步骤 3:CLI 轮询令牌端点
当我们使用手机时,CLI 会进入循环,每隔五秒尝试联系服务器并获取状态:
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 – 你轮询得太快了。RFC 规定你必须至少增加 5 秒的间隔。
expired_token – 交换超时,停止轮询。
access_denied – 用户点击了“否”或关闭了页面。停止轮询。
success – 令牌在响应体中,就像普通的 OAuth 响应:
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "v1.MRn7...",
"scope": "read:repos write:repos"
}这就是整个协议。如果我们能够处理这五种情况,我们就有了一个可用的设备流客户端。
1. 将 `user_code` 当作秘密对待。 它不是。用户会在他们的手机上输入它。你的工作是让它足够短以便读取但又无法猜测。标准是去掉容易混淆的字符(没有 0、O、I、1),使用 8 到 9 个字母数字字符。GitHub 使用格式为 XXXX XXXX 的 8 个字符。这是一个不错的模板。
2. 验证页面没有速率限制。 用户输入 user_code 的端点是一个暴力破解的目标。即使是从缩减的字母表中生成的 9 位字符代码,也有足够的理论熵,但在任何给定时刻,待验证的代码数量可能达到数千。请加以限制。按 IP 地址、按会话、失败后指数退避。
3. 忽略 `slow_down`。 我见过一些客户端无论服务器说什么,都会永远以相同的间隔休眠。这在开发环境中可以工作。但在生产环境中,这会让你被标记为滥用行为。
4. 不在使用 `user_code` 后立即将其标记为已消费。 用户提交验证码后,立即将其标记为已使用。原子操作。如果你的检查-然后标记逻辑不是事务性的,那么你会遇到一个潜在的严重问题。
5. 将其与 PKCE 混淆。 设备流适用于输入受限的设备。PKCE 适用于无法保守秘密的公开客户端(移动应用、单页应用)。它们表面上看起来相似(没有客户端密钥),但解决的问题不同。有时你需要两者。设备流 + PKCE 是可行的,并且越来越常见。
设备流是一种在 RFC 中看起来复杂但实际上可以写在餐巾纸上的协议。两个端点,五种响应情况。一旦你实现了一次,你会想知道为什么有人曾经在电视上使用密码。
如果你今天正在发布一个 CLI 并仍然要求用户将个人访问令牌粘贴到提示符中,请为他们着想。花一个下午的时间来实现这个功能。你的用户会注意到的。