使用代码模型

借助 Google Identity 服务库,用户可以使用基于浏览器的弹出式窗口或重定向用户体验流程向 Google 请求授权代码。这会启动安全的 OAuth 2.0 流程,并生成一个访问令牌,用于代表用户调用 Google API。

OAuth 2.0 授权代码流程摘要:

  • Google 帐号所有者在浏览器中通过按钮点击等手势向 Google 请求授权代码。
  • Google 会做出响应,会向用户浏览器中运行的 JavaScript Web 应用中的回调函数发送唯一的授权代码,或者使用浏览器重定向直接调用您的授权代码端点。
  • 您的后端平台托管授权代码端点并接收该代码。验证完成后,系统会使用向 Google 令牌端点发出的请求来交换每个用户的访问权限和刷新令牌。
  • Google 会验证授权代码,确认来自您的安全平台的请求,颁发访问令牌和刷新令牌,并通过调用您的平台托管的登录端点返回令牌。
  • 您的登录端点会接收访问令牌和刷新令牌,从而安全地存储刷新令牌以供日后使用。

初始化代码客户端

google.accounts.oauth2.initCodeClient() 方法可初始化代码客户端。

您可以选择使用重定向弹出式模式用户流来共享身份验证代码。使用重定向模式时,您会在自己的服务器上托管 OAuth2 授权端点,Google 会将用户代理重定向到此端点,并以网址参数的形式共享身份验证代码。对于弹出模式,您可以定义一个 JavaScript 回调方法来将授权代码发送到您的服务器。弹出式窗口模式可用于提供顺畅的用户体验,而无需访问者离开您的网站。

如需初始化客户端以执行以下操作:

  • 重定向用户体验流程,将 ux_mode 设置为 redirect,并将 redirect_uri 的值设置为平台的授权代码端点。该值必须与您在 API 控制台中配置的 OAuth 2.0 客户端的某个已授权重定向 URI 完全匹配。它还必须符合我们的重定向 URI 验证规则

  • 弹出用户体验流程,将 ux_mode 设置为 popup,并将 callback 的值设置为将用于向平台发送授权代码的函数的名称。

防止 CSRF 攻击

为帮助防止跨站请求伪造 (CSRF) 攻击,我们为重定向和弹出模式用户体验流程采用了略有不同的技术。对于重定向模式,系统会使用 OAuth 2.0 state 参数。如需详细了解如何生成和验证 state 参数,请参阅 RFC6749 第 10.12 节:跨站请求伪造。在弹出模式下,您可以向请求添加自定义 HTTP 标头,然后在服务器上确认其是否与预期值和来源匹配。

选择一种用户体验模式,以查看显示身份验证代码和 CSRF 处理方式的代码段:

重定向模式

初始化客户端,Google 会将用户的浏览器重定向到您的身份验证端点,并以网址参数的形式共享身份验证代码。

const client = google.accounts.oauth2.initCodeClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  ux_mode: 'redirect',
  redirect_uri: "https://your.domain/code_callback_endpoint",
  state: "YOUR_BINDING_VALUE"
});

初始化客户端,用户的浏览器从 Google 接收身份验证代码并将其发送到您的服务器。

const client = google.accounts.oauth2.initCodeClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  ux_mode: 'popup',
  callback: (response) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', code_receiver_uri, true);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    // Set custom header for CRSF
    xhr.setRequestHeader('X-Requested-With', 'XmlHttpRequest');
    xhr.onload = function() {
      console.log('Auth code response: ' + xhr.responseText);
    };
    xhr.send('code=' + response.code);
  },
});

触发 OAuth 2.0 代码流程

调用代码客户端的 requestCode() 方法以触发用户流:

<button onclick="client.requestCode();">Authorize with Google</button>

这样一来,用户必须先登录 Google 帐号并同意共享各个范围,然后才能将授权代码返回到重定向端点或回调处理程序。

授权代码处理

Google 会为每位用户生成一个唯一的授权代码,您将在后端服务器上接收并验证该代码。

对于弹出模式,在用户浏览器中运行的 callback 指定的处理程序会将授权代码中继到由您的平台托管的端点。

对于重定向模式,系统会将 GET 请求发送到 redirect_url 指定的端点,并共享网址 code 参数中的授权代码。如需接收授权代码,请执行以下操作:

  • 创建一个新的授权端点(如果您尚无实现);或者

  • 更新您的现有端点以接受 GET 请求和网址参数。之前使用的是 PUT 请求,且在载荷中包含授权代码值。

授权端点

您的授权代码端点必须处理包含以下网址查询字符串参数的 GET 请求:

名称
授权用户 请求进行用户登录身份验证
验证码 Google 生成的 OAuth2 授权代码
高清 用户账号的托管域
提示符 用户意见征求对话框
范围 要授权的一个或多个 OAuth2 范围的列表(以空格分隔)
state CRSF 状态变量

使用网址参数向名为 auth-code 且由 example.com 托管的端点发送的示例 GET 请求:

Request URL: https://www.example.com/auth-code?state=42a7bd822fe32cc56&code=4/0AX4XfWiAvnXLqxlckFUVao8j0zvZUJ06AMgr-n0vSPotHWcn9p-zHCjqwr47KHS_vDvu8w&scope=email%20profile%20https://www.googleapis.com/auth/calendar.readonly%20https://www.googleapis.com/auth/photoslibrary.readonly%20https://www.googleapis.com/auth/contacts.readonly%20openid%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile&authuser=0&hd=example.com&prompt=consent

如果授权代码流程由较早的 JavaScript 库启动,或直接调用 Google OAuth 2.0 端点,则会使用 POST 请求。

POST 请求示例:在 HTTP 请求正文中包含授权代码作为载荷:

Request URL: https://www.example.com/auth-code
Request Payload: 4/0AX4XfWhll-BMV82wi4YwbrSaTPaRpUGpKqJ4zBxQldU\_70cnIdh-GJOBZlyHU3MNcz4qaw

验证请求

在服务器上执行以下操作,帮助避免 CSRF 攻击。

针对重定向模式,检查 state 参数的值。

确认 X-Requested-With: XmlHttpRequest 标头已设置为弹出式模式。

然后,仅当您首先成功验证了身份验证代码请求后,才应继续从 Google 获取刷新令牌和访问令牌。

获取访问令牌和刷新令牌

在您的后端平台收到来自 Google 的授权代码并验证请求后,使用该身份验证代码从 Google 获取访问令牌和刷新令牌,以便进行 API 调用。

按照为 Web 服务器应用使用 OAuth 2.0 指南中的第 5 步:交换刷新令牌和访问令牌的授权代码按照说明执行操作。

管理令牌

您的平台会以安全的方式存储刷新令牌。在移除用户帐号、google.accounts.oauth2.revoke 或直接从 https://myaccount.google.com/permissions 撤消用户意见征求时,删除存储的刷新令牌。

(可选)您可以考虑使用 RISC 使用跨帐号保护功能保护用户帐号

通常,您的后端平台将使用访问令牌调用 Google API。如果您的 Web 应用还将直接从用户的浏览器调用 Google API,那么您必须实现一种与 Web 应用共享访问令牌的方法,那么这样做不在本指南的讨论范围内。按照此方法操作并使用 JavaScript 版 Google API 客户端库时,请使用 gapi.client.SetToken() 将访问令牌临时存储在浏览器内存中,并使该库能够调用 Google API。