使用令牌模型

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

在通过浏览器访问受保护的用户数据之前,您网站上的用户会触发 Google 基于网络的账号选择器、登录和意见征求流程,以及 Google 的 OAuth 服务器会发出并向您的 Web 应用返回一个访问令牌。

在基于令牌的授权模型中,无需按用户存储 刷新令牌。

建议您按照此处介绍的方法,而不是 旧版适用于客户端 Web 应用的 OAuth 2.0 涵盖的技术 指南。

设置

按照获取您的 Google API 客户端 ID 指南。然后,向网页添加客户端库 调用 Google API 的代码。最后,初始化令牌 客户端。通常,这在客户端库的 onload 处理程序中完成。

初始化令牌客户端

调用 initTokenClient() 以使用 Web 应用的 Client-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 会调用您的回调函数,以返回访问令牌和用户 已批准的范围然后,您的应用会安全地处理各种不同的结果 授予对象权限。

增量授权

对于 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()。默认情况下,只有在首次 当用户访问您的网站并请求新范围时,系统可能会在 在 Token Client 配置对象中使用 prompt=consent

使用令牌

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

将 REST 和 CORS 与 Google API 搭配使用

访问令牌可用于向 Google API 发出经过身份验证的请求 REST 和 CORS。这样,用户登录账号、表示同意后,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 API 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);
  });