如何处理精细权限

概览

通过精细的权限,用户可以更精细地控制选择与每个应用分享的账号数据。它们可以提供更高的控制力、透明度和安全性,让用户和开发者都受益。本指南将帮助您了解必要的更改和步骤,以成功更新应用以处理精细的权限。

什么是精细权限?

假设您要开发一款效率应用,需要同时请求电子邮件和日历作用域。您的用户可能希望只针对 Google 日历使用您的应用,而不使用 Gmail。通过精细的 OAuth 权限,用户可以选择仅授予 Google 日历权限,而不授予 Gmail 权限。通过允许用户授予对特定数据的访问权限,可以最大限度地减少数据泄露、增进信任,并让用户能够以隐私保护为先的掌控其数字生活。请务必设计您的应用以处理此类情况。

请求多个非登录范围时

登录范围和非登录范围

对于同时请求登录范围和非登录范围的应用,用户首先会看到登录范围的同意页面。用户同意共享其基本身份信息(姓名、电子邮件地址和个人资料照片)后,将看到针对非登录范围的精细权限同意屏幕。在这种情况下,应用必须检查用户授予的范围,不能假定用户授予了请求的所有范围。在以下示例中,Web 应用请求所有三个登录范围和一个 Google 云端硬盘非登录范围。在用户同意登录范围后,用户将看到针对 Google 云端硬盘权限的精细权限同意屏幕:

登录范围和非登录范围

多个非登录范围

当应用请求多个非登录范围时,系统会向用户显示精细的权限同意屏幕。用户可以选择想要批准与应用共享的权限。以下是一个精细权限同意屏幕示例,请求访问用户的 Gmail 邮件和 Google 日历数据:

多个非登录范围

确定您的应用是否受到影响

全面检查您的应用中使用 Google OAuth 2.0 授权端点发出权限请求的所有部分。请注意那些请求多个范围的应用,当它们激活向用户显示的精细权限同意屏幕时,请留意。在此类情况下,请确保您的代码可以处理用户仅授权部分范围的情况。

如何确定您的应用是否使用多个范围

检查您的应用代码去电网络调用,确定您的应用发出的 Google OAuth 2.0 授权请求是否会导致显示精细权限同意屏幕。

检查应用代码

检查您调用 Google OAuth 授权端点的应用代码中的哪些部分,以向用户请求权限。如果您使用某个 Google API 客户端库,通常可以在客户端初始化步骤中找到您的应用请求的范围。以下部分介绍了一些示例。您应参考应用处理 Google OAuth 2.0 时所用的 SDK 的文档,确定您的应用是否会受到影响,并参考以下示例中显示的指南。

Google Identity 服务

以下 Google Identity 服务 JavaScript 库代码段使用多个非登录范围初始化 TokenClient。当 Web 应用向用户请求授权时,系统将显示精细权限同意屏幕。

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

Python

以下代码段使用 google-auth-oauthlib.flow 模块构建授权请求;scope 参数包含两个非登录范围。当 Web 应用向用户请求授权时,系统将显示精细权限同意屏幕。

import google.oauth2.credentials
import google_auth_oauthlib.flow

# Use the client_secret.json file to identify the application requesting
# authorization. The client ID (from that file) and access scopes are required.
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/calendar.readonly',
                    'https://www.googleapis.com/auth/contacts.readonly'])

Node.js

以下代码段将创建一个 google.auth.OAuth2 对象,该对象定义了授权请求中的参数,其 scope 参数包含两个非登录范围。当 Web 应用向用户请求授权时,系统会显示精细的权限同意屏幕。

const {google} = require('googleapis');

/**
  * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI
  * from the client_secret.json file. To get these credentials for your application, visit
  * https://console.cloud.google.com/apis/credentials.
  */
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for read-only Calendar and Contacts.
const scopes = [
  'https://www.googleapis.com/auth/calendar.readonly',
  'https://www.googleapis.com/auth/contacts.readonly']
];

// Generate a url that asks permissions
const authorizationUrl = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  /** Pass in the scopes array defined above.
    * Alternatively, if only one scope is needed, you can pass a scope URL as a string */
  scope: scopes,
  // Enable incremental authorization. Recommended as best practices.
  include_granted_scopes: true
});

