适用于客户端 Web 应用的 OAuth 2.0

本文档介绍了如何实现 OAuth 2.0 授权,以便从 JavaScript Web 应用访问 Google API。OAuth 2.0 可让用户与应用共享特定数据,同时保持其用户名、密码和其他信息的私密性。 例如,应用可以使用 OAuth 2.0 获得用户许可,将文件存储在用户的 Google 云端硬盘中。

此 OAuth 2.0 流程称为隐式授权流程。它专为仅在用户使用应用时访问 API 的应用而设计。这些应用无法存储机密信息。

在此流程中,您的应用会打开一个 Google 网址,该网址使用查询参数来标识您的应用以及该应用所需的 API 访问权限类型。您可以在当前浏览器窗口或对话框中打开该网址。用户可以使用 Google 进行身份验证,并授予所请求的权限。 然后,Google 会将用户重定向回您的应用。重定向包含一个访问令牌,您的应用会验证该令牌,然后使用该令牌发出 API 请求。

Google API 客户端库和 Google Identity 服务

如果您使用适用于 JavaScript 的 Google API 客户端库向 Google 发出授权调用,则应使用 Google Identity 服务 JavaScript 库来处理授权流程。我们强烈建议您使用 Google Identity 服务 代码模型,该模型基于更安全的 PKCE 授权代码流程。

前提条件

为您的项目启用 API

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

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

  1. 在 Google API 控制台中打开 API 库。
  2. 如果系统提示您选择一个项目或创建一个新项目,请视需要选择相应选项。
  3. API 库中列出了所有可用的 API(按产品系列和热门程度分组)。 如果列表中没有显示您要启用的 API,请使用搜索功能查找该 API,或点击该 API 所属产品系列中的查看全部。
  4. 选择您要启用的 API,然后点击启用按钮。
  5. 如果系统提示,请启用结算功能。
  6. 如果系统提示您阅读并接受该 API 的服务条款,请按提示操作。

创建授权凭据

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

  1. 前往“客户”页面。
  2. 点击创建客户端。
  3. 选择 Web 应用应用类型。
  4. 填写表单。 使用 JavaScript 发出已获授权的 Google API 请求的应用必须指定已获授权的 JavaScript 来源。来源用于标识您的应用可以向 OAuth 2.0 服务器发送请求的网域。这些来源必须遵守 Google 的验证规则。

确定访问权限范围

借助范围,您的应用可以仅请求访问其所需的资源,同时让用户能够控制他们向您的应用授予的访问权限。因此,请求的范围数量与获得用户同意的可能性之间可能存在反比关系。

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

OAuth 2.0 API 范围文档包含您可能用于访问 Google API 的范围的完整列表。

获取 OAuth 2.0 访问令牌

以下步骤展示了您的应用如何与 Google 的 OAuth 2.0 服务器交互,以征得用户同意代表用户执行 API 请求。您的应用必须先获得该同意,然后才能执行需要用户授权的 Google API 请求。

第 1 步:重定向到 Google 的 OAuth 2.0 服务器

如需请求访问用户数据的权限,请将用户重定向到 Google 的 OAuth 2.0 服务器。

OAuth 2.0 端点

