针对网络服务器应用使用 OAuth 2.0

本文档介绍了网络服务器应用如何使用 Google API 客户端库或 Google OAuth 2.0 端点来实现 OAuth 2.0 授权以访问 Google API。

OAuth 2.0 允许用户与应用共享特定数据,同时保持用户名、密码和其他信息的私密性。例如,应用可以使用 OAuth 2.0 获得用户许可,将文件存储在他们的 Google 云端硬盘中。

此 OAuth 2.0 流程专门用于用户授权。它专为可存储机密信息并维护状态的应用而设计。适当授权的网络服务器应用可以在用户与应用互动时或用户离开应用后访问相应 API。

Web 服务器应用也经常使用 服务帐号向 API 请求授权,尤其是在调用 Cloud API 访问基于项目的数据时,而不是访问特定于用户的数据时。网络服务器应用可以将服务帐号与用户授权结合使用。

客户端库

本页面上特定于语言的示例使用 Google API 客户端库来实现 OAuth 2.0 授权。要运行代码示例,您必须先安装适合您的语言的客户端库。

当您使用 Google API 客户端库处理应用的 OAuth 2.0 流程时,该客户端库会执行应用需要自行处理的许多操作。例如,它决定了应用何时可以使用或刷新存储的访问令牌,以及应用何时必须征得用户同意。客户端库还会生成正确的重定向网址,并有助于实现使用授权代码交换访问令牌的重定向处理程序。

适用于服务器端应用的 Google API 客户端库支持以下语言:

前提条件

为您的项目启用 API

调用 Google API 的任何应用都需要在 API Console中启用这些 API。

如需为您的项目启用该 API,请按以下步骤操作:

  1. Open the API Library (在 Google API Console中)。
  2. If prompted, select a project, or create a new one.
  3. API Library 列出了所有可用的 API(按产品系列和热门程度分组)。如果列表中没有显示您要启用的 API,请使用搜索功能查找该 API,或点击 API 所属的产品系列中的查看全部
  4. 选择您要启用的 API,然后点击启用按钮。
  5. If prompted, enable billing.
  6. If prompted, read and accept the API's Terms of Service.

创建授权凭据

任何使用 OAuth 2.0 访问 Google API 的应用都必须拥有授权凭据,向 Google 的 OAuth 2.0 服务器证明应用的身份。以下步骤介绍了如何为您的项目创建凭据。然后,您的应用就可以使用这些凭据访问您为该项目启用的 API。

  1. Go to the Credentials page.
  2. 依次点击创建凭据 > OAuth 客户端 ID
  3. 选择 Web 应用应用类型。
  4. 填写表单,然后点击创建。使用 PHP、Java、Python、Ruby 和 .NET 等语言和框架的应用必须指定经授权的重定向 URI。重定向 URI 是 OAuth 2.0 服务器可将响应发送到的端点。这些端点必须遵循 Google 的验证规则

    在测试时,您可以指定引用本地机器的 URI,例如 http://localhost:8080。请注意,本文档中的所有示例均使用 http://localhost:8080 作为重定向 URI。

    我们建议您设计应用的身份验证端点,使应用不会向页面上的其他资源公开授权代码。

创建凭据后,从 API Console下载 client_secret.json 文件。将该文件安全地存储在只有您的应用可以访问的位置。

确定访问权限范围

通过范围,您的应用可以仅请求访问所需的资源,同时还可以控制用户向您的应用授予的访问权限大小。因此,请求的范围数量与征得用户同意的可能性之间可能存在反向关系。

在开始实现 OAuth 2.0 授权之前,我们建议您确定您的应用需要访问权限的范围。

我们还建议您的应用通过增量授权流程请求对授权范围的访问权限,在此过程中,您的应用会在上下文中请求访问用户数据。此最佳做法可帮助用户更轻松地了解应用为何需要请求的访问权限。

OAuth 2.0 API 范围文档包含可用于访问 Google API 的范围的完整列表。

特定语言的要求

如需运行本文档中的任何代码示例,您需要拥有 Google 帐号、互联网访问权限和网络浏览器。如果您使用的是其中一个 API 客户端库,另请参阅下文针对特定语言的要求。

PHP

如需运行本文档中的 PHP 代码示例,您需要:

  • 安装有命令行界面 (CLI) 和 JSON 扩展程序的 PHP 5.6 或更高版本。
  • Composer 依赖项管理工具。
  • 适用于 PHP 的 Google API 客户端库:

    composer require google/apiclient:^2.10

Python

如需运行本文档中的 Python 代码示例,您需要:

  • Python 2.6 或更高版本
  • pip 软件包管理工具。
  • Python 版 Google API 客户端库:
    pip install --upgrade google-api-python-client
  • 用于用户授权的 google-authgoogle-auth-oauthlibgoogle-auth-httplib2
    pip install --upgrade google-auth google-auth-oauthlib google-auth-httplib2
  • Flask Python Web 应用框架。
    pip install --upgrade flask
  • requests HTTP 库。
    pip install --upgrade requests

Ruby

如需运行本文档中的 Ruby 代码示例,您需要:

  • Ruby 2.2.2 或更高版本
  • 适用于 Ruby 的 Google API 客户端库:

    gem install google-api-client
  • Sinatra Ruby Web 应用框架。

    gem install sinatra

Node.js

如需运行本文档中的 Node.js 代码示例,您需要:

  • Node.js 维护 LTS、活跃 LTS 或当前版本。
  • Google API Node.js 客户端:

    npm install googleapis

HTTP/REST

您无需安装任何库即可直接调用 OAuth 2.0 端点。

获取 OAuth 2.0 访问令牌

以下步骤显示了您的应用如何与 Google 的 OAuth 2.0 服务器进行交互,以征得用户的同意,以代表用户执行 API 请求。您的应用必须先获得用户的同意,然后才能执行需要用户授权的 Google API 请求。

以下列表简要总结了这些步骤:

  1. 您的应用标识其所需的权限。
  2. 您的应用会将用户重定向至所请求的权限列表。
  3. 用户决定是否向您的应用授予权限。
  4. 您的应用会找出用户的决定。
  5. 如果用户被授予所请求的权限,您的应用会检索代表用户发出 API 请求所需的令牌。

第 1 步:设置授权参数

第一步是创建授权请求。该请求设置了一些参数,用于标识您的应用并定义用户将向您的应用授予的权限。

  • 如果您使用 Google 客户端库进行 OAuth 2.0 身份验证和授权,则需要创建并配置定义这些参数的对象。
  • 如果您直接调用 Google OAuth 2.0 端点,则会生成一个网址并在该网址上设置参数。

下面的标签定义了网络服务器应用支持的授权参数。各语言的示例还介绍了如何使用客户端库或授权库来配置用于设置这些参数的对象。

PHP

以下代码段将创建一个 Google\Client() 对象,用于定义授权请求中的参数。

该对象使用 client_secret.json 文件中的信息来标识您的应用。(如需详细了解此文件,请参阅创建授权凭据)。该对象还可以标识您的应用正在请求的访问权限范围,以及应用的身份验证端点的网址,该端点将处理来自 Google OAuth 2.0 服务器的响应。最后,代码会设置可选的 access_typeinclude_granted_scopes 参数。

例如,以下代码请求以只读方式离线访问用户的 Google 云端硬盘:

$client = new Google\Client();
$client->setAuthConfig('client_secret.json');
$client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY);
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php');
// offline access will give you both an access and refresh token so that
// your app can refresh the access token without user interaction.
$client->setAccessType('offline');
// Using "consent" ensures that your application always receives a refresh token.
// If you are not using offline access, you can omit this.
$client->setApprovalPrompt('consent');
$client->setIncludeGrantedScopes(true);   // incremental auth

该请求指定了以下信息:

参数
client_id 必需

应用的客户端 ID。您可以在 API Console Credentials page中找到此值。

在 PHP 中,调用 setAuthConfig 函数从 client_secret.json 文件加载授权凭据。

$client = new Google\Client();
$client->setAuthConfig('client_secret.json');
redirect_uri 必需

