用于客户端 Web 应用程序的 OAuth 2.0

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

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

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

先决条件

为您的项目启用 API

任何调用 Google API 的应用程序都需要在 API Console.xml 文件中启用这些 API。

要为您的项目启用 API:

  1. Google API Console 中的 Open the API Library
  2. If prompted, select a project, or create a new one.
  3. API Library 列出了所有可用的 API,按产品系列和流行程度分组。如果您要启用的 API 在列表中不可见,请使用搜索来查找它,或单击它所属的产品系列中的查看全部
  4. 选择要启用的 API,然后单击启用按钮。
  5. If prompted, enable billing.
  6. If prompted, read and accept the API's Terms of Service.

创建授权凭证

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

  1. Go to the Credentials page.
  2. 单击创建凭据 > OAuth 客户端 ID
  3. 选择Web 应用程序应用程序类型。
  4. 完成表格。使用 JavaScript 发出授权 Google API 请求的应用程序必须指定授权的JavaScript 来源。源标识您的应用程序可以向 OAuth 2.0 服务器发送请求的域。这些来源必须遵守Google 的验证规则

确定访问范围

范围使您的应用程序能够仅请求访问它需要的资源,同时还使用户能够控制他们授予您的应用程序的访问权限。因此,请求的范围数量与获得用户同意的可能性之间可能存在反比关系。

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

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

获取 OAuth 2.0 访问令牌

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

第一步:配置客户端对象

如果您使用适用于 JavaScript 的 Google API 客户端库来处理 OAuth 2.0 流程,您的第一步是配置gapi.auth2gapi.client对象。这些对象使您的应用程序能够获得用户授权并发出授权的 API 请求。

客户端对象标识您的应用程序请求访问权限的范围。这些值通知 Google 向用户显示的同意屏幕。

JS 客户端库

JavaScript 客户端库简化了授权过程的许多方面:

  1. 它为 Google 的授权服务器创建重定向 URL,并提供一种将用户定向到该 URL 的方法。
  2. 它处理从该服务器重定向回您的应用程序。
  3. 它验证授权服务器返回的访问令牌。
  4. 它存储授权服务器发送给您的应用程序的访问令牌,并在您的应用程序随后进行授权 API 调用时检索它。

下面的代码片段摘自本文档后面显示的完整示例。此代码初始化gapi.client对象,您的应用程序稍后将使用该对象进行 API 调用。创建该对象时,您的应用程序用来检查和监控用户授权状态的gapi.auth2对象也会被初始化。

gapi.client.init的调用指定了以下字段:

  • apiKeyclientId值指定您的应用程序的授权凭据。如创建授权凭证部分所述,这些值可以在 API Console 中获得。请注意,如果您的应用程序发出授权的 API 请求,则需要clientId 。仅发出未经授权的请求的应用程序只能指定 API 密钥。
  • scope字段指定了一个以空格分隔的访问范围列表,这些范围对应于您的应用程序可以代表用户访问的资源。这些值通知 Google 向用户显示的同意屏幕。

    我们建议您的应用程序尽可能请求访问上下文中的授权范围。通过在上下文中请求访问用户数据,通过增量授权,您可以帮助用户更轻松地了解您的应用程序为什么需要它所请求的访问权限。

  • discoveryDocs字段标识您的应用程序使用的API Discovery 文档列表。 Discovery 文档描述了 API 的表面,包括其资源模式,JavaScript 客户端库使用该信息来生成应用程序可以使用的方法。在此示例中,代码检索 Google Drive API 版本 3 的发现文档。

gapi.client.init调用完成后,代码设置GoogleAuth变量来标识 Google Auth 对象。最后,代码设置了一个监听器,当用户的登录状态发生变化时调用函数。 (该函数未在代码段中定义。)

var GoogleAuth; // Google Auth object.
function initClient() {
  gapi.client.init({
      'apiKey': 'YOUR_API_KEY',
      'clientId': 'YOUR_CLIENT_ID',
      'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly',
      'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']
  }).then(function () {
      GoogleAuth = gapi.auth2.getAuthInstance();

      // Listen for sign-in state changes.
      GoogleAuth.isSignedIn.listen(updateSigninStatus);
  });
}

OAuth 2.0 端点