检查去电网络通话

检查网络调用的方法因应用客户端类型而异。

检查网络调用时,请查找发送到 Google OAuth 授权端点的请求并检查 scope 参数。

这些值cause导致显示精细的权限同意屏幕。

  • scope 参数包含登录范围和非登录范围。

    以下示例请求包含所有三个登录范围和一个非登录范围,用于查看用户 Google 云端硬盘文件的元数据:

    https://accounts.google.com/o/oauth2/v2/auth?
    access_type=offline&
    scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20openid%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly&
    include_granted_scopes=true&
    response_type=code&
    redirect_uri=YOUR_REDIRECT_URL&
    client_id=YOUR_CLIENT_ID
  • scope 参数包含多个非登录范围。

    以下示例请求包含两个非登录范围,用于查看用户的 Google 云端硬盘元数据和管理特定的 Google 云端硬盘文件:

  • https://accounts.google.com/o/oauth2/v2/auth?
    access_type=offline&
    scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file&
    include_granted_scopes=true&
    response_type=code&
    redirect_uri=YOUR_REDIRECT_URL&
    client_id=YOUR_CLIENT_ID

处理精细权限的最佳做法

如果您determine需要更新应用来处理精细权限,则应对代码进行必要的更新,以正确处理多个范围的同意情况。所有应用都应遵循以下最佳实践:

  1. 查看 Google API 服务:用户数据政策,确保您遵守其中的规定。
  2. 请求任务所需的特定范围。您必须遵守 Google OAuth 2.0 政策,您仅请求所需的范围。您应该避免在登录时请求多个作用域,除非这对实现应用的核心功能至关重要。将多个作用域捆绑在一起(尤其是对于不熟悉应用功能的首次用户而言),可能会使他们难以理解对这些权限的需求。这可能会引发闹钟提醒,并阻碍用户进一步与您的应用互动。
  3. 在发出授权请求之前向用户提供理由。明确说明您的应用为何需要所请求的权限、您将如何处理用户数据,以及批准该请求将为用户带来哪些好处。 我们的研究表明,这些说明可以提高用户信任度和互动度。
  4. 如果您的应用请求范围,请使用 增量授权,这样就不必管理多个访问令牌。
  5. 查看用户授予了哪些范围。在一次请求多个范围时,用户可能不会授予应用请求的所有范围。您的应用应始终检查用户授予了哪些作用域,并通过停用相关功能来处理任何作用域拒绝事件。遵循关于处理多个范围的同意情况的 Google OAuth 2.0 政策,并且仅在用户明确表示有意使用需要该范围的特定功能时,才再次提示用户。

更新您的应用以处理精细权限

Android 应用

您应参阅用于与 Google OAuth 2.0 交互的 SDK 的文档,并根据最佳实践更新您的应用以处理精细权限。

如果您使用 Play 服务中的 auth.api.signin SDK 与 Google OAuth 2.0 进行交互,可以使用 requestPermissions 函数请求所需的最小范围集,并使用 hasPermissions 函数检查用户在请求精细权限时授予的范围。

Chrome 扩展程序应用

根据最佳实践,您应该使用 Chrome Identity API 来与 Google OAuth 2.0 配合使用。

以下示例展示了如何正确处理精细权限。

manifest.json

示例清单文件为 Chrome 扩展程序应用声明了两个非登录范围。

{
  "name": "Example Chrome extension application",
  ...
  "permissions": [
      "identity"
    ],
  "oauth2" : {
      "client_id": "YOUR_CLIENT_ID",
      "scopes":["https://www.googleapis.com/auth/calendar.readonly",
                "https://www.googleapis.com/auth/contacts.readonly"]
  }
}

方法不正确

全部或零

用户点击该按钮即可开始授权流程。该代码段假设针对 manifest.json 文件中指定的两个范围向用户显示“一刀切”式同意屏幕。而不会检查用户授予了哪些范围。

oauth.js

...
document.querySelector('button').addEventListener('click', function () {
  chrome.identity.getAuthToken({ interactive: true },
      function (token) {
          if (token === undefined) {
            // User didn't authorize both scopes.
            // Updating the UX and application accordingly
            ...
          } else {
            // User authorized both or one of the scopes.
            // It neglects to check which scopes users granted and assumes users granted all scopes.

            // Calling the APIs, etc.
            ...
          }
      });
});