确定在用户完成授权流程后,API 服务器会将用户重定向到哪里。该值必须与您在客户端的 API Console Credentials page中配置的 OAuth 2.0 客户端的其中一个已获授权的重定向 URI 完全匹配。如果此值与提供的 client_id 的授权重定向 URI 不匹配,您会收到 redirect_uri_mismatch 错误。

请注意,httphttps 架构、大小写和尾随斜杠(“/”)必须全部匹配。

如需在 PHP 中设置此值,请调用 setRedirectUri 函数。请注意,您必须为提供的 client_id 指定有效的重定向 URI。

$client->setRedirectUri('https://oauth2.example.com/code');
scope 必需

以空格分隔的范围列表,用于标识您的应用可以代表用户访问的资源。这些值会通知 Google 向用户显示的同意屏幕。

通过范围,您的应用可以仅请求访问所需的资源,同时还可以控制用户向您的应用授予的访问权限大小。因此,请求的范围数量与征得用户同意的可能性之间存在反向关系。

如需在 PHP 中设置此值,请调用 addScope 函数:

$client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY);

建议您的应用尽可能尽快请求访问授权范围。通过在上下文中通过增量授权请求对用户数据的访问,您可以帮助用户更轻松地了解您的应用为何会请求其所请求的访问权限。

access_type 推荐

指示您的应用是否可以在浏览器不存在时刷新访问令牌。有效的参数值包括 online(默认值)和 offline

如果您的应用需要在用户不在浏览器时刷新访问令牌,请将该值设置为 offline。这是刷新本文档稍后所述的访问令牌的方法。此值会指示 Google 授权服务器在应用首次交换授权代码时返回刷新令牌和访问令牌。

如需在 PHP 中设置此值,请调用 setAccessType 函数:

$client->setAccessType('offline');
state 推荐

指定您的应用用于维护授权请求与授权服务器响应之间的状态的任何字符串值。在用户同意或拒绝您的应用的访问请求之后,服务器将返回您在 redirect_uri 的网址查询组件 (?) 中作为 name=value 对发送的确切值。

您可以将此参数用于多种用途,例如将用户定向到应用中的正确资源、发送 Nonce 以及减少跨网站请求伪造。由于您的 redirect_uri 是可以猜测的,因此使用 state 值可提高您对传入连接是否是身份验证请求结果的保证。如果您生成随机字符串,或对捕获客户端状态的 Cookie 或其他值进行哈希处理,则可以验证响应,以进一步确保请求和响应来自同一浏览器,从而防范跨站请求伪造等攻击。如需查看有关如何创建和确认 state 令牌的示例,请参阅 OpenID Connect 文档。

如需在 PHP 中设置此值,请调用 setState 函数:

$client->setState($sample_passthrough_value);
include_granted_scopes 可选

允许应用使用增量授权在上下文中请求访问其他范围。如果您将此参数的值设为 true,且授权请求被授予,那么新的访问令牌也将涵盖用户之前向其授予应用访问权限的所有范围。有关示例,请参阅增量授权部分。

如需在 PHP 中设置此值,请调用 setIncludeGrantedScopes 函数:

$client->setIncludeGrantedScopes(true);
login_hint 可选

如果您的应用知道哪个用户正在尝试进行身份验证,它可以使用该参数为 Google 身份验证服务器提供提示。服务器会使用此提示来简化登录流程,具体做法是填写登录表单中的电子邮件字段,或选择相应的多帐号登录会话。

将参数值设为电子邮件地址或 sub 标识符(等同于用户的 Google ID)。

如需在 PHP 中设置此值,请调用 setLoginHint 函数:

$client->setLoginHint('None');
prompt 可选

一系列以空格分隔且区分大小写的提示列表,用于向用户展示。如果您未指定此参数,则系统只会在您的项目首次请求访问权限时提示用户。如需了解详情,请参阅 提示重新同意

如需在 PHP 中设置此值,请调用 setApprovalPrompt 函数:

$client->setApprovalPrompt('consent');

可能的值包括:

none 不显示任何身份验证或同意屏幕。不得使用其他值指定。
consent 提示用户同意。
select_account 提示用户选择帐号。

Python

以下代码段使用 google-auth-oauthlib.flow 模块构建授权请求。

该代码会构建一个 Flow 对象,该对象会使用您在创建授权凭据后下载的 client_secret.json 文件中的信息来识别您的应用。该对象还标识了您的应用请求访问权限的范围,以及应用的身份验证端点的网址,该端点将处理来自 Google OAuth 2.0 服务器的响应。最后,代码会设置可选的 access_typeinclude_granted_scopes 参数。

例如,以下代码请求以只读方式离线访问用户的 Google 云端硬盘:

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/drive.metadata.readonly'])

# Indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required. The value must exactly
# match one of the authorized redirect URIs for the OAuth 2.0 client, which you
# configured in the API Console. If this value doesn't match an authorized URI,
# you will get a 'redirect_uri_mismatch' error.
flow.redirect_uri = 'https://www.example.com/oauth2callback'

# Generate URL for request to Google's OAuth 2.0 server.
# Use kwargs to set optional request parameters.
authorization_url, state = flow.authorization_url(
    # Enable offline access so that you can refresh an access token without
    # re-prompting the user for permission. Recommended for web server apps.
    access_type='offline',
    # Enable incremental authorization. Recommended as a best practice.
    include_granted_scopes='true')

该请求指定了以下信息:

参数
client_id 必需

应用的客户端 ID。您可以在 API Console Credentials page中找到此值。

在 Python 中,调用 from_client_secrets_file 方法以从 client_secret.json 文件检索客户端 ID。(您也可以使用 from_client_config 方法,该方法会传递最初显示在客户端密钥文件中但不访问文件本身的客户端配置。)

flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'])
redirect_uri 必需

确定在用户完成授权流程后,API 服务器会将用户重定向到哪里。该值必须与您在客户端的 API Console Credentials page中配置的 OAuth 2.0 客户端的其中一个已获授权的重定向 URI 完全匹配。如果此值与提供的 client_id 的授权重定向 URI 不匹配,您会收到 redirect_uri_mismatch 错误。

请注意,httphttps 架构、大小写和尾随斜杠(“/”)必须全部匹配。

如需在 Python 中设置此值,请设置 flow 对象的 redirect_uri 属性:

flow.redirect_uri = 'https://oauth2.example.com/code'
scope 必需

范围列表,用于标识您的应用可以代表用户访问的资源。这些值会通知 Google 向用户显示的同意屏幕。

通过范围,您的应用可以仅请求访问所需的资源,同时还可以控制用户向您的应用授予的访问权限大小。因此,请求的范围数量与征得用户同意的可能性之间存在反向关系。

在 Python 中,请使用您设置 client_id 的方式来指定范围列表。

flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'])

建议您的应用尽可能尽快请求访问授权范围。通过在上下文中通过增量授权请求对用户数据的访问,您可以帮助用户更轻松地了解您的应用为何会请求其所请求的访问权限。

access_type 推荐

指示您的应用是否可以在浏览器不存在时刷新访问令牌。有效的参数值包括 online(默认值)和 offline

如果您的应用需要在用户不在浏览器时刷新访问令牌,请将该值设置为 offline。这是刷新本文档稍后所述的访问令牌的方法。此值会指示 Google 授权服务器在应用首次交换授权代码时返回刷新令牌和访问令牌。

在 Python 中,可以在调用 flow.authorization_url 方法时将 access_type 指定为关键字参数,从而设置 access_type 参数:

authorization_url, state = flow.authorization_url(
    access_type='offline',
    include_granted_scopes='true')
state 推荐

指定您的应用用于维护授权请求与授权服务器响应之间的状态的任何字符串值。在用户同意或拒绝您的应用的访问请求之后,服务器将返回您在 redirect_uri 的网址查询组件 (?) 中作为 name=value 对发送的确切值。

您可以将此参数用于多种用途,例如将用户定向到应用中的正确资源、发送 Nonce 以及减少跨网站请求伪造。由于您的 redirect_uri 是可以猜测的,因此使用 state 值可提高您对传入连接是否是身份验证请求结果的保证。如果您生成随机字符串,或对捕获客户端状态的 Cookie 或其他值进行哈希处理,则可以验证响应,以进一步确保请求和响应来自同一浏览器,从而防范跨站请求伪造等攻击。如需查看有关如何创建和确认 state 令牌的示例,请参阅 OpenID Connect 文档。

