使用令牌模型

google.accounts.oauth2 JavaScript 库可帮助您提示用户征求其同意,并获取访问令牌以处理用户数据。它基于 OAuth 2.0 隐式授权流程,旨在让您能够直接使用 REST 和 CORS 调用 Google API,或者使用我们的 Google API JavaScript 客户端库(也称为 gapi.client)以简单灵活的方式访问我们更复杂的 API。

在从浏览器访问受保护的用户数据之前,您网站上的用户会触发 Google 基于 Web 的账号选择器、登录和同意流程,最后 Google 的 OAuth 服务器会向您的 Web 应用颁发并返回访问令牌。

在基于令牌的授权模型中,无需在后端服务器上存储每个用户的刷新令牌。

建议您遵循此处概述的方法,而不是旧版适用于客户端 Web 应用的 OAuth 2.0 指南中介绍的技术。

前提条件

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

初始化令牌客户端

调用 initTokenClient() 以使用 Web 应用的客户端 ID 初始化新的令牌客户端,您需要添加用户需要访问的一个或多个范围的列表:

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  callback: (response) => {
    ...
  },
});

触发 OAuth 2.0 令牌流程

使用 requestAccessToken() 方法触发令牌用户体验流程并获取访问令牌。Google 会提示用户执行以下操作:

  • 选择其账号,
  • 登录 Google 账号(如果尚未登录),
  • 授予您的 Web 应用访问每个请求范围的同意。

用户手势触发令牌流程:<button onclick="client.requestAccessToken();">Authorize me</button>

然后,Google 会向您的回调处理程序返回一个 TokenResponse,其中包含访问令牌和用户已授予访问权限的范围列表,或者返回一个错误。

用户可能会关闭账号选择器或登录窗口,在这种情况下,您的回调函数将不会被调用。

只有在仔细查看 Google 的 OAuth 2.0 政策后,才能实现应用的设计和用户体验。这些政策涵盖了处理多个范围、何时以及如何处理用户意见征求等内容。

增量授权是一种政策和应用设计方法,用于仅在需要时使用范围请求对资源的访问权限,而不是预先一次性请求所有权限。用户可以批准或拒绝分享您的应用请求的各个资源,这称为精细权限

在此过程中,Google 会提示用户授予同意,并单独列出每个请求的范围。用户选择要与您的应用共享的资源,最后,Google 会调用您的回调函数以返回访问令牌和用户批准的范围。然后,您的应用会安全地处理精细权限可能带来的各种不同结果。

不过,也有例外情况。具有全网域授权的 Google Workspace 企业应用或标记为受信任的应用会绕过精细权限同意页面。对于这些应用,用户不会看到精细权限同意屏幕。您的应用将收到所有请求的范围,或者不收到任何范围。

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

增量授权

对于 Web 应用,以下两个高级别场景演示了如何使用以下方式进行增量授权:

  • 单页 Ajax 应用,通常使用 XMLHttpRequest 动态访问资源。
  • 多个网页,资源按网页进行分隔和管理。

我们提供这两种方案是为了说明设计注意事项和方法,但并非旨在提供有关如何在应用中纳入意见征求功能的全面建议。实际应用可能会使用这些技术的变体或组合。

Ajax

通过多次调用 requestAccessToken() 并使用 OverridableTokenClientConfig 对象的 scope 参数,在需要时(且仅在必要时)请求各个范围,从而为您的应用添加增量授权支持。在此示例中,只有在用户手势展开折叠的内容部分后,才会请求并显示资源。

Ajax 应用
在网页加载时初始化令牌客户端:
        const client = google.accounts.oauth2.initTokenClient({
          client_id: 'YOUR_GOOGLE_CLIENT_ID',
          callback: "onTokenResponse",
        });
      
通过用户手势请求同意并获取访问令牌,点击“+”打开:

值得一读的文档

显示近期文档

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/documents.readonly'
             })
           );
        

即将举办的活动

显示日历信息

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/calendar.readonly'
             })
           );
        

显示照片

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/photoslibrary.readonly'
             })
           );
        

每次调用 requestAccessToken 都会触发用户同意时刻,您的应用将仅有权访问用户选择展开的部分所需的资源,从而通过用户选择限制资源共享。