如果您直接访问 OAuth 2.0 端点,则可以继续下一步。

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

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

JS 客户端库

调用GoogleAuth.signIn()方法将用户定向到 Google 的授权服务器。

GoogleAuth.signIn();

实际上,您的应用程序可能会设置一个布尔值来确定是否在尝试进行 API 调用之前调用signIn()方法。

下面的代码片段演示了如何启动用户授权流程。请注意有关代码段的以下几点:

  • 代码中引用的GoogleAuth对象与步骤 1 中代码片段中定义的全局变量相同。

  • updateSigninStatus函数是一个侦听器,用于侦听用户授权状态的更改。它作为侦听器的角色也在步骤 1 的代码片段中定义:
    GoogleAuth.isSignedIn.listen(updateSigninStatus);
  • 该代码段定义了两个额外的全局变量:

    • isAuthorized是一个布尔变量,指示用户是否已经登录。可以在应用加载时设置此值,如果用户登录或退出应用,则可以更新此值。

      在此代码段中, sendAuthorizedApiRequest函数检查变量的值以确定应用程序是应该尝试需要授权的 API 请求还是提示用户对应用程序进行授权。

    • currentApiRequest是一个对象,用于存储有关用户尝试的最后一个 API 请求的详细信息。该对象的值在应用程序调用sendAuthorizedApiRequest函数时设置。

      如果用户已授权该应用程序,则会立即执行该请求。否则,该函数会将用户重定向到登录。用户登录后, updateSignInStatus函数调用sendAuthorizedApiRequest ,传入授权流开始之前尝试的相同请求。

var isAuthorized;
var currentApiRequest;

/**
 * Store the request details. Then check to determine whether the user
 * has authorized the application.
 *   - If the user has granted access, make the API request.
 *   - If the user has not granted access, initiate the sign-in flow.
 */
function sendAuthorizedApiRequest(requestDetails) {
  currentApiRequest = requestDetails;
  if (isAuthorized) {
    // Make API request
    // gapi.client.request(requestDetails)

    // Reset currentApiRequest variable.
    currentApiRequest = {};
  } else {
    GoogleAuth.signIn();
  }
}

/**
 * Listener called when user completes auth flow. If the currentApiRequest
 * variable is set, then the user was prompted to authorize the application
 * before the request executed. In that case, proceed with that API request.
 */
function updateSigninStatus(isSignedIn) {
  if (isSignedIn) {
    isAuthorized = true;
    if (currentApiRequest) {
      sendAuthorizedApiRequest(currentApiRequest);
    }
  } else {
    isAuthorized = false;
  }
}

OAuth 2.0 端点

生成一个 URL 以请求从 Google 的 OAuth 2.0 端点访问https://accounts.google.com/o/oauth2/v2/auth 。此端点可通过 HTTPS 访问;拒绝普通的 HTTP 连接。

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

参数
client_id必需的

您的应用程序的客户端 ID。您可以在 API Console Credentials page 中找到该值。

redirect_uri必需的

确定用户完成授权流程后 API 服务器将用户重定向到何处。该值必须与您在客户端的 API Console Credentials page 中配置的 OAuth 2.0 客户端的授权重定向 URI 之一完全匹配。如果此值与提供的client_id的授权重定向 URI 不匹配,您将收到redirect_uri_mismatch错误。

请注意, httphttps方案、大小写和尾部斜杠 (' / ') 必须全部匹配。

response_type必需的

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

scope必需的

一个以空格分隔的范围列表,用于标识您的应用程序可以代表用户访问的资源。这些值通知 Google 向用户显示的同意屏幕。

范围使您的应用程序能够仅请求访问它需要的资源,同时还使用户能够控制他们授予您的应用程序的访问权限。因此,请求的范围数量与获得用户同意的可能性之间存在反比关系。

我们建议您的应用程序尽可能请求访问上下文中的授权范围。通过在上下文中请求访问用户数据,通过增量授权,您可以帮助用户更轻松地了解您的应用程序为什么需要它所请求的访问权限。

state受到推崇的