在 Python 中,可以在调用 flow.authorization_url 方法时将 state 指定为关键字参数,从而设置 state 参数:

authorization_url, state = flow.authorization_url(
    access_type='offline',
    state=sample_passthrough_value,
    include_granted_scopes='true')
include_granted_scopes 可选

允许应用使用增量授权在上下文中请求访问其他范围。如果您将此参数的值设为 true,且授权请求被授予,那么新的访问令牌也将涵盖用户之前向其授予应用访问权限的所有范围。有关示例,请参阅增量授权部分。

在 Python 中,可以在调用 flow.authorization_url 方法时将 include_granted_scopes 指定为关键字参数,从而设置 include_granted_scopes 参数:

authorization_url, state = flow.authorization_url(
    access_type='offline',
    include_granted_scopes='true')
login_hint 可选

如果您的应用知道哪个用户正在尝试进行身份验证,它可以使用该参数为 Google 身份验证服务器提供提示。服务器会使用此提示来简化登录流程,具体做法是填写登录表单中的电子邮件字段,或选择相应的多帐号登录会话。

将参数值设为电子邮件地址或 sub 标识符(等同于用户的 Google ID)。

在 Python 中,可以在调用 flow.authorization_url 方法时将 login_hint 指定为关键字参数,从而设置 login_hint 参数:

authorization_url, state = flow.authorization_url(
    access_type='offline',
    login_hint='None',
    include_granted_scopes='true')
prompt 可选

一系列以空格分隔且区分大小写的提示列表,用于向用户展示。如果您未指定此参数,则系统只会在您的项目首次请求访问权限时提示用户。如需了解详情,请参阅 提示重新征得用户

在 Python 中,可以在调用 flow.authorization_url 方法时将 prompt 指定为关键字参数,从而设置 prompt 参数:

authorization_url, state = flow.authorization_url(
      access_type='offline',
      prompt='consent',
      include_granted_scopes='true')

可能的值包括:

none 不显示任何身份验证或同意屏幕。不得使用其他值指定。
consent 提示用户同意。
select_account 提示用户选择帐号。

Ruby

使用您创建的 client_secrets.json 文件在应用中配置客户端对象。配置客户端对象时,您需要指定应用需要访问的范围,以及应用的身份验证端点的网址,该端点将处理来自 OAuth 2.0 服务器的响应。

例如,以下代码请求以只读方式离线访问用户的 Google 云端硬盘:

require 'google/apis/drive_v2'
require 'google/api_client/client_secrets'

client_secrets = Google::APIClient::ClientSecrets.load
auth_client = client_secrets.to_authorization
auth_client.update!(
  :scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
  :redirect_uri => 'http://www.example.com/oauth2callback',
  :additional_parameters => {
    "access_type" => "offline",         # offline access
    "include_granted_scopes" => "true"  # incremental auth
  }
)

您的应用使用客户端对象执行 OAuth 2.0 操作,例如生成授权请求网址以及对 HTTP 请求应用访问令牌。

Node.js

以下代码段将创建一个 google.auth.OAuth2 对象,用于定义授权请求中的参数。

该对象使用 client_secret.json 文件中的信息来识别您的应用。如需请求用户授予检索访问令牌的权限,您可以将用户重定向到同意页面。 如需创建同意书页面网址,请执行以下操作:

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 Drive activity.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly'
];

// Generate a url that asks permissions for the Drive activity scope
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 a best practice.
  include_granted_scopes: true
});

重要提示 - refresh_token 仅在首次授权时返回。如需了解详情,请点击 此处

HTTP/REST

Google 的 OAuth 2.0 端点位于 https://accounts.google.com/o/oauth2/v2/auth。此端点只能通过 HTTPS 访问。拒绝普通 HTTP 连接。

对于网络服务器应用,Google 授权服务器支持以下查询字符串参数:

参数
client_id 必需

应用的客户端 ID。您可以在 API Console Credentials page中找到此值。

redirect_uri 必需

确定在用户完成授权流程后,API 服务器会将用户重定向到哪里。该值必须与您在客户端的 API Console Credentials page中配置的 OAuth 2.0 客户端的其中一个已获授权的重定向 URI 完全匹配。如果此值与提供的 client_id 的授权重定向 URI 不匹配,您会收到 redirect_uri_mismatch 错误。

请注意,httphttps 架构、大小写和尾随斜杠(“/”)必须全部匹配。

response_type 必需

确定 Google OAuth 2.0 端点是否返回授权代码。

对于网络服务器应用,将参数值设为 code

scope 必需

以空格分隔的范围列表,用于标识您的应用可以代表用户访问的资源。这些值会通知 Google 向用户显示的同意屏幕。

通过范围,您的应用可以仅请求访问所需的资源,同时还可以控制用户向您的应用授予的访问权限大小。因此,请求的范围数量与征得用户同意的可能性之间存在反向关系。

建议您的应用尽可能尽快请求访问授权范围。通过在上下文中通过增量授权请求对用户数据的访问,您可以帮助用户更轻松地了解您的应用为何会请求其所请求的访问权限。

access_type 推荐

指示您的应用是否可以在浏览器不存在时刷新访问令牌。有效的参数值包括 online(默认值)和 offline

如果您的应用需要在用户不在浏览器时刷新访问令牌,请将该值设置为 offline。这是刷新本文档稍后所述的访问令牌的方法。此值会指示 Google 授权服务器在应用首次交换授权代码时返回刷新令牌和访问令牌。

state 推荐

指定您的应用用于维护授权请求与授权服务器响应之间的状态的任何字符串值。在用户同意或拒绝您的应用的访问请求之后,服务器将返回您在 redirect_uri 的网址查询组件 (?) 中作为 name=value 对发送的确切值。

您可以将此参数用于多种用途,例如将用户定向到应用中的正确资源、发送 Nonce 以及减少跨网站请求伪造。由于您的 redirect_uri 是可以猜测的,因此使用 state 值可提高您对传入连接是否是身份验证请求结果的保证。如果您生成随机字符串,或对捕获客户端状态的 Cookie 或其他值进行哈希处理,则可以验证响应,以进一步确保请求和响应来自同一浏览器,从而防范跨站请求伪造等攻击。如需查看有关如何创建和确认 state 令牌的示例,请参阅 OpenID Connect 文档。

include_granted_scopes 可选

允许应用使用增量授权在上下文中请求访问其他范围。如果您将此参数的值设为 true,且授权请求被授予,那么新的访问令牌也将涵盖用户之前向其授予应用访问权限的所有范围。有关示例,请参阅增量授权部分。

login_hint 可选

如果您的应用知道哪个用户正在尝试进行身份验证,它可以使用该参数为 Google 身份验证服务器提供提示。服务器会使用此提示来简化登录流程,具体做法是填写登录表单中的电子邮件字段,或选择相应的多帐号登录会话。

将参数值设为电子邮件地址或 sub 标识符(等同于用户的 Google ID)。

prompt 可选

一系列以空格分隔且区分大小写的提示列表,用于向用户展示。如果您未指定此参数,则系统只会在您的项目首次请求访问权限时提示用户。如需了解详情,请参阅 提示重新同意

可能的值包括:

none 不显示任何身份验证或同意屏幕。不得使用其他值指定。
consent 提示用户同意。
select_account 提示用户选择帐号。

第 2 步:重定向至 Google 的 OAuth 2.0 服务器

将用户重定向到 Google 的 OAuth 2.0 服务器,以启动身份验证和授权流程。通常,当您的应用首次需要访问用户数据时,就会发生这种情况。对于增量授权,当您的应用首次需要访问其尚未有权访问的其他资源时,也会执行此步骤。

PHP

  1. 生成网址以向 Google 的 OAuth 2.0 服务器请求访问权限:
    $auth_url = $client->createAuthUrl();
  2. 将用户重定向到 $auth_url
    header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));

Python

此示例展示了如何使用 Flask Web 应用框架将用户重定向到授权网址:

return flask.redirect(authorization_url)

Ruby

  1. 生成一个网址,以便向 Google 的 OAuth 2.0 服务器请求访问权限:
    auth_uri = auth_client.authorization_uri.to_s
  2. 将用户重定向到 auth_uri

Node.js

  1. 使用第 1 步中生成的 generateAuthUrl 方法 authorizationUrl 向 Google 的 OAuth 2.0 服务器请求访问权限。
  2. 将用户重定向到 authorizationUrl
    res.writeHead(301, { "Location": authorizationUrl });

HTTP/REST

Sample redirect to Google's authorization server

An example URL is shown below, with line breaks and spaces for readability.

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly&
 access_type=offline&
 include_granted_scopes=true&
 response_type=code&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//oauth2.example.com/code&
 client_id=client_id

创建请求网址后,请将用户重定向到该网址。

Google 的 OAuth 2.0 服务器会验证用户身份,并征得用户同意,允许应用访问所请求的范围。系统会使用您指定的重定向网址将响应发回您的应用。

第 3 步:Google 提示用户同意

在此步骤中,用户决定是否向您的应用请求访问。在此阶段,Google 会显示一个同意窗口,其中会显示您的应用名称以及使用用户的授权凭据请求访问的 Google API 服务以及要授予的访问权限范围摘要。然后,用户可以同意向您应用请求的一个或多个范围授予访问权限,或拒绝该请求。

在此阶段,您的应用无需执行任何操作,因为它等待 Google 的 OAuth 2.0 服务器的响应,以表明是否已授予任何访问权限。下一步中会说明该响应。

错误

对 Google 的 OAuth 2.0 授权端点的请求可能会显示面向用户的错误消息,而不是预期的身份验证和授权流程。下面列出了常见的错误代码以及建议的解决方法。

admin_policy_enforced

由于用户的 Google Workspace 管理员政策,其 Google 帐号无法向请求的一个或多个范围授权。请参阅 Google Workspace 管理员帮助文章 控制哪些第三方应用和内部应用可以访问 Google Workspace 数据。详细了解管理员如何限制对所有范围或者敏感和受限范围的访问权限,直到明确向您的 OAuth 客户端 ID 授予访问权限为止。

disallowed_useragent

授权端点会显示在 Google OAuth 2.0 政策所禁止的嵌入式用户代理中。

Android

Android 开发者在 android.webkit.WebView 中打开授权请求时可能会遇到此错误消息。开发者应改用 Android 库,如 Google 登录 Android 版或 OpenID Foundation 的 AppAuth for Android

当 Android 应用在嵌入式用户代理中打开常规 Web 链接,并且用户从您的网站转到 Google 的 OAuth 2.0 授权端点时,Web 开发者可能会遇到此错误。开发者应允许操作系统的默认链接处理程序(包括 Android App Links 处理程序或默认浏览器应用)中打开常规链接。Android 自定义标签页库也受支持。

iOS

iOS 和 macOS 开发者在 WKWebView 中打开授权请求时可能会遇到此错误。开发者应改用 iOS 库,例如 iOS 版 Google 登录或 OpenID Foundation 的 AppAuth for iOS

当 iOS 或 macOS 应用打开嵌入式用户代理中的常规 Web 链接,并且用户从您的网站转到 Google 的 OAuth 2.0 授权端点时,Web 开发者可能会遇到此错误。开发者应允许操作系统的默认链接处理程序(包括通用链接处理程序或默认浏览器应用)中打开常规链接。SFSafariViewController 库也是一个受支持的选项。

org_internal

请求中的 OAuth 客户端 ID 属于某个项目,该项目限制了对特定 Google Cloud 组织中的 Google 帐号的访问。如需详细了解此配置选项,请参阅“设置 OAuth 权限请求页面”帮助文章中的用户类型部分。

invalid_client

OAuth 客户端密钥不正确。查看 OAuth 客户端配置,包括用于此请求的客户端 ID 和密钥。

invalid_grant

刷新访问令牌或使用增量授权时,令牌可能已过期或已失效。 再次验证用户身份,并请求用户同意以获取新令牌。如果仍看到此错误,请确保您的应用已正确配置,并且您在请求中使用了正确的令牌和参数。否则,用户帐号可能已被删除或停用。

redirect_uri_mismatch

授权请求中传递的 redirect_uri 与 OAuth 客户端 ID 的已获授权的重定向 URI 不匹配。查看 Google API Console Credentials page中已获授权的重定向 URI。

redirect_uri 参数可能是指已废弃且不再受支持的 OAuth 带外 (OOB) 流程。请参阅迁移指南以更新您的集成。

第 4 步:处理 OAuth 2.0 服务器响应

OAuth 2.0 服务器会使用请求中指定的网址响应应用的访问权限请求。

如果用户批准了访问请求,响应中就会包含授权代码。如果用户未批准该请求,响应中会包含错误消息。返回网络服务器的授权代码或错误消息会显示在查询字符串中,如下所示:

错误响应:

https://oauth2.example.com/auth?error=access_denied

授权代码响应:

https://oauth2.example.com/auth?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7

OAuth 2.0 服务器响应示例

您可以点击以下示例网址请求此流程,以便请求获取对 Google 云端硬盘中文件的元数据的只读权限:

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly&
 access_type=offline&
 include_granted_scopes=true&
 response_type=code&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//oauth2.example.com/code&
 client_id=client_id

完成 OAuth 2.0 流程后,系统应该会将您重定向到 http://localhost/oauth2callback,除非您在本地计算机上使用该地址提供文件,否则这可能会导致 404 NOT FOUND 错误。下一步会更加详细地介绍当用户重定向回您的应用时在 URI 中返回的信息。

第 5 步:使用授权代码交换刷新令牌和访问令牌

网络服务器收到授权代码后,可以使用授权代码来换取访问令牌。

PHP

如需用授权代码换取访问令牌,请使用 authenticate 方法:

$client->authenticate($_GET['code']);

您可以使用 getAccessToken 方法检索访问令牌:

$access_token = $client->getAccessToken();

Python

在回调页面上,使用 google-auth 库验证授权服务器响应。然后,使用 flow.fetch_token 方法用响应中的授权代码交换访问令牌:

state = flask.session['state']
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'],
    state=state)
flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)

# Store the credentials in the session.
# ACTION ITEM for developers:
#     Store user's access and refresh tokens in your data store if
#     incorporating this code into your real app.
credentials = flow.credentials
flask.session['credentials'] = {
    'token': credentials.token,
    'refresh_token': credentials.refresh_token,
    'token_uri': credentials.token_uri,
    'client_id': credentials.client_id,
    'client_secret': credentials.client_secret,
    'scopes': credentials.scopes}

Ruby

如需用授权代码换取访问令牌,请使用 fetch_access_token! 方法:

auth_client.code = auth_code
auth_client.fetch_access_token!

Node.js

如需用授权代码换取访问令牌,请使用 getToken 方法:

const url = require('url');

// Receive the callback from Google's OAuth 2.0 server.
if (req.url.startsWith('/oauth2callback')) {
  // Handle the OAuth 2.0 server response
  let q = url.parse(req.url, true).query;

  // Get access and refresh tokens (if access_type is offline)
  let { tokens } = await oauth2Client.getToken(q.code);
  oauth2Client.setCredentials(tokens);
}

HTTP/REST

如需用授权代码换取访问令牌,请调用 https://oauth2.googleapis.com/token 端点并设置以下参数:

字段
client_id 从 API Console Credentials page获取的客户端 ID。
client_secret 从 API Console Credentials page获取的客户端密钥。
code 初始请求返回的授权代码。
grant_type 如 OAuth 2.0 规范中所定义,此字段的值必须设置为 authorization_code
redirect_uri 在 API Console Credentials page 中,为给定 client_id 为您的项目列出的其中一个重定向 URI。

以下代码段展示了一个示例请求:

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https%3A//oauth2.example.com/code&
grant_type=authorization_code

Google 通过返回包含短期访问令牌和刷新令牌的 JSON 对象来响应此请求。请注意,只有在您的应用向 Google 授权服务器发送的初始请求中将 access_type 参数设为 offline 时,系统才会返回刷新令牌。