正确方法

最小范围

选择所需的最小范围集

应用应只请求所需的最小范围集。建议在需要完成任务时,一次请求一个作用域。

在此示例中,假设 manifest.json 文件中声明的两个范围都是所需的最小范围集。oauth.js 文件使用 Chrome Identity API 向 Google 启动授权流程。您应该选择 启用精细权限,以便用户可以更好地控制向您的应用授予权限。您的应用应通过检查用户授权的范围来正确处理用户的响应。

oauth.js

...
document.querySelector('button').addEventListener('click', function () {
  chrome.identity.getAuthToken({ interactive: true, enableGranularPermissions: true },
      function (token, grantedScopes) {
          if (token === undefined) {
            // User didn't authorize any scope.
            // Updating the UX and application accordingly
            ...
          } else {
            // User authorized the request. Now, check which scopes were granted.
            if (grantedScopes.includes('https://www.googleapis.com/auth/calendar.readonly'))
            {
              // User authorized Calendar read permission.
              // Calling the APIs, etc.
              ...
            }
            else
            {
              // User didn't authorize Calendar read permission.
              // Update UX and application accordingly
              ...
            }

            if (grantedScopes.includes('https://www.googleapis.com/auth/contacts.readonly'))
            {
              // User authorized Contacts read permission.
              // Calling the APIs, etc.
              ...
            }
            else
            {
              // User didn't authorize Contacts read permission.
              // Update UX and application accordingly
              ...
            }
          }
      });
});

iOS、iPadOS 和 macOS 应用

您应参阅用于与 Google OAuth 2.0 交互的 SDK 的文档,并根据最佳实践更新您的应用以处理精细权限。

如果您使用 iOS 和 macOS 版 Google 登录库与 Google OAuth 2.0 进行交互,则应查看有关处理精细权限的文档

Web 应用

您应参阅用于与 Google OAuth 2.0 交互的 SDK 的文档,并根据最佳实践更新您的应用以处理精细权限。

服务器端(离线)访问

若要使用服务器端(离线)访问模式,您需要执行以下操作:
  • 建立服务器并定义一个可公开访问的端点来接收授权代码。
  • 在 Google Cloud 控制台的 Credentials page 中配置公共端点的 重定向 URI

以下代码段展示了一个 NodeJS 示例请求两个非登录作用域。用户将看到精细的权限同意屏幕。

方法不正确

全部或零

系统会将用户重定向到授权网址。该代码段假设针对 scopes 指令中指定的两个范围,向用户显示“一刀切”式同意屏幕。而不会检查用户授予了哪些范围。

main.js

...
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for two non-Sign-In scopes - Google Calendar and Contacts
const scopes = [
  'https://www.googleapis.com/auth/contacts.readonly',
  'https://www.googleapis.com/auth/calendar.readonly'
];

// Generate a url that asks permissions for the Google Calendar and Contacts scopes
const authorizationUrl = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  // Pass in the scopes array defined above
  scope: scopes,
  // Enable incremental authorization. Recommended as best practices.
  include_granted_scopes: true
});

async function main() {
  const server = http.createServer(async function (req, res) {
    // Example on redirecting user to Google OAuth 2.0 server.
    if (req.url == '/') {
      res.writeHead(301, { "Location": authorizationUrl });
    }
    // Receive the callback from Google OAuth 2.0 server.
    if (req.url.startsWith('/oauth2callback')) {
      // Handle the Google OAuth 2.0 server response
      let q = url.parse(req.url, true).query;

      if (q.error) {
        // User didn't authorize both scopes.
        // Updating the UX and application accordingly
        ...
      } else {
        // User authorized both or one of the scopes.
        // It neglects to check which scopes users granted and assumes users granted all scopes.

        // Get access and refresh tokens (if access_type is offline)
        let { tokens } = await oauth2Client.getToken(q.code);
        // Calling the APIs, etc.
        ...
      }
    }
    res.end();
  }).listen(80);
}
正确方法

最小范围

选择所需的最小范围集