多个网页

在设计增量授权时,系统会使用多个网页来仅请求加载网页所需的范围,从而降低复杂性,并减少为征得用户同意和检索访问令牌而需要进行的多次调用。

多页应用
网页 代码
第 1 页。要阅读的文档
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/documents.readonly',
  });
  client.requestAccessToken();
          
第 2 页。即将举办的活动
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/calendar.readonly',
  });
  client.requestAccessToken();
          
第 3 页。照片轮播界面
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/photoslibrary.readonly',
  });
  client.requestAccessToken();
          

每个网页都会在加载时通过调用 initTokenClient()requestAccessToken() 请求必要的范围并获取访问令牌。在此场景中,各个网页用于按范围清晰分隔用户功能和资源。在实际情况中,单个网页可能会请求多个相关范围。

精细权限

在所有场景中,细化权限的处理方式都相同;在 requestAccessToken() 调用您的回调函数并返回访问令牌后,请使用 hasGrantedAllScopes()hasGrantedAnyScope() 检查用户是否已批准所请求的范围。例如:

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly \
          https://www.googleapis.com/auth/documents.readonly \
          https://www.googleapis.com/auth/photoslibrary.readonly',
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) {
      if (google.accounts.oauth2.hasGrantedAnyScope(tokenResponse,
          'https://www.googleapis.com/auth/photoslibrary.readonly')) {
        // Look at pictures
        ...
      }
      if (google.accounts.oauth2.hasGrantedAllScopes(tokenResponse,
          'https://www.googleapis.com/auth/calendar.readonly',
          'https://www.googleapis.com/auth/documents.readonly')) {
        // Meeting planning and review documents
        ...
      }
    }
  },
});

之前会话或请求中已接受的任何授权也会包含在响应中。系统会针对每个用户和客户端 ID 维护用户同意记录,并且该记录会在对 initTokenClient()requestAccessToken() 的多次调用中保持不变。默认情况下,用户只需在首次访问您的网站并请求新范围时表示同意,但也可以使用令牌客户端配置对象中的 prompt=consent 在每次网页加载时请求用户表示同意。

使用令牌

在令牌模型中,操作系统或浏览器不会存储访问令牌,而是在网页加载时首次获取新令牌,或者随后通过用户手势(例如按下按钮)触发对 requestAccessToken() 的调用来获取新令牌。

将 REST 和 CORS 与 Google API 搭配使用

访问令牌可用于通过 REST 和 CORS 向 Google API 发出经过身份验证的请求。这样一来,用户就可以登录、授予同意权,Google 就可以颁发访问令牌,您的网站就可以使用用户的数据。

在此示例中,使用 tokenRequest() 返回的访问令牌查看已登录用户的即将到来的日历活动:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.googleapis.com/calendar/v3/calendars/primary/events');
xhr.setRequestHeader('Authorization', 'Bearer ' + tokenResponse.access_token);
xhr.send();

如需了解详情,请参阅如何使用 CORS 访问 Google API

下一部分将介绍如何轻松集成更复杂的 API。

使用 Google APIs JavaScript 库

令牌客户端可与 JavaScript 版 Google API 客户端库搭配使用。请参阅下面的代码段。

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) {
      gapi.client.setApiKey('YOUR_API_KEY');
      gapi.client.load('calendar', 'v3', listUpcomingEvents);
    }
  },
});

function listUpcomingEvents() {
  gapi.client.calendar.events.list(...);
}

令牌过期

根据设计,访问令牌的生命周期较短。如果访问令牌在用户会话结束之前过期,请通过从用户驱动的事件(例如按钮按压)调用 requestAccessToken() 来获取新令牌。

调用 google.accounts.oauth2.revoke 方法可移除用户同意情况以及对授予您应用的所有范围的资源访问权限。撤消此权限需要有效的访问令牌:

google.accounts.oauth2.revoke('414a76cb127a7ece7ee4bf287602ca2b56f8fcbf7fcecc2cd4e0509268120bd7', done => {
    console.log(done);
    console.log(done.successful);
    console.log(done.error);
    console.log(done.error_description);
  });