响应包含以下字段:

字段
access_token 您的应用发送的用于授权 Google API 请求的令牌。
expires_in 访问令牌的剩余生命周期(以秒为单位)。
refresh_token 可用于获取新访问令牌的令牌。刷新令牌在用户撤销访问权限之前一直有效。 同样,如果您在向 Google 授权服务器发出的初始请求中将 access_type 参数设置为 offline,那么此字段只会在此响应中显示。
scope access_token 授予的访问权限范围,表示为一系列以空格分隔的区分大小写的字符串。
token_type 返回的令牌类型。目前,此字段的值始终设置为 Bearer

以下代码段显示了一个响应示例:

{
  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "token_type": "Bearer",
  "scope": "https://www.googleapis.com/auth/drive.metadata.readonly",
  "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}

错误

用授权代码换取访问令牌时,您可能会遇到以下错误,而不是预期响应。下面列出了常见的错误代码以及建议的解决方法。

invalid_grant

提供的授权代码无效或格式有误。通过重启 OAuth 流程请求新的代码,以便再次提示用户同意。

调用 Google API

PHP

使用访问令牌,通过完成以下步骤来调用 Google API:

  1. 如果您需要将访问令牌应用于新的 Google\Client 对象(例如,已将访问令牌存储在用户会话中),请使用 setAccessToken 方法:
    $client->setAccessToken($access_token);
  2. 为您要调用的 API 构建服务对象。您可以通过向已调用的 API 的构造函数提供已获授权的 Google\Client 对象来构建服务对象。例如,如需调用 Drive API:
    $drive = new Google\Service\Drive($client);
  3. 使用服务对象提供的接口向 API 服务发出请求。例如,如需列出已通过身份验证的用户的 Google 云端硬盘中的文件,请执行以下操作:
    $files = $drive->files->listFiles(array())->getItems();

Python

获得访问令牌后,您的应用可以使用该令牌代表指定的用户帐号或服务帐号为 API 请求授权。使用特定于用户的授权凭据为您要调用的 API 构建服务对象,然后使用该对象发出已获授权的 API 请求。

  1. 为您要调用的 API 构建服务对象。如需构建服务对象,您可以使用 API 的名称和用户凭据调用 googleapiclient.discovery 库的 build 方法: 例如,如需调用 Drive API 的版本 2,请执行以下操作:
    from googleapiclient.discovery import build
    
    drive = build('drive', 'v2', credentials=credentials)
  2. 使用服务对象提供的接口向 API 服务发出请求。例如,如需列出已通过身份验证的用户的 Google 云端硬盘中的文件,请执行以下操作:
    files = drive.files().list().execute()

Ruby

完成以下步骤,使用 auth_client 对象调用 Google API:

  1. 为您要调用的 API 构建服务对象。例如,如需调用 Drive API 版本 2,请运行以下命令:
    drive = Google::Apis::DriveV2::DriveService.new
  2. 在服务上设置凭据:
    drive.authorization = auth_client
  3. 使用服务对象提供的接口向 API 服务发出请求。例如,如需列出已通过身份验证的用户的 Google 云端硬盘中的文件,请运行以下命令:
    files = drive.list_files

或者,您可以通过向方法提供 options 参数,为每个方法提供授权:

files = drive.list_files(options: { authorization: auth_client })

Node.js

获取访问令牌并将其设置为 OAuth2 对象后,请使用该对象调用 Google API。您的应用可以使用该令牌代表指定的用户帐号或服务帐号为 API 请求授权。为您要调用的 API 构建服务对象。

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

// Example of using Google Drive API to list filenames in user's Drive.
const drive = google.drive('v3');
drive.files.list({
  auth: oauth2Client,
  pageSize: 10,
  fields: 'nextPageToken, files(id, name)',
}, (err1, res1) => {
  if (err1) return console.log('The API returned an error: ' + err1);
  const files = res1.data.files;
  if (files.length) {
    console.log('Files:');
    files.map((file) => {
      console.log(`${file.name} (${file.id})`);
    });
  } else {
    console.log('No files found.');
  }
});

HTTP/REST

在您的应用获得访问令牌后,您可以使用该令牌代表指定用户帐号调用 Google API(如果 API 所需的访问权限范围已被授予的话)。为此,请在向 API 发出的请求中添加访问令牌,具体方法是添加 access_token 查询参数或 Authorization HTTP 标头 Bearer 值。请尽可能使用 HTTP 标头,因为查询字符串通常会显示在服务器日志中。在大多数情况下,您可以使用客户端库来设置对 Google API 的调用(例如,在调用 Drive Files API 时)。

您可以访问 OAuth 2.0 Playground,试用所有 Google API 并查看其范围。

HTTP GET 示例

使用 Authorization: Bearer HTTP 标头调用 drive.files 端点 (Drive Files API) 的过程可能如下所示。请注意,您需要指定自己的访问令牌:

GET /drive/v2/files HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer access_token

以下是使用 access_token 查询字符串参数对经过身份验证的用户的同一 API 的调用:

GET https://www.googleapis.com/drive/v2/files?access_token=access_token

curl 示例

您可以使用 curl 命令行应用测试这些命令。下面是一个使用 HTTP 标头选项(首选)的示例:

curl -H "Authorization: Bearer access_token" https://www.googleapis.com/drive/v2/files

或者,查询字符串参数选项:

curl https://www.googleapis.com/drive/v2/files?access_token=access_token

完整示例

以下示例会在用户通过身份验证并允许应用访问用户的云端硬盘元数据后,在用户的 Google 云端硬盘中输出 JSON 格式的文件列表。

PHP

如需运行此示例,请执行以下操作:

  1. 在 API Console中,将本地机器的网址添加到重定向网址列表中。例如,添加 http://localhost:8080
  2. 创建一个新目录,然后切换到该目录。例如:
    mkdir ~/php-oauth2-example
    cd ~/php-oauth2-example
  3. 使用 Composer 安装 PHP 版 Google API 客户端库
    composer require google/apiclient:^2.10
  4. 使用以下内容创建 index.phpoauth2callback.php 文件。
  5. 运行配置为提供 PHP 的网络服务器示例。如果您使用 PHP 5.6 或更高版本,则可以使用 PHP 的内置测试 Web 服务器:
    php -S localhost:8080 ~/php-oauth2-example

index.php

<?php
require_once __DIR__.'/vendor/autoload.php';

session_start();

$client = new Google\Client();
$client->setAuthConfig('client_secrets.json');
$client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY);

if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
  $client->setAccessToken($_SESSION['access_token']);
  $drive = new Google\Service\Drive($client);
  $files = $drive->files->listFiles(array())->getItems();
  echo json_encode($files);
} else {
  $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php';
  header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}

oauth2callback.php

<?php
require_once __DIR__.'/vendor/autoload.php';

session_start();

$client = new Google\Client();
$client->setAuthConfigFile('client_secrets.json');
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php');
$client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY);

if (! isset($_GET['code'])) {
  $auth_url = $client->createAuthUrl();
  header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
  $client->authenticate($_GET['code']);
  $_SESSION['access_token'] = $client->getAccessToken();
  $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/';
  header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}

Python

此示例使用 Flask 框架。它在 http://localhost:8080 中运行一个 Web 应用,可用于测试 OAuth 2.0 流程。如果您转到该网址,应该会看到四个链接:

  • 测试 API 请求:此链接指向尝试执行示例 API 请求的页面。必要时,它会启动授权流程。如果成功,页面会显示 API 响应。
  • 直接测试身份验证流程:此链接指向尝试通过授权流程将用户引导至的网页。应用请求相应权限,以代表用户提交已获授权的 API 请求。
  • 撤消当前凭据:此链接指向的页面会 撤消用户已向应用授予的权限。
  • 清除 Flask 会话凭据:此链接会清除 Flask 会话中存储的授权凭据。这样一来,如果用户已授权您的应用尝试在新会话中执行 API 请求,会发生什么情况。它还可以让您看到,如果用户已撤消向您的应用授予的权限,并且您的应用仍尝试使用授权的访问令牌为请求提供授权,您的应用会收到 API 响应。
