适用于 TV 应用和受限输入设备应用的 OAuth 2.0

本文档介绍了如何实现 OAuth 2.0 授权,以便通过在电视、游戏机和打印机等设备上运行的应用访问 Google API。更具体地说,此流程适用于无法访问浏览器或输入功能受限的设备。

OAuth 2.0 可让用户与应用共享特定数据,同时保持其用户名、密码和其他信息的私密性。例如,电视应用可以使用 OAuth 2.0 来获取选择 Google 云端硬盘中存储的文件的权限。

由于使用此流程的应用会分发到各个设备,因此假定这些应用无法保密。在用户使用应用时或应用在后台运行时,它们可以访问 Google API。

替代方案

如果您要为 Android、iOS、macOS、Linux 或 Windows(包括通用 Windows 平台)等具有浏览器访问权限和完整输入功能的平台编写应用,请使用适用于移动和桌面应用的 OAuth 2.0 流程。(即使您的应用是没有图形界面的命令行工具,也应使用该流程。)

如果您希望用户使用其 Google 账号登录,并使用 JWT ID 令牌获取基本用户个人资料信息,请参阅在电视和输入设备上登录

前提条件

为您的项目启用 API

任何调用 Google API 的应用都需要在 中启用这些 API。

如需为您的项目启用该 API,请按以下步骤操作:

  1. 中的 。
  2. 列出了所有可用的 API(按产品系列和热门程度分组)。如果列表中没有显示您要启用的 API,请使用搜索功能查找该 API,或点击其所属产品系列中的查看全部
  3. 选择您要启用的 API,然后点击启用按钮。

创建授权凭据

任何使用 OAuth 2.0 访问 Google API 的应用都必须具有授权凭据,以便向 Google 的 OAuth 2.0 服务器表明应用的身份。以下步骤介绍了如何为项目创建凭据。然后,您的应用便可使用这些凭据访问您为该项目启用的 API。

  1. 点击创建客户端
  2. 选择电视和受限输入设备应用类型。
  3. 为您的 OAuth 2.0 客户端命名,然后点击创建

确定访问权限范围

有了这一范围,您不但可以让应用仅请求访问所需的资源,而且还可以让用户控制其向您的应用授予的访问权限大小。因此,请求的范围数量与获得用户同意的可能性之间可能存在反比关系。

在开始实现 OAuth 2.0 授权之前,我们建议您确定应用需要访问权限的范围。

请参阅已安装的应用或设备的允许的范围列表。

获取 OAuth 2.0 访问令牌

即使您的应用在输入功能有限的设备上运行,用户也必须拥有对输入功能更丰富的设备的单独访问权限,才能完成此授权流程。 该流程包含以下步骤:

  1. 您的应用会向 Google 的授权服务器发送请求,其中会标识您的应用将请求访问的范围。
  2. 服务器会响应并提供一些信息,以供后续步骤使用,例如设备代码和用户代码。
  3. 您可以显示用户可以在其他设备上输入的信息,以便授权您的应用。
  4. 您的应用会开始轮询 Google 的授权服务器,以确定用户是否已授权您的应用。
  5. 用户切换到输入功能更丰富的设备,启动网络浏览器,前往第 3 步中显示的网址,然后输入第 3 步中显示的代码。然后,用户可以授予(或拒绝)对应用的访问权限。
  6. 对轮询请求的下一个响应包含您的应用需要的令牌,以便代表用户授权请求。(如果用户拒绝了对您应用的访问权限,则响应中不会包含令牌。)

下图展示了此过程:

用户在具有浏览器的其他设备上登录

以下部分将详细介绍这些步骤。鉴于设备可能具有的各种功能和运行时环境,本文档中显示的示例使用 curl 命令行实用程序。这些示例应易于移植到各种语言和运行时。

第 1 步:请求设备和用户代码