生成一个网址,用于向 Google 的 OAuth 2.0 端点 (https://accounts.google.com/o/oauth2/v2/auth) 请求访问权限。此端点可通过 HTTPS 访问;纯 HTTP 连接会被拒绝。

Google 授权服务器支持 Web 服务器应用的以下查询字符串参数:

参数
client_id 必需

应用的客户端 ID。您可以在 Cloud 控制台的“客户端”页面中找到此值。

redirect_uri 必需

确定用户完成授权流程后,API 服务器将用户重定向到的位置。该值必须与您在客户端的 Cloud 控制台“客户端”页面中配置的 OAuth 2.0 客户端的某个已获授权的重定向 URI 完全一致。如果此值与所提供 client_id 的授权重定向 URI 不匹配,您将收到 redirect_uri_mismatch 错误。

请注意,http 或 https 架构、大小写和尾部斜杠(“/”)都必须匹配。

response_type 必需

JavaScript 应用需要将该参数的值设置为 token。此值指示 Google 授权服务器在用户完成授权流程后被重定向到的 URI (#) 的片段标识符中,以 name=value 对的形式返回访问令牌。

scope 必需

以空格分隔的范围列表,用于标识您的应用可以代表用户访问的资源。这些值会告知 Google 向用户显示的权限请求页面。

借助范围,您的应用可以仅请求访问其所需的资源,同时用户也可以控制向您的应用授予的访问权限。因此,请求的范围数量与获得用户同意的可能性之间存在反比关系。

我们建议您的应用尽可能在上下文中请求对授权范围的访问权限。通过使用增量授权在用户执行相关操作时请求访问用户数据,您可以帮助用户了解您的应用为何需要其请求的访问权限。

state 建议

指定应用用于在授权请求与授权服务器响应之间保持状态的任何字符串值。 在用户同意或拒绝应用的访问请求后,服务器会在 redirect_uri 的网址片段标识符 (#) 中以 name=value 对的形式返回您发送的确切值。

您可以将此参数用于多种用途,例如将用户定向到应用中的正确资源、发送 Nonce 以及缓解跨站请求伪造。由于您的 redirect_uri 可能会被猜到,因此使用 state 值可以提高您对传入连接是身份验证请求结果的保证。如果您生成随机字符串或对 Cookie 的哈希值或捕获客户端状态的其他值进行编码,则可以验证响应,以额外确保请求和响应源自同一浏览器,从而防范跨站请求伪造等攻击。如需查看有关如何创建和确认 state 令牌的示例,请参阅 OpenID Connect 文档。

include_granted_scopes 可选

使应用能够使用增量授权来请求访问上下文中的其他范围。如果您将此参数的值设置为 true 且授权请求获得批准,则新的访问令牌还将涵盖用户之前向应用授予访问权限的所有范围。如需查看示例,请参阅增量授权部分。

login_hint 可选

如果您的应用知道哪个用户正在尝试进行身份验证,则可以使用此参数向 Google 身份验证服务器提供提示。服务器会使用该提示来简化登录流程,方法是在登录表单中预填充电子邮件地址字段,或选择适当的多登录会话。

将参数值设置为电子邮件地址或 sub 标识符,这相当于用户的 Google ID。

prompt 可选

以空格分隔、区分大小写的提示列表,用于向用户显示。如果您未指定此参数,则系统仅会在您的项目首次请求访问权限时提示用户。如需了解详情,请参阅 提示用户重新同意。

可能的值包括:

none 不显示任何身份验证或权限请求页面。不得与其他值一起指定。
consent 提示用户表示同意。
select_account 提示用户选择账号。

重定向到 Google 授权服务器的示例

下面是一个示例网址,其中包含换行符和空格,以便于阅读。

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly&
 include_granted_scopes=true&
 response_type=token&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//developers.google.com/oauthplayground&
 client_id=client_id

创建请求网址后,将用户重定向到该网址。

JavaScript 示例代码

以下 JavaScript 代码段展示了如何在不使用 Google APIs 客户端库 for JavaScript 的情况下,在 JavaScript 中启动授权流程。由于此 OAuth 2.0 端点不支持跨域资源共享 (CORS),因此该代码段会创建一个表单,用于打开对该端点的请求。

/*
 * Create form to request access token from Google's OAuth 2.0 server.
 */
function oauthSignIn() {
  // Google's OAuth 2.0 endpoint for requesting an access token
  var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';

  // Create <form> element to submit parameters to OAuth 2.0 endpoint.
  var form = document.createElement('form');
  form.setAttribute('method', 'GET'); // Send as a GET request.
  form.setAttribute('action', oauth2Endpoint);

  // Parameters to pass to OAuth 2.0 endpoint.
  var params = {'client_id': 'YOUR_CLIENT_ID',
                'redirect_uri': 'YOUR_REDIRECT_URI',
                'response_type': 'token',
                'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly',
                'include_granted_scopes': 'true',
                'state': 'pass-through value'};

  // Add form parameters as hidden input values.
  for (var p in params) {
    var input = document.createElement('input');
    input.setAttribute('type', 'hidden');
    input.setAttribute('name', p);
    input.setAttribute('value', params[p]);
    form.appendChild(input);
  }

  // Add form to page and submit it to open the OAuth 2.0 endpoint.
  document.body.appendChild(form);
  form.submit();
}

第 2 步:Google 提示用户表示同意

在此步骤中,用户决定是否向您的应用授予所请求的访问权限。在此阶段,Google 会显示一个权限请求窗口,其中会显示您的应用名称、应用请求使用用户的授权凭据访问的 Google API 服务,以及要授予的访问权限范围摘要。然后,用户可以同意授予对应用请求的一个或多个范围的访问权限,也可以拒绝该请求。

您的应用在此阶段无需执行任何操作,只需等待 Google 的 OAuth 2.0 服务器返回响应,指明是否已授予任何访问权限。下一步将对该响应进行说明。

错误

向 Google 的 OAuth 2.0 授权端点发出的请求可能会显示面向用户的出错提示,而不是预期的身份验证和授权流程。常见错误代码和建议的解决方法如下:

admin_policy_enforced

由于 Google Workspace 管理员的政策,Google 账号无法授权一个或多个请求的范围。请参阅 Google Workspace 管理员帮助文章 控制哪些第三方应用和内部应用可以访问 Google Workspace 数据,详细了解管理员如何限制对所有范围或敏感和受限范围的访问,直到明确向您的 OAuth 客户端 ID 授予访问权限。

disallowed_useragent

授权端点显示在 Google 的 OAuth 2.0 政策不允许的嵌入式用户代理中。

iOS 和 macOS 开发者在 WKWebView 中打开授权请求时可能会遇到此错误。 开发者应改用 iOS 库,例如 Google 登录 iOS 版或 OpenID Foundation 的 AppAuth iOS 版。

当 iOS 或 macOS 应用在嵌入式用户代理中打开常规网页链接,并且用户从您的网站导航到 Google 的 OAuth 2.0 授权端点时,Web 开发者可能会遇到此错误。开发者应允许在操作系统的默认链接处理程序中打开常规链接,这包括通用链接处理程序或默认浏览器应用。SFSafariViewController 库也是受支持的选项。

org_internal

请求中的 OAuth 客户端 ID 属于一个项目,该项目限制只有特定 Google Cloud 组织中的 Google 账号才能访问。 如需详细了解此配置选项,请参阅“设置 OAuth 权限请求页面”帮助文章中的用户类型部分。

invalid_client

发出请求的来源未获得此客户端的授权。请参阅 origin_mismatch。

deleted_client

用于发出请求的 OAuth 客户端已被删除。删除操作可以手动执行,也可以在客户端未使用 的情况下自动执行。已删除的客户端可在删除后的 30 天内恢复。 了解详情 。

invalid_grant

使用增量授权时,令牌可能已过期或已失效。 再次对用户进行身份验证,并征求用户同意以获取新令牌。如果您仍然看到此错误,请确保您的应用已正确配置,并且您在请求中使用了正确的令牌和参数。否则,用户账号可能已被删除或停用。

origin_mismatch

发起授权请求的 JavaScript 的架构、网域和/或端口可能与为 OAuth 客户端 ID 注册的已获授权 JavaScript 来源 URI 不匹配。在 Google Cloud 控制台的“客户端”页面中查看已获授权的 JavaScript 来源。

redirect_uri_mismatch

授权请求中传递的 redirect_uri 与 OAuth 客户端 ID 的授权重定向 URI 不匹配。在 Google Cloud 控制台“客户端”页面中查看已获授权的重定向 URI。

发起授权请求的 JavaScript 的架构、网域和/或端口可能与为 OAuth 客户端 ID 注册的已获授权 JavaScript 来源 URI 不匹配。在 Google Cloud 控制台“客户端”页面中查看已获授权的 JavaScript 来源。

redirect_uri 参数可能引用了已弃用且不再受支持的 OAuth 带外 (OOB) 流程。请参阅迁移指南,更新您的集成。

invalid_request

您发出的请求存在问题。这可能是由以下几个原因导致的:

  • 请求格式不正确
  • 请求中缺少必需参数
  • 该请求使用了 Google 不支持的授权方法。验证您的 OAuth 集成是否使用了推荐的集成方法

第 3 步:处理 OAuth 2.0 服务器响应

OAuth 2.0 端点

OAuth 2.0 服务器会向访问令牌请求中指定的 redirect_uri 发送响应。

如果用户批准了请求,响应中就会包含访问令牌。如果用户未批准请求,响应中会包含出错提示。访问令牌或出错提示会在重定向 URI 的哈希片段中返回,如以下示例所示:

  • 访问令牌响应:

    https://oauth2.example.com/callback#access_token=4/P7q7W91&token_type=Bearer&expires_in=3600

    除了 access_token 参数之外,片段字符串还包含 token_type 参数(始终设置为 Bearer)和 expires_in 参数(指定令牌的生命周期,以秒为单位)。如果在访问令牌请求中指定了 state 参数,则其值也会包含在响应中。

  • 错误响应:
    https://oauth2.example.com/callback#error=access_denied

OAuth 2.0 服务器响应示例

您可以通过点击以下示例网址来测试此流程,该网址会请求以只读方式访问 Google 云端硬盘中文件的元数据,以及以只读方式查看 Google 日历活动:

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly&
 include_granted_scopes=true&
 response_type=token&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//developers.google.com/oauthplayground&
 client_id=client_id

完成 OAuth 2.0 流程后,浏览器会将您重定向到 OAuth 2.0 Playground,这是一个用于测试 OAuth 流程的工具。您会看到 OAuth 2.0 Playground 已自动捕获授权代码。

第 4 步:检查用户授予了哪些范围

请求多项权限(范围)时,用户可能不会向您的应用授予所有权限。您的应用必须验证实际授予了哪些范围,并妥善处理某些权限被拒的情况,通常是停用依赖于这些被拒范围的功能。

但也有例外情况。具有全网域授权的 Google Workspace 企业版应用或标记为受信任的应用会绕过精细权限权限请求页面。对于这些应用,用户不会看到精细权限请求页面。相反,您的应用要么会获得所有请求的范围,要么一个也得不到。

如需了解详情,请参阅如何处理精细权限。

OAuth 2.0 端点

如需检查用户是否已向您的应用授予对特定范围的访问权限,请检查访问令牌响应中的 scope 字段。access_token 授予的访问权限范围,表示为以空格分隔、区分大小写的字符串列表。

例如,以下示例访问令牌响应表明,用户已向您的应用授予对只读云端硬盘活动和日历活动权限的访问权限:

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

调用 Google API

OAuth 2.0 端点

应用获取访问令牌后,如果已获得 API 所需的访问权限范围,您就可以使用该令牌代表特定用户账号调用 Google API。为此,请在向 API 发出的请求中添加访问令牌,方法是添加 access_token 查询参数或 Authorization HTTP 标头 Bearer 值。如果可能,最好使用 HTTP 标头,因为查询字符串往往会在服务器日志中显示。在大多数情况下,您可以使用客户端库来设置对 Google API 的调用(例如,调用 Drive Files API 时)。

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

HTTP GET 示例

使用 Authorization: Bearer HTTP 标头对 drive.files 端点(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

JavaScript 示例代码

以下代码段演示了如何使用 CORS(跨域资源共享)向 Google API 发送请求。此示例不使用 JavaScript 版 Google API 客户端库。 不过,即使您不使用客户端库,该库文档中的 CORS 支持指南也可能有助于您更好地了解这些请求。

在此代码段中,access_token 变量表示您为代表授权用户发出 API 请求而获取的令牌。完整示例演示了如何将该令牌存储在浏览器的本地存储空间中,并在发出 API 请求时检索该令牌。

var xhr = new XMLHttpRequest();
xhr.open('GET',
    'https://www.googleapis.com/drive/v3/about?fields=user&' +
    'access_token=' + params['access_token']);
xhr.onreadystatechange = function (e) {
  console.log(xhr.response);
};
xhr.send(null);

完整示例

OAuth 2.0 端点

此代码示例演示了如何在不使用 JavaScript 版 Google API 客户端库的情况下,在 JavaScript 中完成 OAuth 2.0 流程。该代码用于显示一个按钮的 HTML 页面,该按钮用于尝试 API 请求。如果您点击该按钮,代码会检查网页是否已在浏览器的本地存储空间中存储 API 访问令牌。如果是,它会执行 API 请求。否则,它会启动 OAuth 2.0 流程。

对于 OAuth 2.0 流程,该页面会执行以下步骤:

  1. 它会将用户引导至 Google 的 OAuth 2.0 服务器,该服务器会请求访问 https://www.googleapis.com/auth/drive.metadata.readonly 和 https://www.googleapis.com/auth/calendar.readonly 范围。
  2. 在授予(或拒绝)对一个或多个请求范围的访问权限后,用户会被重定向到原始页面,该页面会从片段标识符字符串中解析访问令牌。
  3. 该页面会检查用户已向应用授予哪些范围的访问权限。
  4. 如果用户已授予对所请求范围的访问权限,则该页面会使用访问令牌发出示例 API 请求。

    API 请求会调用 Drive API 的 about.get 方法,以检索有关授权用户的 Google 云端硬盘账号的信息。

  5. 如果请求成功执行,API 响应会记录在浏览器的调试控制台中。

您可以通过 Google 账号的权限页面撤消对应用的访问权限。该应用会列为在创建客户端 ID 期间,OAuth 权限请求页面内的品牌页面中提供的应用名称。

如需在本地运行此代码,您需要为 YOUR_CLIENT_ID 和 YOUR_REDIRECT_URI 变量设置与您的授权凭据对应的值。YOUR_REDIRECT_URI 变量应设置为网页的实际网址。该值必须与您在 Cloud 控制台“客户端”页面中配置的 OAuth 2.0 客户端的某个已获授权的重定向 URI 完全一致。如果此值与授权 URI 不匹配,您将收到 redirect_uri_mismatch 错误。您的项目还必须为此请求启用了适当的 API。

<html><head></head><body>
<script>
  var YOUR_CLIENT_ID = 'REPLACE_THIS_VALUE';
  var YOUR_REDIRECT_URI = 'REPLACE_THIS_VALUE';

  // Parse query string to see if page request is coming from OAuth 2.0 server.
  var fragmentString = location.hash.substring(1);
  var params = {};
  var regex = /([^&=]+)=([^&]*)/g, m;
  while (m = regex.exec(fragmentString)) {
    params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
  }
  if (Object.keys(params).length > 0 && params['state']) {
    if (params['state'] == localStorage.getItem('state')) {
      localStorage.setItem('oauth2-test-params', JSON.stringify(params) );

      trySampleRequest();
    } else {
      console.log('State mismatch. Possible CSRF attack');
    }
  }

  // Function to generate a random state value
  function generateCryptoRandomState() {
    const randomValues = new Uint32Array(2);
    window.crypto.getRandomValues(randomValues);

    // Encode as UTF-8
    const utf8Encoder = new TextEncoder();
    const utf8Array = utf8Encoder.encode(
      String.fromCharCode.apply(null, randomValues)
    );

    // Base64 encode the UTF-8 data
    return btoa(String.fromCharCode.apply(null, utf8Array))
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '');
  }

  // If there's an access token, try an API request.
  // Otherwise, start OAuth 2.0 flow.
  function trySampleRequest() {
    var params = JSON.parse(localStorage.getItem('oauth2-test-params'));
    if (params && params['access_token']) { 
      // User authorized the request. Now, check which scopes were granted.
      if (params['scope'].includes('https://www.googleapis.com/auth/drive.metadata.readonly')) {
        // User authorized read-only Drive activity permission.
        // Calling the APIs, etc.
        var xhr = new XMLHttpRequest();
        xhr.open('GET',
          'https://www.googleapis.com/drive/v3/about?fields=user&' +
          'access_token=' + params['access_token']);
        xhr.onreadystatechange = function (e) {
          if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.response);
          } else if (xhr.readyState === 4 && xhr.status === 401) {
            // Token invalid, so prompt for user permission.
            oauth2SignIn();
          }
        };
        xhr.send(null);
      }
      else {
        // User didn't authorize read-only Drive activity permission.
        // Update UX and application accordingly
        console.log('User did not authorize read-only Drive activity permission.');
      }

      // Check if user authorized Calendar read permission.
      if (params['scope'].includes('https://www.googleapis.com/auth/calendar.readonly')) {
        // User authorized Calendar read permission.
        // Calling the APIs, etc.
        console.log('User authorized Calendar read permission.');
      }
      else {
        // User didn't authorize Calendar read permission.
        // Update UX and application accordingly
        console.log('User did not authorize Calendar read permission.');
      } 
    } else {
      oauth2SignIn();
    }
  }

  /*
   * Create form to request access token from Google's OAuth 2.0 server.
   */
  function oauth2SignIn() {
    // create random state value and store in local storage
    var state = generateCryptoRandomState();
    localStorage.setItem('state', state);

    // Google's OAuth 2.0 endpoint for requesting an access token
    var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';

    // Create element to open OAuth 2.0 endpoint in new window.
    var form = document.createElement('form');
    form.setAttribute('method', 'GET'); // Send as a GET request.
    form.setAttribute('action', oauth2Endpoint);

    // Parameters to pass to OAuth 2.0 endpoint.
    var params = {'client_id': YOUR_CLIENT_ID,
                  'redirect_uri': YOUR_REDIRECT_URI,
                  'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly',
                  'state': state,
                  'include_granted_scopes': 'true',
                  'response_type': 'token'};

    // Add form parameters as hidden input values.
    for (var p in params) {
      var input = document.createElement('input');
      input.setAttribute('type', 'hidden');
      input.setAttribute('name', p);
      input.setAttribute('value', params[p]);
      form.appendChild(input);
    }

    // Add form to page and submit it to open the OAuth 2.0 endpoint.
    document.body.appendChild(form);
    form.submit();
  }
</script>

<button onclick="trySampleRequest();">Try sample request</button>
</body></html>

JavaScript 来源验证规则

Google 会对 JavaScript 来源应用以下验证规则,以帮助开发者确保其应用的安全。您的 JavaScript 来源必须遵守这些规则。 如需了解这些规则中使用的网域、主机和架构的定义,请参阅 RFC 3986 第 3 节。

验证规则
Scheme

JavaScript 来源必须使用 HTTPS 架构,而不是纯 HTTP。Localhost URI(包括 localhost IP 地址 URI)不受此规则约束。

主机

主机不能是原始 IP 地址。本地主机 IP 地址不受此规则约束。

网域
  • 主机 TLD(顶级域名)必须属于公共后缀列表。
  • 主机网域不能是 “googleusercontent.com”。
  • JavaScript 来源不能包含网址缩短服务网域(例如 goo.gl),除非应用拥有该网域。
  • Userinfo

    JavaScript 来源不能包含 userinfo 子组件。

    路径

    JavaScript 来源不能包含路径组件。

    查询

    JavaScript 来源不能包含查询组件。

    fragment

    JavaScript 来源不能包含片段组件。

    字符数 JavaScript 来源不能包含某些字符,包括:
    • 通配符 ('*')
    • 不可打印的 ASCII 字符
    • 百分号编码无效(任何不遵循网址编码形式的百分号编码,即百分号后跟两个十六进制数字)
    • 空字符(编码的 NULL 字符,例如 %00、%C0%80)

    增量授权

    在 OAuth 2.0 协议中,您的应用会请求访问资源的授权,这些资源由范围标识。最佳用户体验做法是在需要时请求资源授权。为了实现这种做法,Google 的授权服务器支持增量授权。借助此功能,您可以根据需要请求范围,如果用户授予新范围的权限,则会返回一个授权代码,该代码可用于换取包含用户已授予项目的所有范围的令牌。

    例如,如果一款应用可让用户试听音乐曲目并创建混音,那么在登录时可能只需要极少的资源,或许只需要登录用户的姓名。不过,保存完成的混音需要访问其 Google 云端硬盘。大多数用户会认为,只有在应用实际需要访问其 Google 云端硬盘时才请求访问权限,这才是自然的做法。

    在这种情况下,应用可能会在登录时请求 openid 和 profile 范围来执行基本登录,然后在首次请求保存混音时请求 https://www.googleapis.com/auth/drive.file 范围。

    以下规则适用于通过增量授权获得的访问令牌:

    • 该令牌可用于访问与新合并授权中包含的任何范围相对应的资源。
    • 当您使用组合授权的刷新令牌获取访问令牌时,该访问令牌代表组合授权,可用于响应中包含的任何 scope 值。
    • 合并后的授权包括用户向 API 项目授予的所有范围,即使这些授权是从不同的客户端请求的。例如,如果用户使用应用的桌面客户端向一个范围授予了访问权限,然后通过移动客户端向同一应用授予了另一个范围,则合并后的授权将包含这两个范围。
    • 如果您撤消代表组合授权的令牌,则代表关联用户对该授权的所有范围的访问权限也会同时撤消。

    以下代码示例展示了如何向现有访问令牌添加范围。此方法可让您的应用避免管理多个访问令牌。

    OAuth 2.0 端点

    如需向现有访问令牌添加范围,请在向 Google 的 OAuth 2.0 服务器发出的请求中添加 include_granted_scopes 参数。

    以下代码段演示了如何执行此操作。该代码段假定您已将访问令牌有效的范围存储在浏览器的本地存储空间中。(已完成示例代码通过在浏览器的本地存储空间中设置 oauth2-test-params.scope 属性,存储访问令牌有效的范围列表。)

    该代码段会将访问令牌有效的范围与您要用于特定查询的范围进行比较。如果访问令牌不涵盖该范围,则会启动 OAuth 2.0 流程。 此处的 oauth2SignIn 函数与第 2 步中提供的函数相同(稍后在完整示例中也会提供)。

    var SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly';
    var params = JSON.parse(localStorage.getItem('oauth2-test-params'));
    
    var current_scope_granted = false;
    if (params.hasOwnProperty('scope')) {
      var scopes = params['scope'].split(' ');
      for (var s = 0; s < scopes.length; s++) {
        if (SCOPE == scopes[s]) {
          current_scope_granted = true;
        }
      }
    }
    
    if (!current_scope_granted) {
      oauth2SignIn(); // This function is defined elsewhere in this document.
    } else {
      // Since you already have access, you can proceed with the API request.
    }

    令牌撤消

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

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

    OAuth 2.0 端点

    如需以编程方式撤消令牌,您的应用需要向 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 以及错误代码。

    以下 JavaScript 代码段展示了如何在不使用 Google APIs Client Library for JavaScript 的情况下,在 JavaScript 中撤消令牌。由于 Google 用于撤消令牌的 OAuth 2.0 端点不支持跨域资源共享 (CORS),因此代码会创建一个表单并将该表单提交到端点,而不是使用 XMLHttpRequest() 方法发布请求。

    function revokeAccess(accessToken) {
      // Google's OAuth 2.0 endpoint for revoking access tokens.
      var revokeTokenEndpoint = 'https://oauth2.googleapis.com/revoke';
    
      // Create <form> element to use to POST data to the OAuth 2.0 endpoint.
      var form = document.createElement('form');
      form.setAttribute('method', 'post');
      form.setAttribute('action', revokeTokenEndpoint);
    
      // Add access token to the form so it is set as value of 'token' parameter.
      // This corresponds to the sample curl request, where the URL is:
      //      https://oauth2.googleapis.com/revoke?token={token}
      var tokenField = document.createElement('input');
      tokenField.setAttribute('type', 'hidden');
      tokenField.setAttribute('name', 'token');
      tokenField.setAttribute('value', accessToken);
      form.appendChild(tokenField);
    
      // Add form to page and submit it to actually revoke the token.
      document.body.appendChild(form);
      form.submit();
    }

    实施跨账号保护

    为了保护用户账号,您还应采取一项额外措施,即利用 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

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