跳到主要内容

自建 Device Flow 授权服务实现规范

适用场景:企业用户希望自行控制 CLI 授权过程,通过搭建私有授权服务并在客户端修改 endpoint 来实现自定义授权。


1. 概述

CloudBase CLI 使用 RFC 8628 — OAuth 2.0 Device Authorization Grant 协议完成设备授权。整个流程涉及三个接口:

接口方法作用规范要求
/device/codePOST客户端发起授权请求,获取 device_codeuser_code必须遵循
/device/verifyPOST用户在浏览器侧确认授权自行实现,本文仅提供参考示例
/tokenPOST客户端轮询获取授权凭证必须遵循

1.1 授权时序


2. 接口规范

2.1 申请设备授权码

POST /device/code

请求

Content-Typeapplication/json

请求体:

字段类型必填约束说明
client_idstring1–128 字符客户端标识

请求示例:

{
"client_id": "cloudbase-cli"
}

响应

成功响应(HTTP 200):

字段类型说明
device_codestring设备码,客户端后续用于轮询换取凭证,建议不少于 32 字节随机值
user_codestring用户码,格式为 XXXX-XXXX(大写字母 + 数字,排除易混淆字符 0, 1, I, O),用户在浏览器端输入以确认授权。服务端应确保活跃的 user_code 唯一,生成时需检测碰撞并重试
verification_uristring用户确认授权的页面 URL
expires_innumber设备码有效期(秒),推荐值 600
intervalnumber客户端轮询间隔(秒),推荐值 3

响应示例:

{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS8A",
"user_code": "QWER-ASDF",
"verification_uri": "https://example.com/cli-auth",
"expires_in": 600,
"interval": 3
}

错误响应

字段类型说明
errorstring错误码
error_descriptionstring错误描述

可能的错误码:

错误码说明
invalid_clientclient_id 格式非法或不在允许列表内

2.2 用户确认授权(参考示例,非强制规范)

ℹ️ 此接口由用户在浏览器端的授权页调用。由于用户会自行搭建授权页面(即 verification_uri 指向的页面),因此 该接口的路径、请求参数、鉴权方式均由实现方自行设计。本节仅提供一个参考示例,帮助理解整体流程。

核心职责

无论接口如何设计,该步骤需要完成以下事项:

  1. 验证用户身份 — 确认当前操作者已通过身份认证
  2. 接收 user_code — 用户在授权页中输入从 CLI 获取的用户码
  3. 关联授权信息 — 将 user_code 对应的 device_code 状态从 pending 更新为 authorized,并关联当前用户的身份信息

参考示例

POST /device/verify

请求体:

{
"user_code": "QWER-ASDF"
}

成功响应:

{
"status": "USER_VERIFIED"
}

💡 实现方可根据业务需求自由扩展该接口,例如增加二次确认、权限范围选择、多因素认证等能力。


2.3 轮询获取凭证

POST /token

请求

Content-Typeapplication/json

请求体:

字段类型必填约束说明
grant_typestring固定值必须为 urn:ietf:params:oauth:grant-type:device_code
device_codestring最长 256 字符通过 /device/code 获取的设备码
client_idstring1–128 字符客户端标识,需与申请设备码时一致
device_infoobject设备信息对象
device_info.osstring最长 64 字符操作系统标识
device_info.macstring最长 128 字符设备 MAC 地址
device_info.hashstring最长 256 字符设备唯一标识哈希

请求示例:

{
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS8A",
"client_id": "cloudbase-cli",
"device_info": {
"os": "darwin",
"mac": "AA:BB:CC:DD:EE:FF",
"hash": "a1b2c3d4e5f6..."
}
}

响应

授权成功(HTTP 200)

⚠️ 与 RFC 8628 标准的差异说明:标准 OAuth 2.0 Token Response 使用 access_tokentoken_typerefresh_tokenexpires_inscope 等字段。本接口的成功响应为 CloudBase 业务扩展格式,直接返回 refreshToken、临时密钥等业务字段,以适配 CloudBase CLI 的凭证体系。实现方应按下表字段返回,而非标准 OAuth Token Response。

ℹ️ 自建授权服务能力说明:在企业自建授权服务场景下,CloudBase CLI 主要依赖临时密钥完成后续操作。refreshTokenexpired 为兼容保留字段,通常依赖中心化账号体系生成,企业自建服务暂时无需实现其真实语义。

用户已确认授权且凭证生成成功时,返回以下字段:

字段类型说明
refreshTokenstring兼容保留字段。企业自建授权服务无需实现,固定返回空字符串 ""
uinstring授权用户的主账号 UIN
macstring回传设备 MAC 地址
osstring回传操作系统标识
tokenIdstring令牌 ID,用于标识和管理登录会话
expirednumber兼容保留字段。企业自建授权服务无需实现真实过期语义,建议固定返回 0
tmpTokenstring临时安全令牌(STS Token)
tmpSecretIdstring临时密钥 SecretId
tmpSecretKeystring临时密钥 SecretKey
tmpExpirednumber临时密钥过期时间戳(毫秒)

成功响应示例:

{
"refreshToken": "",
"uin": "100000001",
"mac": "AA:BB:CC:DD:EE:FF",
"os": "darwin",
"tokenId": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"expired": 0,
"tmpToken": "xxxxxxxxxxxxxxxx",
"tmpSecretId": "AKIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"tmpSecretKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"tmpExpired": 1750557600000
}

💡 在自建授权服务场景中,refreshToken 为空属于预期行为。CLI 通过临时密钥完成后续访问,不要求实现可续期的长期令牌。

授权未完成 / 错误(HTTP 400)

当授权流程尚未完成或发生错误时,返回 HTTP 400 及错误对象:

字段类型说明
errorstring错误码
error_descriptionstring错误描述

错误码一览:

错误码说明客户端行为
authorization_pending用户尚未完成授权继续轮询,按 interval 间隔重试
slow_down轮询过于频繁增大轮询间隔后继续轮询
expired_token设备码已过期停止轮询,需重新发起 /device/code
invalid_clientclient_id 不匹配停止轮询,检查客户端配置
invalid_grant授权数据异常停止轮询,需重新发起流程
unsupported_grant_type不支持的 grant_type停止轮询,检查请求参数
already_consumed设备码已被使用停止轮询,避免重放

错误响应示例:

{
"error": "authorization_pending",
"error_description": "The authorization request is still pending"
}

3. 设备码状态机

设备码在整个生命周期中经历以下状态流转:

状态说明
待授权pending初始状态,等待用户确认
已授权authorized用户已确认,等待客户端换取凭证
已过期expired设备码超过有效期
已消费consumed凭证已成功下发,设备码不可重用

4. 实现要求

4.1 安全性要求

要求说明
传输安全所有接口必须通过 HTTPS(TLS 1.2+)提供服务,禁止明文 HTTP 传输
设备码随机性device_code 应使用密码学安全的随机数生成,建议不少于 32 字节
用户码字符集使用字符集 23456789ABCDEFGHJKLMNPQRSTUVWXYZ(排除 0, 1, I, O),降低用户输入错误
一次性消费每个 device_code 仅允许成功换取一次凭证,换取后应立即标记为 consumed
防重放consumed 状态需保留一定时间(建议 ≥ 60 秒),防止重放攻击
并发安全对同一 device_code 的并发 token 请求需做互斥控制,避免重复下发凭证
验证接口鉴权/device/verify 必须在已认证的上下文中调用,确保操作者身份可信

4.2 限流建议

接口建议限制
/device/code同一 IP 每分钟不超过 20 次
/token同一 IP 每分钟不超过 120 次
/device/verify根据业务需求自定义

4.3 时间参数建议

参数推荐值说明
设备码有效期600 秒(10 分钟)用户需在此时间内完成授权
轮询间隔3 秒客户端轮询的最小间隔
临时密钥有效期按业务需求设定应覆盖 CLI 完成目标操作所需时长
consumed 状态保留≥ 60 秒防重放窗口

5. 客户端轮询逻辑参考

客户端在获取到 device_code 后,按以下逻辑轮询 /token


6. 完整流程示例

Step 1:客户端请求设备码

curl -X POST https://your-auth-server.example.com/device/code \
-H "Content-Type: application/json" \
-d '{"client_id": "cloudbase-cli"}'

响应:

{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS8A",
"user_code": "QWER-ASDF",
"verification_uri": "https://your-auth-server.example.com/cli-auth",
"expires_in": 600,
"interval": 3
}

Step 2:CLI 展示信息

请在浏览器中打开 https://your-auth-server.example.com/cli-auth
并输入授权码: QWER-ASDF

Step 3:用户在浏览器确认(实现方自行设计)

用户在浏览器中打开 verification_uri,完成身份认证后输入 user_code 确认授权。以下为参考示例:

curl -X POST https://your-auth-server.example.com/device/verify \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <user-session-token>" \
-d '{"user_code": "QWER-ASDF"}'

参考响应:

{
"status": "USER_VERIFIED"
}

Step 4:客户端轮询获取凭证

curl -X POST https://your-auth-server.example.com/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS8A",
"client_id": "cloudbase-cli",
"device_info": {
"os": "darwin",
"mac": "AA:BB:CC:DD:EE:FF",
"hash": "a1b2c3d4e5f6..."
}
}'

轮询中(用户尚未确认):

{
"error": "authorization_pending",
"error_description": "The authorization request is still pending"
}

授权成功:

{
"refreshToken": "",
"uin": "100000001",
"mac": "AA:BB:CC:DD:EE:FF",
"os": "darwin",
"tokenId": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"expired": 0,
"tmpToken": "xxxxxxxxxxxxxxxx",
"tmpSecretId": "AKIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"tmpSecretKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"tmpExpired": 1750557600000
}

附录 A:错误码速查表

错误码出现接口
authorization_pendingauthorization_pending/token
expired_tokenexpired_token/token
slow_downslow_down/token
invalid_clientinvalid_client/device/code, /token
invalid_grantinvalid_grant/token
unsupported_grant_typeunsupported_grant_type/token
already_consumedalready_consumed/token

💡 /device/verify 接口由实现方自行设计,其错误码不在本规范约束范围内。