在此步骤中,您的设备会向 Google 的授权服务器 (https://oauth2.googleapis.com/device/code) 发送 HTTP POST 请求,其中包含您的应用以及您的应用想要代表用户访问的访问范围。您应使用 device_authorization_endpoint 元数据值从发现文档检索此网址。添加以下 HTTP 请求参数:

参数
client_id 必填

应用的客户端 ID。您可以在 中找到此值。

scope 必填

以空格分隔的范围列表,用于标识您的应用可以代表用户访问的资源。这些值用于填充 Google 向用户显示的意见征求页面。请参阅已安装的应用或设备的允许的范围列表。

有了这一范围,您不但可以让应用仅请求访问所需的资源,而且还可以让用户控制其向您的应用授予的访问权限大小。因此,请求的范围数量与获得用户同意的可能性之间存在反比关系。

示例

以下代码段显示了一个示例请求:

POST /device/code HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

client_id=client_id&scope=email%20profile

以下示例展示了用于发送相同请求的 curl 命令:

curl -d "client_id=client_id&scope=email%20profile" \
     https://oauth2.googleapis.com/device/code

第 2 步:处理授权服务器响应

授权服务器将返回以下响应之一:

成功响应

如果请求有效,您的响应将是一个包含以下属性的 JSON 对象:

属性
device_code Google 分配的唯一值,用于标识运行请求授权的应用的设备。用户将通过另一部具有更丰富输入功能的设备授权该设备。例如,用户可以使用笔记本电脑或手机为在电视上运行的应用授权。在本例中,device_code 用于标识电视。

借助此代码,运行该应用的设备可以安全地确定用户是已授予还是已拒绝访问权限。

expires_in device_codeuser_code 的有效时长(以秒为单位)。如果在此期间,用户未完成授权流程,并且您的设备也没有轮询以检索有关用户决定的信息,您可能需要从第 1 步重新开始此流程。
interval 设备在轮询请求之间应等待的时长(以秒为单位)。例如,如果该值为 5,则设备应每 5 秒向 Google 的授权服务器发送一次轮询请求。如需了解详情,请参阅第 3 步
user_code 一个区分大小写的值,用于向 Google 指明应用请求访问的范围。您的界面会指示用户在具有更丰富输入功能的独立设备上输入此值。然后,当 Google 提示用户授予对应用的访问权限时,会使用该值来显示正确的一组镜重。
verification_url 用户必须在单独的设备上访问的网址,以便输入 user_code 并授予或拒绝对应用的访问权限。您的界面也会显示此值。

以下代码段显示了示例响应:

{
  "device_code": "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8",
  "user_code": "GQVQ-JKEC",
  "verification_url": "https://www.google.com/device",
  "expires_in": 1800,
  "interval": 5
}

“超出配额”响应

如果您的设备代码请求超出了与您的客户 ID 关联的配额,您将收到 403 响应,其中包含以下错误:

{
  "error_code": "rate_limit_exceeded"
}

在这种情况下,请使用回退策略来降低请求速率。

第 3 步:显示用户代码

向用户显示在第 2 步中获取的 verification_urluser_code。这两个值都可以包含 US-ASCII 字符集中的任何可打印字符。您向用户显示的内容应指示用户在单独的设备上前往 verification_url 并输入 user_code

设计界面时,请遵循以下规则:

  • user_code
    • user_code 必须显示在可处理 15 个“W”大小字符的字段中。换句话说,如果您可以正确显示代码 WWWWWWWWWWWWWWW,则表示您的界面有效,我们建议您在测试 user_code 在界面中的显示方式时使用该字符串值。
    • user_code 区分大小写,不应以任何方式修改,例如更改大小写或插入其他格式字符。
  • verification_url
    • 用于显示 verification_url 的空间必须足够宽,才能处理长度为 40 个字符的网址字符串。
    • 您不得以任何方式修改 verification_url,但可以选择移除要显示的方案。如果您确实出于显示原因计划从网址中剥离 scheme(例如 https://),请确保您的应用可以同时处理 httphttps 变体。

第 4 步:轮询 Google 的授权服务器

由于用户将使用单独的设备导航到 verification_url 并授予(或拒绝)访问权限,因此当用户响应访问请求时,请求设备不会自动收到通知。因此,发出请求的设备需要轮询 Google 的授权服务器,以确定用户何时响应了请求。

请求设备应继续发送轮询请求,直到收到表明用户已回复访问权限请求的响应,或者在 第 2 步中获取的 device_codeuser_code 过期为止。第 2 步中返回的 interval 指定了请求之间等待的时间(以秒为单位)。

要轮询的端点的网址为 https://oauth2.googleapis.com/token。轮询请求包含以下参数:

参数
client_id 应用的客户端 ID。您可以在 中找到此值。
client_secret 所提供 client_id 的客户端密钥。您可以在 中找到此值。
device_code 授权服务器在第 2 步中返回的 device_code
grant_type 将此值设为 urn:ietf:params:oauth:grant-type:device_code

示例

以下代码段显示了一个示例请求:

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

client_id=client_id&
client_secret=client_secret&
device_code=device_code&
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code

以下示例展示了用于发送相同请求的 curl 命令:

curl -d "client_id=client_id&client_secret=client_secret& \
         device_code=device_code& \
         grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code" \
         -H "Content-Type: application/x-www-form-urlencoded" \
         https://oauth2.googleapis.com/token

第 5 步:用户回复访问权限请求

以下图片显示的页面与用户在导航到您在第 3 步中显示的 verification_url 时看到的页面类似:

通过输入代码连接设备

进入 user_code 并登录 Google(如果尚未登录),用户会看到如下所示的意见征求界面:

设备客户端的意见征求界面示例

第 6 步:处理对轮询请求的响应

Google 的授权服务器会使用以下某种响应来响应每个轮询请求:

已授予访问权限

如果用户授予了对设备的访问权限(通过在意见征求界面上点击 Allow),则响应中会包含访问令牌和刷新令牌。借助这些令牌,您的设备可以代表用户访问 Google API。(响应中的 scope 属性决定了设备可以访问哪些 API。)

在本例中,API 响应包含以下字段:

字段
access_token 您的应用发送的用于授权 Google API 请求的令牌。
expires_in 访问令牌的剩余生命周期(以秒为单位)。
refresh_token 您可以使用此令牌获取新的访问令牌。刷新令牌在用户撤消访问权限之前有效。 请注意,系统始终会为设备返回刷新令牌。
scope access_token 授予的访问权限范围,表示为以空格分隔且区分大小写的字符串列表。
token_type 返回的令牌类型。此时,此字段的值始终设置为 Bearer

以下代码段显示了示例响应:

{
  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "scope": "openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
  "token_type": "Bearer",
  "refresh_token": "1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}

访问令牌的有效期有限。如果您的应用需要长时间访问某个 API,则可以使用刷新令牌获取新的访问令牌。如果您的应用需要此类访问权限,则应存储刷新令牌以备日后使用。

访问遭拒

如果用户拒绝授予对设备的访问权限,则服务器响应将包含 403 HTTP 响应状态代码 (Forbidden)。响应包含以下错误:

{
  "error": "access_denied",
  "error_description": "Forbidden"
}

待授权

如果用户尚未完成授权流程,则服务器会返回 428 HTTP 响应状态代码 (Precondition Required)。响应包含以下错误:

{
  "error": "authorization_pending",
  "error_description": "Precondition Required"
}

轮询过于频繁

如果设备发送轮询请求的频率过高,则服务器会返回 403 HTTP 响应状态代码 (Forbidden)。响应包含以下错误:

{
  "error": "slow_down",
  "error_description": "Forbidden"
}

其他错误

如果轮询请求缺少任何必需参数或参数值不正确,授权服务器也会返回错误。这些请求通常具有 400 (Bad Request) 或 401 (Unauthorized) HTTP 响应状态代码。这些错误包括:

错误 HTTP 状态代码 说明
admin_policy_enforced 400 由于 Google Workspace 管理员的政策,Google 账号无法授权所请求的一个或多个镜重范围。如需详细了解管理员如何限制对范围的访问,请参阅 Google Workspace 管理员帮助文章控制哪些第三方应用和内部应用可以访问 Google Workspace 数据,直到向您的 OAuth 客户端 ID 明确授予访问权限。
invalid_client 401

未找到 OAuth 客户端。例如,如果 client_id 参数值无效,就会出现此错误。

OAuth 客户端类型不正确。确保客户端 ID 的应用类型已设置为电视和受限输入设备

invalid_grant 400 code 参数值无效、已被声明或无法解析。
unsupported_grant_type 400 grant_type 参数值无效。
org_internal 403 请求中的 OAuth 客户端 ID 属于一个项目,该项目会限制对特定 Google Cloud 组织中的 Google 账号的访问权限。确认 OAuth 应用的用户类型配置

调用 Google API

应用获取访问令牌后,如果已授予 API 所需的访问范围,您就可以使用该令牌代表给定用户账号调用 Google API。为此,请通过添加 access_token 查询参数或 Authorization HTTP 标头 Bearer 值,在向 API 发出的请求中添加访问令牌。请尽可能使用 HTTP 标头,因为查询字符串通常会显示在服务器日志中。在大多数情况下,您可以使用客户端库设置对 Google API 的调用(例如,调用云端硬盘 Files API 时)。

您可以在 OAuth 2.0 Playground 中试用所有 Google API 并查看其范围。

HTTP GET 示例

使用 Authorization: Bearer HTTP 标头调用 drive.files 端点(即云端硬盘文件 API)的代码可能如下所示。请注意,您需要指定自己的访问令牌:

GET /drive/v2/files HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer access_token

以下是使用 access_token 查询字符串参数对已验证用户调用同一 API 的示例:

GET https://www.googleapis.com/drive/v2/files?access_token=access_token

curl 示例

您可以使用 curl 命令行应用测试这些命令。下面是一个使用 HTTP 标头选项(首选)的示例:

curl -H "Authorization: Bearer access_token" https://www.googleapis.com/drive/v2/files

或者,您也可以使用查询字符串参数选项:

curl https://www.googleapis.com/drive/v2/files?access_token=access_token

刷新访问令牌

访问令牌会定期过期,并成为相关 API 请求的无效凭据。如果您请求离线访问与令牌关联的范围,则可以刷新访问令牌,而不提示用户授予权限(包括在用户不存在的情况下)。

如需刷新访问令牌,您的应用会向 Google 的授权服务器 (https://oauth2.googleapis.com/token) 发送包含以下参数的 HTTPS POST 请求:

字段
client_id 从 获取的客户端 ID。
client_secret 从 获取的客户端密钥。
grant_type OAuth 2.0 规范中所定义,此字段的值必须设置为 refresh_token
refresh_token 从授权代码交换返回的刷新令牌。

以下代码段显示了一个示例请求:

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

client_id=your_client_id&
client_secret=your_client_secret&
refresh_token=refresh_token&
grant_type=refresh_token

只要用户未撤消向应用授予的访问权限,令牌服务器就会返回包含新访问令牌的 JSON 对象。以下代码段显示了示例响应:

{
  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly",
  "token_type": "Bearer"
}

请注意,系统会对要发出的刷新令牌数量施加限制:每个客户端/用户组合有一个限制,所有客户端中的每个用户还有一个限制。您应将刷新令牌保存在长期存储空间中,并在其有效期间继续使用。如果您的应用请求的刷新令牌过多,可能会遇到这些限制,在这种情况下,较早的刷新令牌将无法使用。

撤消令牌

在某些情况下,用户可能希望撤消向某个应用授予的访问权限。用户可以访问 账号设置来撤消访问权限。如需了解详情,请参阅“有权访问您账号的第三方网站和应用”支持文档中的“撤消网站或应用访问权限”部分

应用还可以通过程序化方式撤消授予给它的访问权限。 在用户取消订阅、移除应用或应用所需的 API 资源发生重大变化的情况下,程序化撤消非常重要。换句话说,移除流程的一部分可以包含 API 请求,以确保移除之前授予应用的权限。

如需以编程方式撤消令牌,您的应用需要向 https://oauth2.googleapis.com/revoke 发出请求,并将令牌作为参数包含在内:

curl -d -X -POST --header "Content-type:application/x-www-form-urlencoded" \
        https://oauth2.googleapis.com/revoke?token={token}

该令牌可以是访问令牌或刷新令牌。如果令牌是访问令牌且具有相应的刷新令牌,则刷新令牌也会被撤消。

如果撤消操作成功处理,则响应的 HTTP 状态代码为 200。对于错误情况,系统会返回 HTTP 状态代码 400 以及错误代码。

允许的镜重

仅以下范围的设备支持 OAuth 2.0 流程:

OpenID ConnectGoogle 登录

  • email
  • openid
  • profile

Drive API

  • https://www.googleapis.com/auth/drive.appdata
  • https://www.googleapis.com/auth/drive.file

YouTube API

  • https://www.googleapis.com/auth/youtube
  • https://www.googleapis.com/auth/youtube.readonly

实现跨账号保护

为了保护用户的账号,您还应采取一项额外的措施,即利用 Google 的跨账号保护服务实现跨账号保护。借助此服务,您可以订阅安全事件通知,以便向您的应用提供有关用户账号重大变化的信息。然后,您可以根据自己决定的事件响应方式,使用这些信息来执行操作。

Google 跨账号保护服务向您的应用发送的事件类型示例包括:

  • https://schemas.openid.net/secevent/risc/event-type/sessions-revoked
  • https://schemas.openid.net/secevent/oauth/event-type/token-revoked
  • https://schemas.openid.net/secevent/risc/event-type/account-disabled

如需详细了解如何实现跨账号保护功能以及可用事件的完整列表,请参阅 使用跨账号保护功能保护用户账号 页面。