指定您的应用程序用于维护授权请求和授权服务器响应之间的状态的任何字符串值。在用户同意或拒绝您的应用程序的访问请求后,服务器会返回您在redirect_uri的 URL 片段标识符 ( # ) 中作为name=value对发送的确切值。

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

include_granted_scopes可选的

使应用程序能够使用增量授权来请求访问上下文中的其他范围。如果您将此参数的值设置为true并授予授权请求,则新的访问令牌还将涵盖用户先前授予应用程序访问权限的任何范围。有关示例,请参阅增量授权部分。

login_hint可选的

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

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

prompt可选的

以空格分隔、区分大小写的提示列表,以呈现给用户。如果您不指定此参数,则仅在您的项目第一次请求访问时才会提示用户。有关更多信息,请参阅提示重新同意

可能的值为:

none不显示任何身份验证或同意屏幕。不得与其他值一起指定。
consent提示用户同意。
select_account提示用户选择一个帐户。

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

下面显示了一个示例 URL,为了便于阅读,带有换行符和空格。

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

创建请求 URL 后,将用户重定向到它。

JavaScript 示例代码

以下 JavaScript 代码段展示了如何在不使用 JavaScript 的 Google API 客户端库的情况下在 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',
                '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();
}

第 3 步: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 政策不允许的嵌入式用户代理中。

安卓

Android 开发人员在android.webkit.WebView打开授权请求时可能会遇到此错误消息。开发人员应该改用 Android 库,例如Google Sign-In for Android或 OpenID Foundation 的AppAuth for Android

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

IOS

iOS 和 macOS 开发人员在WKWebView打开授权请求时可能会遇到此错误。开发人员应改为使用 iOS 库,例如适用于 iOS 的 Google Sign-In适用于 iOS 的OpenID Foundation 的AppAuth

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

org_internal

请求中的 OAuth 客户端 ID 是限制对特定Google Cloud Organization 中的Google 帐户的访问的项目的一部分。有关此配置选项的更多信息,请参阅设置 OAuth 同意屏幕帮助文章中的用户类型部分。

origin_mismatch

发起授权请求的 JavaScript 的方案、域和/或端口可能与为 OAuth 客户端 ID 注册的授权 JavaScript 源 URI 不匹配。查看 Google API Console Credentials page 中授权的 JavaScript 来源。

redirect_uri_mismatch

redirect_uri在授权请求传递不匹配的OAuth用户端ID被授权的重定向URI。查看 Google API Console Credentials page 中的授权重定向 URI。

发起授权请求的 JavaScript 的方案、域和/或端口可能与为 OAuth 客户端 ID 注册的授权 JavaScript 源 URI 不匹配。查看 Google API Console Credentials page 中授权的 JavaScript 来源。

步骤 4:处理 OAuth 2.0 服务器响应

JS 客户端库

JavaScript 客户端库处理来自 Google 授权服务器的响应。如果您设置侦听器来监视当前用户登录状态的更改,则在用户授予对应用程序的请求访问权限时调用该函数。

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 服务器响应示例

您可以通过单击以下示例 URL 来测试此流程,该示例 URL 请求只读访问权限以查看 Google Drive 中文件的元数据:

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

完成 OAuth 2.0 流程后,您将被重定向到http://localhost/oauth2callback 。该 URL 将产生404 NOT FOUND错误,除非您的本地机器恰好在该地址提供文件。下一步提供有关当用户重定向回您的应用程序时 URI 中返回的信息的更多详细信息。

调用 Google API

JS 客户端库

在您的应用程序获得访问令牌后,您可以使用 JavaScript 客户端库代表用户发出 API 请求。客户端库为您管理访问令牌,您无需执行任何特殊操作即可在请求中发送它。

客户端库支持两种调用 API 方法的方式。如果您加载了发现文档,API 将为您定义特定于方法的函数。您还可以使用gapi.client.request函数来调用 API 方法。以下两个片段演示了 Drive API 的about.get方法的这些选项。

// Example 1: Use method-specific function
var request = gapi.client.drive.about.get({'fields': 'user'});

// Execute the API request.
request.execute(function(response) {
  console.log(response);
});


// Example 2: Use gapi.client.request(args) function
var request = gapi.client.request({
  'method': 'GET',
  'path': '/drive/v3/about',
  'params': {'fields': 'user'}
});
// Execute the API request.
request.execute(function(response) {
  console.log(response);
});