# -*- coding: utf-8 -*-

import os
import flask
import requests

import google.oauth2.credentials
import google_auth_oauthlib.flow
import googleapiclient.discovery

# This variable specifies the name of a file that contains the OAuth 2.0
# information for this application, including its client_id and client_secret.
CLIENT_SECRETS_FILE = "client_secret.json"

# This OAuth 2.0 access scope allows for full read/write access to the
# authenticated user's account and requires requests to use an SSL connection.
SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly']
API_SERVICE_NAME = 'drive'
API_VERSION = 'v2'

app = flask.Flask(__name__)
# Note: A secret key is included in the sample so that it works.
# If you use this code in your application, replace this with a truly secret
# key. See https://flask.palletsprojects.com/quickstart/#sessions.
app.secret_key = 'REPLACE ME - this value is here as a placeholder.'


@app.route('/')
def index():
  return print_index_table()


@app.route('/test')
def test_api_request():
  if 'credentials' not in flask.session:
    return flask.redirect('authorize')

  # Load credentials from the session.
  credentials = google.oauth2.credentials.Credentials(
      **flask.session['credentials'])

  drive = googleapiclient.discovery.build(
      API_SERVICE_NAME, API_VERSION, credentials=credentials)

  files = drive.files().list().execute()

  # Save credentials back to session in case access token was refreshed.
  # ACTION ITEM: In a production app, you likely want to save these
  #              credentials in a persistent database instead.
  flask.session['credentials'] = credentials_to_dict(credentials)

  return flask.jsonify(**files)


@app.route('/authorize')
def authorize():
  # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      CLIENT_SECRETS_FILE, scopes=SCOPES)

  # The URI created here must exactly match one of the authorized redirect URIs
  # for the OAuth 2.0 client, which you configured in the API Console. If this
  # value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch'
  # error.
  flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

  authorization_url, state = flow.authorization_url(
      # Enable offline access so that you can refresh an access token without
      # re-prompting the user for permission. Recommended for web server apps.
      access_type='offline',
      # Enable incremental authorization. Recommended as a best practice.
      include_granted_scopes='true')

  # Store the state so the callback can verify the auth server response.
  flask.session['state'] = state

  return flask.redirect(authorization_url)


@app.route('/oauth2callback')
def oauth2callback():
  # Specify the state when creating the flow in the callback so that it can
  # verified in the authorization server response.
  state = flask.session['state']

  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
  flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

  # Use the authorization server's response to fetch the OAuth 2.0 tokens.
  authorization_response = flask.request.url
  flow.fetch_token(authorization_response=authorization_response)

  # Store credentials in the session.
  # ACTION ITEM: In a production app, you likely want to save these
  #              credentials in a persistent database instead.
  credentials = flow.credentials
  flask.session['credentials'] = credentials_to_dict(credentials)

  return flask.redirect(flask.url_for('test_api_request'))


@app.route('/revoke')
def revoke():
  if 'credentials' not in flask.session:
    return ('You need to <a href="/authorize">authorize</a> before ' +
            'testing the code to revoke credentials.')

  credentials = google.oauth2.credentials.Credentials(
    **flask.session['credentials'])

  revoke = requests.post('https://oauth2.googleapis.com/revoke',
      params={'token': credentials.token},
      headers = {'content-type': 'application/x-www-form-urlencoded'})

  status_code = getattr(revoke, 'status_code')
  if status_code == 200:
    return('Credentials successfully revoked.' + print_index_table())
  else:
    return('An error occurred.' + print_index_table())


@app.route('/clear')
def clear_credentials():
  if 'credentials' in flask.session:
    del flask.session['credentials']
  return ('Credentials have been cleared.<br><br>' +
          print_index_table())


def credentials_to_dict(credentials):
  return {'token': credentials.token,
          'refresh_token': credentials.refresh_token,
          'token_uri': credentials.token_uri,
          'client_id': credentials.client_id,
          'client_secret': credentials.client_secret,
          'scopes': credentials.scopes}

def print_index_table():
  return ('<table>' +
          '<tr><td><a href="/test">Test an API request</a></td>' +
          '<td>Submit an API request and see a formatted JSON response. ' +
          '    Go through the authorization flow if there are no stored ' +
          '    credentials for the user.</td></tr>' +
          '<tr><td><a href="/authorize">Test the auth flow directly</a></td>' +
          '<td>Go directly to the authorization flow. If there are stored ' +
          '    credentials, you still might not be prompted to reauthorize ' +
          '    the application.</td></tr>' +
          '<tr><td><a href="/revoke">Revoke current credentials</a></td>' +
          '<td>Revoke the access token associated with the current user ' +
          '    session. After revoking credentials, if you go to the test ' +
          '    page, you should see an <code>invalid_grant</code> error.' +
          '</td></tr>' +
          '<tr><td><a href="/clear">Clear Flask session credentials</a></td>' +
          '<td>Clear the access token currently stored in the user session. ' +
          '    After clearing the token, if you <a href="/test">test the ' +
          '    API request</a> again, you should go back to the auth flow.' +
          '</td></tr></table>')


if __name__ == '__main__':
  # When running locally, disable OAuthlib's HTTPs verification.
  # ACTION ITEM for developers:
  #     When running in production *do not* leave this option enabled.
  os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

  # Specify a hostname and port that are set as a valid redirect URI
  # for your API project in the Google API Console.
  app.run('localhost', 8080, debug=True)

Ruby

此示例使用 Sinatra 框架。

require 'google/apis/drive_v2'
require 'google/api_client/client_secrets'
require 'json'
require 'sinatra'

enable :sessions
set :session_secret, 'setme'

get '/' do
  unless session.has_key?(:credentials)
    redirect to('/oauth2callback')
  end
  client_opts = JSON.parse(session[:credentials])
  auth_client = Signet::OAuth2::Client.new(client_opts)
  drive = Google::Apis::DriveV2::DriveService.new
  files = drive.list_files(options: { authorization: auth_client })
  "<pre>#{JSON.pretty_generate(files.to_h)}</pre>"
end

get '/oauth2callback' do
  client_secrets = Google::APIClient::ClientSecrets.load
  auth_client = client_secrets.to_authorization
  auth_client.update!(
    :scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
    :redirect_uri => url('/oauth2callback'))
  if request['code'] == nil
    auth_uri = auth_client.authorization_uri.to_s
    redirect to(auth_uri)
  else
    auth_client.code = request['code']
    auth_client.fetch_access_token!
    auth_client.client_secret = nil
    session[:credentials] = auth_client.to_json
    redirect to('/')
  end
end

Node.js

如需运行此示例,请执行以下操作:

  1. 在 API Console中,将本地机器的网址添加到重定向网址列表中。例如,添加 http://localhost
  2. 确保您已安装 Node.js 维护 LTS、有效 LTS 或当前版本。
  3. 创建一个新目录,然后切换到该目录。例如:
    mkdir ~/nodejs-oauth2-example
    cd ~/nodejs-oauth2-example
  4. Install the Google API Client Library for Node.js using npm:
    npm install googleapis
  5. 创建包含以下内容的 main.js 文件。
  6. 运行示例:
    node .\main.js

main.js

const http = require('http');
const https = require('https');
const url = require('url');
const { google } = require('googleapis');

/**
 * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI.
 * 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 Drive activity.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly'
];

// Generate a url that asks permissions for the Drive activity scope
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 a best practice.
  include_granted_scopes: true
});

/* Global variable that stores user credential in this code example.
 * ACTION ITEM for developers:
 *   Store user's refresh token in your data store if
 *   incorporating this code into your real app.
 *   For more information on handling refresh tokens,
 *   see https://github.com/googleapis/google-api-nodejs-client#handling-refresh-tokens
 */
let userCredential = null;

