使用代码模型

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

OAuth 2.0 授权代码流程摘要:

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

前提条件

按照设置中所述的步骤配置 OAuth 权限请求页面、获取客户端 ID 并加载客户端库。

初始化代码客户端

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

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

如需为以下对象初始化客户端:

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

  • 对于弹出式界面用户体验流程,请将 ux_mode 设置为 popup,并将 callback 的值设置为您将用于向平台发送授权码的函数的名称。redirect_uri 的值默认为调用 initCodeClient 的网页的来源。此值稍后会在流程中用于将授权代码换成访问令牌或刷新令牌。例如,https://www.example.com/index.html 调用 initCodeClient,来源 https://www.example.comredirect_url 的值。

防范 CSRF 攻击

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

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

重定向模式

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

const client = google.accounts.oauth2.initCodeClient({   client_id: 'YOUR_CLIENT_ID',   scope: 'https://www.googleapis.com/auth/calendar.readonly',   ux_mode: 'redirect',   redirect_uri: 'https://oauth2.example.com/code',   state: 'YOUR_BINDING_VALUE' }); 

初始化一个客户端,用户可以在弹出式对话框中开始授权流程。征得用户同意后,Google 会使用回调函数将授权代码返回给用户的浏览器。来自 JS 回调处理程序的 POST 请求将授权代码传递到后端服务器上的某个端点,在该端点上,系统会先验证该授权代码,然后完成 OAuth 流程的其余部分。

const client = google.accounts.oauth2.initCodeClient({   client_id: 'YOUR_CLIENT_ID',   scope: 'https://www.googleapis.com/auth/calendar.readonly',   ux_mode: 'popup',   callback: (response) => {     const xhr = new XMLHttpRequest();     xhr.open('POST', "https://oauth2.example.com/code", 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 指定的处理程序在用户浏览器中运行,并将授权代码中继到您的平台托管的端点。

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

  • 如果您没有现有的实现,请创建新的授权端点,或者

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

授权端点

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

名称
authuser 请求用户登录身份验证
代码 由 Google 生成的 OAuth2 授权代码
高清 用户账号的托管网域
提示 用户意见征求对话框
范围 以空格分隔的授权 OAuth2 范围列表(包含一个或多个范围)
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。