OAuth 2.0 端点

在您的应用程序获得访问令牌后,如果已授予 API 所需的访问范围,您可以使用该令牌代表给定的用户帐户调用 Google API。为此,请通过包含access_token查询参数或Authorization HTTP 标头Bearer值在对 API 的请求中包含访问令牌。如果可能,最好使用 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);

完整示例

JS 客户端库

示例代码演示

本节包含以下代码示例的工作演示,以演示代码在实际应用程序中的行为。在您授权该应用程序后,它将列在与您的 Google 帐户相关联应用程序中。该应用程序名为OAuth 2.0 Demo for Google API Docs 。同样,如果您撤销访问权限并刷新该页面,则该应用程序将不再列出。

请注意,此应用请求访问https://www.googleapis.com/auth/drive.metadata.readonly范围。请求访问权限仅用于演示如何在 JavaScript 应用程序中启动 OAuth 2.0 流程。此应用程序不会发出任何 API 请求。

JavaScript 示例代码

如上所示,此代码示例适用于加载适用于 JavaScript 的 Google API 客户端库并启动 OAuth 2.0 流程的页面(应用程序)。该页面显示:

  • 一个让用户登录应用程序的按钮。如果用户之前未授权应用程序,则应用程序将启动 OAuth 2.0 流程。
  • 两个按钮,允许用户退出应用程序或撤销先前授予应用程序的访问权限。如果您退出应用程序,则您并未撤销授予该应用程序的访问权限。您需要重新登录,然后该应用才能代表您发出其他授权请求,但您下次使用该应用时无需再次授予访问权限。但是,如果您撤消访问权限,则确实需要再次授予访问权限。

您还可以通过 Google 帐户的“权限”页面撤销对该应用的访问权限。该应用程序被列为OAuth 2.0 Demo for Google API Docs

<script>
  var GoogleAuth;
  var SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly';
  function handleClientLoad() {
    // Load the API's client and auth2 modules.
    // Call the initClient function after the modules load.
    gapi.load('client:auth2', initClient);
  }

  function initClient() {
    // In practice, your app can retrieve one or more discovery documents.
    var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';

    // Initialize the gapi.client object, which app uses to make API requests.
    // Get API key and client ID from API Console.
    // 'scope' field specifies space-delimited list of access scopes.
    gapi.client.init({
        'apiKey': 'YOUR_API_KEY',
        'clientId': 'YOUR_CLIENT_ID',
        'discoveryDocs': [discoveryUrl],
        'scope': SCOPE
    }).then(function () {
      GoogleAuth = gapi.auth2.getAuthInstance();

      // Listen for sign-in state changes.
      GoogleAuth.isSignedIn.listen(updateSigninStatus);

      // Handle initial sign-in state. (Determine if user is already signed in.)
      var user = GoogleAuth.currentUser.get();
      setSigninStatus();

      // Call handleAuthClick function when user clicks on
      //      "Sign In/Authorize" button.
      $('#sign-in-or-out-button').click(function() {
        handleAuthClick();
      });
      $('#revoke-access-button').click(function() {
        revokeAccess();
      });
    });
  }

  function handleAuthClick() {
    if (GoogleAuth.isSignedIn.get()) {
      // User is authorized and has clicked "Sign out" button.
      GoogleAuth.signOut();
    } else {
      // User is not signed in. Start Google auth flow.
      GoogleAuth.signIn();
    }
  }

  function revokeAccess() {
    GoogleAuth.disconnect();
  }

  function setSigninStatus() {
    var user = GoogleAuth.currentUser.get();
    var isAuthorized = user.hasGrantedScopes(SCOPE);
    if (isAuthorized) {
      $('#sign-in-or-out-button').html('Sign out');
      $('#revoke-access-button').css('display', 'inline-block');
      $('#auth-status').html('You are currently signed in and have granted ' +
          'access to this app.');
    } else {
      $('#sign-in-or-out-button').html('Sign In/Authorize');
      $('#revoke-access-button').css('display', 'none');
      $('#auth-status').html('You have not authorized this app or you are ' +
          'signed out.');
    }
  }

  function updateSigninStatus() {
    setSigninStatus();
  }
</script>