async function main() {
  const server = http.createServer(async function (req, res) {
    // Example on redirecting user to Google's OAuth 2.0 server.
    if (req.url == '/') {
      res.writeHead(301, { "Location": authorizationUrl });
    }

    // Receive the callback from Google's OAuth 2.0 server.
    if (req.url.startsWith('/oauth2callback')) {
      // Handle the OAuth 2.0 server response
      let q = url.parse(req.url, true).query;

      if (q.error) { // An error response e.g. error=access_denied
        console.log('Error:' + q.error);
      } else { // Get access and refresh tokens (if access_type is offline)
        let { tokens } = await oauth2Client.getToken(q.code);
        oauth2Client.setCredentials(tokens);

        /** Save credential to the global variable in case access token was refreshed.
          * ACTION ITEM: In a production app, you likely want to save the refresh token
          *              in a secure persistent database instead. */
        userCredential = tokens;

        // Example of using Google Drive API to list filenames in user's Drive.
        const drive = google.drive('v3');
        drive.files.list({
          auth: oauth2Client,
          pageSize: 10,
          fields: 'nextPageToken, files(id, name)',
        }, (err1, res1) => {
          if (err1) return console.log('The API returned an error: ' + err1);
          const files = res1.data.files;
          if (files.length) {
            console.log('Files:');
            files.map((file) => {
              console.log(`${file.name} (${file.id})`);
            });
          } else {
            console.log('No files found.');
          }
        });
      }
    }

    // Example on revoking a token
    if (req.url == '/revoke') {
      // Build the string for the POST request
      let postData = "token=" + userCredential.access_token;

      // Options for POST request to Google's OAuth 2.0 server to revoke a token
      let postOptions = {
        host: 'oauth2.googleapis.com',
        port: '443',
        path: '/revoke',
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': Buffer.byteLength(postData)
        }
      };

      // Set up the request
      const postReq = https.request(postOptions, function (res) {
        res.setEncoding('utf8');
        res.on('data', d => {
          console.log('Response: ' + d);
        });
      });

      postReq.on('error', error => {
        console.log(error)
      });

      // Post the request with data
      postReq.write(postData);
      postReq.end();
    }
    res.end();
  }).listen(80);
}
main().catch(console.error);

HTTP/REST

此 Python 示例使用 Flask 框架和 Requests 库来演示 OAuth 2.0 Web 流程。我们建议在此流程中使用 Python 版 Google API 客户端库。(Python 标签页中的示例使用的是客户端库。)

import json

import flask
import requests


app = flask.Flask(__name__)

CLIENT_ID = '123456789.apps.googleusercontent.com'
CLIENT_SECRET = 'abc123'  # Read from a file or environmental variable in a real app
SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly'
REDIRECT_URI = 'http://example.com/oauth2callback'


@app.route('/')
def index():
  if 'credentials' not in flask.session:
    return flask.redirect(flask.url_for('oauth2callback'))
  credentials = json.loads(flask.session['credentials'])
  if credentials['expires_in'] <= 0:
    return flask.redirect(flask.url_for('oauth2callback'))
  else:
    headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])}
    req_uri = 'https://www.googleapis.com/drive/v2/files'
    r = requests.get(req_uri, headers=headers)
    return r.text


@app.route('/oauth2callback')
def oauth2callback():
  if 'code' not in flask.request.args:
    auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
                '&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE)
    return flask.redirect(auth_uri)
  else:
    auth_code = flask.request.args.get('code')
    data = {'code': auth_code,
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET,
            'redirect_uri': REDIRECT_URI,
            'grant_type': 'authorization_code'}
    r = requests.post('https://oauth2.googleapis.com/token', data=data)
    flask.session['credentials'] = r.text
    return flask.redirect(flask.url_for('index'))


if __name__ == '__main__':
  import uuid
  app.secret_key = str(uuid.uuid4())
  app.debug = False
  app.run()

重定向 URI 验证规则

Google 会应用以下验证规则来重定向 URI,以帮助开发者确保其应用安全无虞。您的重定向 URI 必须遵守这些规则。如需了解网域、主机、路径、查询、架构和 userinfo 的定义,请参阅 RFC 3986 第 3 节

验证规则
架构

重定向 URI 必须使用 HTTPS 架构,而不是普通的 HTTP。本地主机 URI(包括 localhost IP 地址 URI)不受此规则约束。

主机

主机不能是原始 IP 地址。本地主机 IP 地址不受此规则约束。