应用应只请求所需的最小范围集。建议在需要完成任务时,一次请求一个作用域。每当您的应用请求范围时,都应使用增量授权,以免需要管理多个访问令牌。

如果您的应用必须请求多个非登录范围,您在请求和检查用户授予的范围时应始终使用增量授权

在此示例中,假设应用正常运行需要声明的这两个范围。您应该选择 启用精细权限,以便用户可以更好地控制向您的应用授予权限。您的应用应通过检查用户已授权的范围来正确处理用户的响应。

main.js

...
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for two non-Sign-In scopes - Google Calendar and Contacts
const scopes = [
  'https://www.googleapis.com/auth/contacts.readonly',
  'https://www.googleapis.com/auth/calendar.readonly'
];

// Generate a url that asks permissions for the Google Calendar and Contacts scopes
const authorizationUrl = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  // Pass in the scopes array defined above
  scope: scopes,
  // Enable incremental authorization. Recommended as best practices.
  include_granted_scopes: true,
  // Set to true to enable more granular permissions for Google OAuth 2.0 client IDs created before 2019.
  // No effect for newer Google OAuth 2.0 client IDs, since more granular permissions is always enabled for them.
  enable_granular_consent: true
});

async function main() {
  const server = http.createServer(async function (req, res) {
    // Redirect users to Google OAuth 2.0 server.
    if (req.url == '/') {
      res.writeHead(301, { "Location": authorizationUrl });
    }
    // Receive the callback from Google OAuth 2.0 server.
    if (req.url.startsWith('/oauth2callback')) {
      // Handle the Google OAuth 2.0 server response
      let q = url.parse(req.url, true).query;

      if (q.error) {
        // User didn't authorize both scopes.
        // Updating the UX and application accordingly
        ...
      } else {
        // Get access and refresh tokens (if access_type is offline)
        let { tokens } = await oauth2Client.getToken(q.code);
        oauth2Client.setCredentials(tokens);

        // User authorized the request. Now, check which scopes were granted.
        if (tokens.scope.includes('https://www.googleapis.com/auth/calendar.readonly'))
        {
          // User authorized Calendar read permission.
          // Calling the APIs, etc.
          ...
        }
        else
        {
          // User didn't authorize Calendar read permission.
          // Calling the APIs, etc.
          ...
        }

        // Check which scopes user granted the permission to application
        if (tokens.scope.includes('https://www.googleapis.com/auth/contacts.readonly'))
        {
          // User authorized Contacts read permission.
          // Calling the APIs, etc.
          ...
        }
        else
        {
          // User didn't authorize Contacts read permission.
          // Update UX and application accordingly
          ...
        }
      }
    }
    res.end();
  }).listen(80);
}

请参阅 服务器端 Web 应用指南,了解如何从基于服务器的应用访问 Google API。

仅客户端访问

  • 对于使用 Google Identity 服务 JavaScript 库与 Google OAuth 2.0 进行交互的应用,您应查看此文档,了解如何处理精细权限。
  • 对于使用 JavaScript 直接调用 Google OAuth 2.0 授权端点的应用,应查看此文档,了解如何处理精细权限。

测试更新后的应用如何处理精细权限

  1. 概述用户可以响应权限请求的所有情况以及应用的预期行为。例如,如果用户只授权了所请求三个范围中的两个,则应用应采取相应的行为。
  2. 在启用精细权限的情况下测试您的应用。您可以通过以下两种方式启用精细权限:
    1. 检查应用的 OAuth 2.0 同意屏幕,查看是否已为您的应用启用细化权限。您还可以出于测试目的,通过 Google Cloud 控制台创建新的 Web、Android 或 iOS Google OAuth 2.0 客户端 ID,因为系统始终会为这些客户端启用精细权限。
    2. 调用 Google OAuth 授权端点时,请将参数 enable_granular_consent 设置为 true。某些 SDK 明确支持此参数。对于其他参数,请参阅相关文档,了解如何手动添加此参数及其值。 如果您的实现不支持添加该参数,则可以通过 Google Cloud 控制台创建一个新的 Web、Android 或 iOS 版 Google OAuth 2.0 客户端 ID,仅用于测试目的(如前所述)。