<button id="sign-in-or-out-button"
        style="margin-left: 25px">Sign In/Authorize</button>
<button id="revoke-access-button"
        style="display: none; margin-left: 25px">Revoke access</button>

<div id="auth-status" style="display: inline; padding-left: 25px"></div><hr>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
        onload="this.onload=function(){};handleClientLoad()"
        onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>

OAuth 2.0 端点

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

对于 OAuth 2.0 流程,页面遵循以下步骤:

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

    API 请求调用 Drive API 的about.get方法来检索有关授权用户的 Google Drive 帐户的信息。

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

您可以通过 Google 帐户的“权限”页面撤销对该应用的访问权限。该应用程序将列为OAuth 2.0 Demo for Google API Docs

要在本地运行此代码,您需要为与您的授权凭证对应的YOUR_CLIENT_IDYOUR_REDIRECT_URI变量设置值。 YOUR_REDIRECT_URI变量应设置为提供页面的相同 URL。该值必须与您在 API Console Credentials page 中配置的 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';
  var fragmentString = location.hash.substring(1);

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

  // 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']) {
      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 {
      oauth2SignIn();
    }
  }

  /*
   * Create form to request access token from Google's OAuth 2.0 server.
   */
  function oauth2SignIn() {
    // 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',
                  'state': 'try_sample_request',
                  '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 节,如下所述。

验证规则
方案

URI 必须使用 HTTPS 方案,而不是普通的 HTTP。本地主机 URI(包括本地主机 IP 地址 URI)不受此规则约束。

主持人

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

领域
  • 主机 TLD(顶级域)必须属于公共后缀列表
  • 主机域不能是“googleusercontent.com”
  • 除非应用拥有该域,否则 URI 不能包含 URL 缩短器域(例如goo.gl )。
  • 人物URI 不能包含某些字符,包括:
    • 通配符 ( '*' )
    • 不可打印的 ASCII 字符
    • 无效的百分比编码(任何不遵循 URL 编码形式的百分比符号后跟两个十六进制数字的百分比编码)
    • 空字符(编码的空字符,例如%00%C0%80

    增量授权

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

    例如,一个让人们采样音乐曲目和创建混音的应用程序在登录时可能只需要很少的资源,可能只需要登录人的姓名。但是,保存完整的混音需要访问他们的 Google Drive .大多数人会发现,如果他们只在应用程序实际需要时才被要求访问他们的 Google 云端硬盘,那么这很自然。

    在这种情况下,在登录时应用程序可能会请求openidprofile范围来执行基本登录,然后在登录时请求https://www.googleapis.com/auth/drive.file范围第一个保存混音的请求。

    以下规则适用于从增量授权中获取的访问令牌:

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

    下面的代码示例展示了如何向现有访问令牌添加范围。这种方法使您的应用程序无需管理多个访问令牌。

    JS 客户端库

    要将范围添加到现有访问令牌,请调用GoogleUser.grant(options)方法。 options对象标识您要授予访问权限的其他范围。

    // Space-separated list of additional scope(s) you are requesting access to.
    // This code adds read-only access to the user's calendars via the Calendar API.
    var NEW_SCOPES = 'https://www.googleapis.com/auth/calendar.readonly';
    
    // Retrieve the GoogleUser object for the current user.
    var GoogleUser = GoogleAuth.currentUser.get();
    GoogleUser.grant({'scope': NEW_SCOPES});

    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 请求,以确保先前授予应用程序的权限被删除。

    JS 客户端库

    要以编程方式撤销令牌,请调用GoogleAuth.disconnect()

    GoogleAuth.disconnect();

    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}

    令牌可以是访问令牌或刷新令牌。如果令牌是访问令牌并且它有相应的刷新令牌,则刷新令牌也将被撤销。

    If the revocation is successfully processed, then the HTTP status code of the response is 200 . For error conditions, an HTTP status code 400 is returned along with an error code.

    The following JavaScript snippet shows how to revoke a token in JavaScript without using the Google APIs Client Library for JavaScript. Since the Google's OAuth 2.0 endpoint for revoking tokens does not support Cross-origin Resource Sharing (CORS), the code creates a form and submits the form to the endpoint rather than using the XMLHttpRequest() method to post the request.

    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();
    }