网域
  • 主机 TLD(顶级域名)必须属于公共后缀列表
  • 托管域名不能为 “googleusercontent.com”
  • 重定向 URI 不能包含网址缩短网域(例如 goo.gl),除非应用拥有该网域。此外,如果拥有较短域名的网域应用选择重定向到该网域,则该重定向 URI 必须在其路径中包含 “/google-callback/” 或以 “/google-callback” 结尾。
  • 用户信息

    重定向 URI 不能包含 userinfo 子组件。

    路径

    重定向 URI 不能包含路径遍历(也称为目录反向跟踪),由 “/..”“\..” 或其网址编码表示。

    查询

    重定向 URI 不能包含开放重定向

    fragment

    重定向 URI 不能包含 fragment 组件。

    字符 重定向 URI 不能包含某些字符,包括:
    • 通配符 ('*')
    • 不可打印的 ASCII 字符
    • 百分号编码无效(百分号编码未遵循百分号后紧跟两个十六进制数字的网址编码形式)
    • Null 字符(已编码的 NULL 字符,例如%00%C0%80

    增量授权

    在 OAuth 2.0 协议中,您的应用会请求访问由范围标识的资源。我们认为最好的做法是在您需要时请求资源授权。为了支持这种做法,Google 的授权服务器支持增量授权。通过此功能,您可以根据需要请求范围,如果用户向新范围授予权限,则会返回授权代码,该代码可能会交换包含用户已授予项目的所有范围的令牌。

    例如,一款允许用户试听音乐曲目和创建合辑的应用在登录时可能需要的资源非常少,可能只需要登录者的姓名。不过,保存完成后的合辑需要访问其 Google 云端硬盘的权限。对于大多数用户而言,如果他们仅在应用确实需要的时候访问 Google 云端硬盘,就会觉得非常自然。

    在这种情况下,应用在登录时,可能会请求 openidprofile 范围来执行基本登录,然后在第一次请求保存组合时请求 https://www.googleapis.com/auth/drive.file 范围。

    要实现增量授权,您需要按常规流程请求访问令牌,但请确保授权请求包含先前授予的范围。通过此方法,您的应用可以避免管理多个访问令牌。

    以下规则适用于通过增量授权获取的访问令牌:

    • 此令牌可用于访问与新合并的授权中任何范围对应的资源。
    • 当您通过合并授权的刷新令牌获取访问令牌时,访问令牌代表合并授权,可用于响应中包含的任何 scope 值。
    • 合并授权包含用户向 API 项目授予的所有范围,即使授权是从不同的客户端请求的。例如,如果用户使用应用的桌面客户端向一个范围授予访问权限,然后通过移动客户端向同一应用授予另一个范围,则合并的授权将同时包含这两个范围。
    • 如果您撤消代表合并授权的令牌,则系统将代表关联用户同时撤消该授权对所有范围的访问权限。

    第 1 步:设置授权参数中特定于语言的代码示例以及第 2 步:重定向到 Google 的 OAuth 2.0 服务器中的示例 HTTP/REST 重定向网址都使用了增量授权。下面的代码示例还显示了添加增量授权所需的代码。

    PHP

    $client->setIncludeGrantedScopes(true);

    Python

    在 Python 中,将 include_granted_scopes 关键字参数设置为 true,以确保授权请求包含之前授予的范围。include_granted_scopes 很可能不是您设置的唯一关键字参数,如以下示例所示。

    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='true')

    Ruby

    auth_client.update!(
      :additional_parameters => {"include_granted_scopes" => "true"}
    )

    Node.js

    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 a best practice.
      include_granted_scopes: true
    });
    

    HTTP/REST

    GET https://accounts.google.com/o/oauth2/v2/auth?
      client_id=your_client_id&
      response_type=code&
      state=state_parameter_passthrough_value&
      scope=https%3A//www.googleapis.com/auth/drive.file&
      redirect_uri=https%3A//oauth2.example.com/code&
      prompt=consent&
      include_granted_scopes=true

    刷新访问令牌(离线访问)

    访问令牌会定期过期,并成为相关 API 请求的无效凭据。如果您请求访问与令牌关联的范围,则可以刷新访问令牌,而无需提示用户授予权限(包括当用户不在场时)。

    • 如果您使用 Google API 客户端库,只要您为离线配置对象配置访问令牌,客户端对象就会根据需要刷新访问令牌。
    • 如果您未使用客户端库,则需要在将用户重定向到 Google 的 OAuth 2.0 服务器时,将 access_type HTTP 查询参数设置为 offline。在这种情况下,当您用授权代码交换访问令牌时,Google 的授权服务器会返回刷新令牌。然后,如果访问令牌过期(或任何其他时间),您可以使用刷新令牌来获取新的访问令牌。

    对于需要在用户未存在时访问 Google API 的任何应用,都必须请求离线访问。例如,如果应用执行备份服务或在预定时间执行操作,则需要在用户未出现时刷新其访问令牌。默认访问权限样式称为 online

    服务器端 Web 应用、已安装的应用和设备均在授权过程中获得刷新令牌。刷新令牌通常不用于客户端 (JavaScript) Web 应用。

    PHP

    如果您的应用需要离线访问 Google API,请将 API 客户端的访问权限类型设置为 offline

    $client->setAccessType("offline");

    用户向请求的范围授予离线访问权限后,您可以在用户离线时继续使用 API 客户端代表用户访问 Google API。客户端对象会根据需要刷新访问令牌。

    Python

    在 Python 中,将 access_type 关键字参数设置为 offline,以确保您将能够刷新访问令牌,而无需重新提示用户授予权限。access_type 很可能不是您设置的唯一关键字参数,如以下示例所示。

    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='true')

    用户向请求的范围授予离线访问权限后,您可以在用户离线时继续使用 API 客户端代表用户访问 Google API。客户端对象会根据需要刷新访问令牌。

    Ruby

    如果您的应用需要离线访问 Google API,请将 API 客户端的访问权限类型设置为 offline

    auth_client.update!(
      :additional_parameters => {"access_type" => "offline"}
    )

    用户向请求的范围授予离线访问权限后,您可以在用户离线时继续使用 API 客户端代表用户访问 Google API。客户端对象会根据需要刷新访问令牌。

    Node.js

    如果您的应用需要离线访问 Google API,请将 API 客户端的访问权限类型设置为 offline

    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 a best practice.
      include_granted_scopes: true
    });
    

    用户向请求的范围授予离线访问权限后,您可以在用户离线时继续使用 API 客户端代表用户访问 Google API。客户端对象会根据需要刷新访问令牌。

    访问令牌到期。该库将自动使用刷新令牌获取即将过期的新访问令牌。若要确保始终存储最新的令牌,一种简单的方法是使用令牌事件:

    oauth2Client.on('tokens', (tokens) => {
      if (tokens.refresh_token) {
        // store the refresh_token in your secure persistent database
        console.log(tokens.refresh_token);
      }
      console.log(tokens.access_token);
    });

    此令牌事件仅出现在第一次授权中,并且您在调用 generateAuthUrl 方法以接收刷新令牌时需要将 access_type 设置为 offline。如果您已向应用授予必要的权限,但未设置接收刷新令牌的相应限制,则需要重新授权应用接收新的刷新令牌。

    如需稍后设置 refresh_token,您可以使用 setCredentials 方法:

    oauth2Client.setCredentials({
      refresh_token: `STORED_REFRESH_TOKEN`
    });
    

    客户端获得刷新令牌后,系统就会获取访问令牌并在下次调用 API 时自动刷新。

    HTTP/REST

    如需刷新访问令牌,您的应用会向 Google 的授权服务器 (https://oauth2.googleapis.com/token) 发送 HTTPS POST 请求,其中包含以下参数:

    字段
    client_id API Console获取的客户端 ID。
    client_secret API Console获取的客户端密钥。
    grant_type 根据 OAuth 2.0 规范的定义,此字段的值必须设置为 refresh_token
    refresh_token 从授权代码交换中返回的刷新令牌。

    以下代码段展示了一个示例请求:

    POST /token HTTP/1.1
    Host: oauth2.googleapis.com
    Content-Type: application/x-www-form-urlencoded
    
    client_id=your_client_id&
    client_secret=your_client_secret&
    refresh_token=refresh_token&
    grant_type=refresh_token

    只要用户尚未撤消授予应用的访问权限,令牌服务器就会返回一个包含新访问令牌的 JSON 对象。以下代码段是一个响应示例:

    {
      "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
      "expires_in": 3920,
      "scope": "https://www.googleapis.com/auth/drive.metadata.readonly",
      "token_type": "Bearer"
    }

    请注意,将颁发的刷新令牌数量有限;每个客户端/用户组合一个限制,所有客户端上的每个用户都不同。您应将刷新令牌保存在长期存储空间中,只要令牌仍然有效,就可以继续使用。如果您的应用请求刷新令牌的次数过多,则可能会遇到这些限制,在这种情况下,较旧的刷新令牌将停止工作。

    撤消令牌

    在某些情况下,用户可能希望撤消对应用的访问权限。用户可以通过访问 帐号设置来撤消访问权限。如需了解详情,请参阅有权访问您帐号的第三方网站和应用的“撤消网站或应用访问权限”部分

    应用也能够以编程方式撤消授予它的访问权限。用户退订、移除应用,或者应用所需的 API 资源发生了显著变化的情况,程序化撤消至关重要。换言之,移除流程的一部分可能包含 API 请求,以确保移除之前授予应用的权限。

    PHP

    如需以编程方式撤消令牌,请调用 revokeToken()

    $client->revokeToken();

    Python

    如需以编程方式撤消令牌,请向 https://oauth2.googleapis.com/revoke 发出包含令牌作为参数的请求,并设置 Content-Type 标头:

    requests.post('https://oauth2.googleapis.com/revoke',
        params={'token': credentials.token},
        headers = {'content-type': 'application/x-www-form-urlencoded'})

    Ruby

    如需以编程方式撤消令牌,请向 oauth2.revoke 端点发出 HTTP 请求:

    uri = URI('https://oauth2.googleapis.com/revoke')
    response = Net::HTTP.post_form(uri, 'token' => auth_client.access_token)
    

    该令牌可以是访问令牌,也可以是刷新令牌。如果此令牌是访问令牌,且具有对应的刷新令牌,则该令牌也会随之撤消。

    如果已成功撤消调用,则响应的状态代码是 200。对于错误条件,系统会返回状态代码 400 以及错误代码。

    Node.js

    如需以编程方式撤消令牌,请向 /revoke 端点发出 HTTPS POST 请求:

    const https = require('https');
    
    // Build the string for the POST request
    let postData = "token=" + userCredential.access_token;
    
    // Options for POST request to Google's OAuth 2.0 server to revoke a token
    let postOptions = {
      host: 'oauth2.googleapis.com',
      port: '443',
      path: '/revoke',
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(postData)
      }
    };
    
    // Set up the request
    const postReq = https.request(postOptions, function (res) {
      res.setEncoding('utf8');
      res.on('data', d => {
        console.log('Response: ' + d);
      });
    });
    
    postReq.on('error', error => {
      console.log(error)
    });
    
    // Post the request with data
    postReq.write(postData);
    postReq.end();
    

    token 参数可以是访问令牌或刷新令牌。如果此令牌是访问令牌,且具有对应的刷新令牌,则该令牌也会随之撤消。

    如果已成功撤消调用,则响应的状态代码是 200。对于错误条件,系统会返回状态代码 400 以及错误代码。

    HTTP/REST

    为了以编程方式撤消令牌,您的应用会向 https://oauth2.googleapis.com/revoke 发出请求,并将令牌添加为参数:

    curl -d -X -POST --header "Content-type:application/x-www-form-urlencoded" \
            https://oauth2.googleapis.com/revoke?token={token}

    该令牌可以是访问令牌,也可以是刷新令牌。如果此令牌是访问令牌,且具有相应的刷新令牌,则该令牌也会一并撤消。

    如果已成功撤消调用,则响应的 HTTP 状态代码为 200。对于错误情况,系统会返回 HTTP 状态代码 400 以及错误代码。