适用于客户端 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 请求。

前提条件

为您的项目启用 API

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

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

  1. Open the API Library (在 Google API Console中)。
  2. If prompted, select a project, or create a new one.
  3. API Library 列出了所有可用的 API,按产品系列和热门程度分组。如果列表中没有显示您要启用的 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 范围文档包含可用于访问 Google API 的完整范围列表。

获取 OAuth 2.0 访问令牌

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

第 1 步:配置客户端对象

如果您使用 JavaScript 版 Google API 客户端库处理 OAuth 2.0 流程,则首先要配置 gapi.auth2gapi.client 对象。这些对象使您的应用能够获得用户授权并发出已获授权的 API 请求。

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

JS 客户端库

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

  1. 它会为 Google 的授权服务器创建重定向网址,并提供一个将用户定向到该网址的方法。
  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 端点

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

对于网络服务器应用,Google 授权服务器支持以下查询字符串参数:

参数
client_id 必需

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

redirect_uri 必需

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

请注意,httphttps 架构、大小写和结尾斜杠(“/”)必须全部匹配。

response_type 必需

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

scope 必需

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

通过范围,您的应用可以仅请求访问所需的资源,同时还可以控制用户向应用授予的访问权限大小。因此,请求的范围数与征得用户同意的可能性之间存在反向关系。

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

state 推荐

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

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

include_granted_scopes 可选

允许应用使用增量授权在上下文中请求访问其他范围。如果您将此参数的值设为 true 且已授予授权请求,则新的访问令牌也将覆盖用户之前授予应用访问权限的所有范围。有关示例,请参阅增量授权部分。

login_hint 可选

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

将参数值设为电子邮件地址或 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&
 include_granted_scopes=true&
 response_type=token&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//oauth2.example.com/code&
 client_id=client_id

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

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 开发者在 android.webkit.WebView 中打开授权请求时可能会遇到此错误消息。开发者应改用 Android 库,如 Google 登录 Android 版或 OpenID Foundation 的 AppAuth for Android

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

iOS

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

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

org_internal

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

origin_mismatch

发起授权请求的 JavaScript 的架构、网域和/或端口可能与为 OAuth 客户端 ID 注册的授权 JavaScript 来源 URI 不一致。查看 Google API ConsoleCredentials 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 参数之外,Fragment 字符串还包含 token_type 参数(始终设置为 Bearer)和 expires_in 参数(用于指定令牌的生命周期)(以秒为单位)。如果在访问令牌请求中指定了 state 参数,则其值也会包含在响应中。

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

OAuth 2.0 服务器响应示例

您可以点击以下示例网址(请求以只读权限查看您 Google 云端硬盘中文件的元数据)测试此流程:

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。除非本地计算机恰好在该地址提供文件,否则该网址会产生 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 端点

在您的应用获得访问令牌后,您可以使用该令牌代表指定用户帐号调用 Google API(如果已授予 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);

完整示例

JS 客户端库

示例代码演示

本部分包含代码示例的有效演示,接下来的演示内容是:该代码在实际应用中的行为方式。您向该应用授权后,它就会列在与您的 Google 帐号相关联的应用中。该应用名为 Google API 文档的 OAuth 2.0 演示。同样,如果您撤消访问权限并刷新该网页,该应用将不再列出。

请注意,此应用请求访问 https://www.googleapis.com/auth/drive.metadata.readonly 范围。申请访问权限只是为了演示如何在 JavaScript 应用中启动 OAuth 2.0 流程。此应用未发出任何 API 请求。

JavaScript 示例代码

如上所示,此代码示例适用于加载 JavaScript 版 Google API 客户端库并启动 OAuth 2.0 流程的网页(应用)。该页面会显示以下任一内容:

  • 一个可让用户登录应用的按钮。如果用户之前未向应用授权,则应用会启动 OAuth 2.0 流程。
  • 两个可让用户退出应用或撤消之前授予应用的访问权限的按钮。如果您退出应用,您尚未撤消授予应用的访问权限。您需要重新登录,应用才能代表您发出其他已获授权的请求,但下次使用该应用时,您无需再次授予访问权限。不过,如果您撤消访问权限,则需要再次授予访问权限。

您还可以通过 Google 帐号的权限页面撤消对应用的访问权限。该应用被列为 Google API 文档的 OAuth 2.0 演示

<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 流程。该代码适用于 HTML 网页,其中显示了一个试用 API 请求的按钮。点击该按钮后,代码将检查该网页是否已在浏览器的本地存储空间中存储了 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 云端硬盘帐号的信息。

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

您可以通过 Google 帐号的权限页面撤消对该应用的访问权限。该应用将列为 Google API 文档的 OAuth 2.0 演示版

如需在本地运行此代码,您需要为与授权凭据对应的 YOUR_CLIENT_IDYOUR_REDIRECT_URI 变量设置值。YOUR_REDIRECT_URI 变量应设置为相应网页的投放网址。该值必须与您在 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 节

验证规则
架构

JavaScript 来源必须使用 HTTPS 架构,而不是纯 HTTP 架构。本地主机 URI(包括 localhost IP 地址 URI)不受此规则限制。

主机

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

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

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

    路径

    JavaScript 源不能包含路径组件。

    查询

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

    fragment

    JavaScript 源不能包含 fragment 组件。

    字符 JavaScript 源不能包含以下特定字符:
    • 通配符 ('*')
    • 不可打印的 ASCII 字符
    • 百分比编码无效(任何百分号编码都不遵循百分号后接两位十六进制数字的网址编码形式)
    • Null 字符(经过编码的 NULL 字符,例如%00%C0%80

    增量授权

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

    例如,如果应用支持用户试听曲目和创建合辑,在登录时可能需要的资源可能很少,可能只有登录用户的名字。但是,保存已完成的合辑需要访问其 Google 云端硬盘。多数人会在应用确实需要的时候访问他们的 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}

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

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

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