为网络服务器应用使用 OAuth 2.0

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

OAuth 2.0 允许用户与应用共享特定数据,同时 用户名、密码和其他隐私信息 例如,应用可以使用 OAuth 2.0 来获取权限 将视频上传到用户的 YouTube 频道。

此 OAuth 2.0 流程专门用于用户授权。专为应用而设计 来存储机密信息并保持状态。拥有适当授权的网络服务器 应用可以在用户与应用交互时或在用户交互后访问 API 已离开申请。

Web 服务器应用通常也使用 服务账号来授权 API 请求,尤其是在调用 Cloud API 以访问 基于项目的数据,而非特定于用户的数据。Web 服务器应用可以使用 Service 与用户授权结合使用。 请注意,YouTube Data API 仅支持 拥有并管理多个 YouTube 频道的 YouTube 内容所有者。 具体来说,内容所有者可以使用服务账号来调用 支持 onBehalfOfContentOwner 请求参数。

客户端库

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

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

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

前提条件

为您的项目启用 API

任何调用 Google API 的应用都需要在 API Console。

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

  1. Open the API Library 在 Google API Console。
  2. If prompted, select a project, or create a new one.
  3. 使用媒体库页面找到并启用 YouTube Data API。查找任何其他 以供您的应用使用并启用这些 API。

创建授权凭据

任何使用 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。

    我们建议您设计应用的身份验证端点, 确保应用不会将授权代码公开给 页面。

创建凭据后,请从 Google Cloud 控制台下载 client_secret.json 文件, API Console。将文件安全地存储在只有 应用可访问的资源

确定访问权限范围

范围让您的应用可以仅请求访问所需的资源,同时 让用户能够控制他们向您的应用授予的访问权限大小。因此, 请求的范围数量与可能性 征得用户同意。

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

我们还建议您的应用通过 增量授权流程,在此流程中,您的应用 在用户执行相关操作时请求访问用户数据。此最佳实践有助于用户更轻松地理解 以及应用需要获取其所请求访问权限的原因。

YouTube Data API v3 使用以下范围:

Scopes
https://www.googleapis.com/auth/youtubeManage your YouTube account
https://www.googleapis.com/auth/youtube.channel-memberships.creatorSee a list of your current active channel members, their current level, and when they became a member
https://www.googleapis.com/auth/youtube.force-sslSee, edit, and permanently delete your YouTube videos, ratings, comments and captions
https://www.googleapis.com/auth/youtube.readonlyView your YouTube account
https://www.googleapis.com/auth/youtube.uploadManage your YouTube videos
https://www.googleapis.com/auth/youtubepartnerView and manage your assets and associated content on YouTube
https://www.googleapis.com/auth/youtubepartner-channel-auditView private information of your YouTube channel relevant during the audit process with a YouTube partner

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.6 或更高版本
  • 适用于 Ruby 的 Google Auth 库:

    gem install googleauth
  • Sinatra Ruby Web 应用框架。

    gem install sinatra

Node.js

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

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

    npm install googleapis crypto express express-session

HTTP/REST

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

获取 OAuth 2.0 访问令牌

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

以下列表快速总结了这些步骤:

  1. 您的应用会标识其所需的权限。
  2. 您的应用程序将用户连同所请求的列表一起重定向到 Google。 权限。
  3. 用户决定是否向您的应用授予权限。
  4. 您的应用会了解用户的决定。
  5. 如果用户授予了所请求的权限,您的应用会检索 代表用户发出 API 请求。

第 1 步:设置授权参数

第一步是创建授权请求。该请求会设置 标识您的应用,并定义需要向用户授予的权限 部署应用

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

以下标签页定义了 Web 服务器应用支持的授权参数。通过 这些特定语言的示例还展示了如何使用客户端库或授权库 配置一个用于设置这些参数的对象。

PHP

以下代码段会创建一个 Google\Client() 对象,该对象定义了 参数。

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

例如,此代码会请求离线访问以管理用户的 YouTube 账号:

$client = new Google\Client();

// Required, call the setAuthConfig function to load authorization credentials from
// client_secret.json file.
$client->setAuthConfig('client_secret.json');

// Required, to set the scope value, call the addScope function
$client->addScope(GOOGLE_SERVICE_YOUTUBE::YOUTUBE_FORCE_SSL);

// Required, call the setRedirectUri function to specify a valid redirect URI for the
// provided client_id
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php');

// Recommended, 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');

// Recommended, call the setState function. Using a state value can increase your assurance that
// an incoming connection is the result of an authentication request.
$client->setState($sample_passthrough_value);

// Optional, if your application knows which user is trying to authenticate, it can use this
// parameter to provide a hint to the Google Authentication Server.
$client->setLoginHint('hint@example.com');

// Optional, call the setPrompt function to set "consent" will prompt the user for consent
$client->setPrompt('consent');

// Optional, call the setIncludeGrantedScopes function with true to enable incremental
// authorization
$client->setIncludeGrantedScopes(true);

Python

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

该代码构造了一个 Flow 对象,该对象使用 之前下载的 client_secret.json 文件中的信息 创建授权凭据。该对象还会标识 范围以及应用所请求权限的 auth 端点,用于处理来自 Google 的 OAuth 2.0 服务器的响应。最后,代码 设置可选的 access_typeinclude_granted_scopes 参数。

例如,此代码会请求离线访问以管理用户的 YouTube 账号:

import google.oauth2.credentials
import google_auth_oauthlib.flow

# Required, call the from_client_secrets_file method to retrieve the client ID from a
# client_secret.json file. The client ID (from that file) and access scopes are required. (You can
# also use the from_client_config method, which passes the client configuration as it originally
# appeared in a client secrets file but doesn't access the file itself.)
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/youtube.force-ssl'])

# Required, 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(
    # Recommended, 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',
    # Optional, enable incremental authorization. Recommended as a best practice.
    include_granted_scopes='true',
    # Optional, if your application knows which user is trying to authenticate, it can use this
    # parameter to provide a hint to the Google Authentication Server.
    login_hint='hint@example.com',
    # Optional, set prompt to 'consent' will prompt the user for consent
    prompt='consent')

Ruby

使用您创建的 client_secrets.json 文件,在 应用。配置客户端对象时,您可以指定应用进行相关操作所需的范围 以及应用程序身份验证端点的网址(用于处理响应) 访问 OAuth 2.0 服务器。

例如,此代码会请求离线访问以管理用户的 YouTube 账号:

require 'google/apis/youtube_v3'
require "googleauth"
require 'googleauth/stores/redis_token_store'

client_id = Google::Auth::ClientId.from_file('/path/to/client_secret.json')
scope = 'https://www.googleapis.com/auth/youtube.force-ssl'
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, '/oauth2callback')

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

Node.js

以下代码段会创建一个 google.auth.OAuth2 对象,该对象定义了 参数。

该对象使用 client_secret.json 文件中的信息来标识您的应用。接收者 向用户请求权限以检索访问令牌,那么您可以将用户重定向到同意页面。 若要创建意见征求页面网址,请执行以下操作:

const {google} = require('googleapis');
const crypto = require('crypto');
const express = require('express');
const session = require('express-session');

/**
 * 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 secure random state value.
const state = crypto.randomBytes(32).toString('hex');

// Store state in the session
req.session.state = state;

// 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,
  // Include the state parameter to reduce the risk of CSRF attacks.
  state: state
});

重要提示 - 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 服务器在用户完成 授权流程。该值必须与以下对象的某个授权重定向 URI 完全匹配: OAuth 2.0 客户端(在客户端的 API Console Credentials page。如果此值与 获得授权的重定向 URI,client_id 您将获得 redirect_uri_mismatch 个错误。

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

response_type 必需

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

对于 Web 服务器应用,请将此参数值设为 code

scope 必需

答 空格分隔 范围列表,用于标识您的应用可以在 。这些值会告知 Google 向 用户。

范围让您的应用可以仅请求访问所需的资源 同时让用户能够控制他们向您的网页授予 应用。因此,所请求的范围数量与 以及征得用户同意的可能性

YouTube Data API v3 使用以下范围:

Scopes
https://www.googleapis.com/auth/youtubeManage your YouTube account
https://www.googleapis.com/auth/youtube.channel-memberships.creatorSee a list of your current active channel members, their current level, and when they became a member
https://www.googleapis.com/auth/youtube.force-sslSee, edit, and permanently delete your YouTube videos, ratings, comments and captions
https://www.googleapis.com/auth/youtube.readonlyView your YouTube account
https://www.googleapis.com/auth/youtube.uploadManage your YouTube videos
https://www.googleapis.com/auth/youtubepartnerView and manage your assets and associated content on YouTube
https://www.googleapis.com/auth/youtubepartner-channel-auditView private information of your YouTube channel relevant during the audit process with a YouTube partner

OAuth 2.0 API 范围文档提供了 可用于访问 Google API 的完整范围列表。

我们建议您的应用在上下文中请求对授权范围的访问权限 。视情况请求访问用户数据,方法为: 增量授权,可以帮助用户更轻松地 了解您的应用为何需要其请求的访问权限。

access_type 建议

指示您的应用是否可以在用户不存在时刷新访问令牌 。有效的参数值为默认值 online 值,以及 offline

如果您的应用需要刷新访问令牌,请将该值设置为 offline 当用户不访问浏览器时触发。这是刷新访问权限的方法 令牌。此值指示 Google 授权 服务器返回刷新令牌访问令牌 应用使用授权代码交换令牌。

state 建议

指定应用用来维持 授权请求和授权服务器的响应。 服务器返回您作为 name=value 对在 网址查询组件 (?) redirect_uri(在用户同意或拒绝您的应用的声明之后) 权限申请。

此参数有多种用途,例如将用户定向到 您应用中的正确资源、发送 Nonce 以及缓解跨网站请求 。由于您的 redirect_uri 可以被猜到,因此使用 state 值可以更好地确保传入的连接是 身份验证请求。如果您生成了随机字符串或对 Cookie 的哈希值或 另一个用于捕获客户端状态的值,您可以验证对 此外,请确保请求和响应来自同一个浏览器, 以防范此类攻击,例如 跨网站请求 伪造行为。请参阅 OpenID Connect 关于如何创建和确认 state 令牌的示例的文档。

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 = authorizer.get_authorization_url(login_hint: user_id, request: request)
  2. 将用户重定向到 auth_uri

Node.js

  1. 使用在第 1 步中生成的网址 authorizationUrl generateAuthUrl 方法,用于向 Google 的 OAuth 2.0 服务器请求访问权限。
  2. 将用户重定向到 authorizationUrl
    res.redirect(authorizationUrl);

HTTP/REST

Sample redirect to Google's authorization server

The sample URL below requests offline access (access_type=offline) to a scope that permits access to view the user's YouTube account. It uses incremental authorization to ensure that the new access token covers any scopes to which the user previously granted the application access. The URL also sets values for the required redirect_uri, response_type, and client_id parameters as well as for the state parameter. The URL contains line breaks and spaces for readability.

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.readonly&
 access_type=offline&
 include_granted_scopes=true&
 state=state_parameter_passthrough_value&
 redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback&
 response_type=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 账号无法对所请求的一个或多个范围进行授权 Google Workspace 管理员。参阅 Google Workspace 管理员帮助文章 您可以控制哪些第三方和内部应用访问 Google Workspace 数据 ,详细了解管理员如何限制对所有范围或敏感 限制范围,直到明确授予对 OAuth 客户端 ID 的访问权限。

disallowed_useragent

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

Android

Android 开发者在以下位置打开授权请求时可能会遇到此错误消息: android.webkit.WebView。 开发者应该改用 Android 库,如 适用于 Android 的 Google 登录或 OpenID Foundation 的 适用于 Android 的 AppAuth

当 Android 应用在 用户从 Google 的 OAuth 2.0 授权端点转到 Google 的 OAuth 2.0 授权端点, 。开发者应该允许在 操作系统,其中包括 Android 应用链接 处理程序或默认浏览器应用。通过 Android 自定义标签页 也是一个受支持的选项。

iOS

iOS 和 macOS 开发者在以下位置打开授权请求时可能会遇到此错误: WKWebView。 开发者应该改用 iOS 库,如 iOS 版 Google 登录或 OpenID Foundation 的 适用于 iOS 的 AppAuth

当 iOS 或 macOS 应用在以下位置打开常规网页链接时,Web 开发者可能会遇到此错误 嵌入的用户代理,而用户从 Google 的 OAuth 2.0 授权端点 。开发者应该允许在 操作系统,其中包括 通用链接 处理程序或默认浏览器应用。通过 SFSafariViewController 也是一个受支持的选项。

org_internal

请求中的 OAuth 客户端 ID 属于某个项目 具体 Google Cloud Organization(Google Cloud 组织)。 有关此配置选项的详细信息,请参阅 用户类型 部分。

invalid_client

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

invalid_grant

刷新访问令牌时或使用 增量授权,那么令牌可能已过期或 已失效。 再次对用户进行身份验证,并请求用户同意以获取新令牌。如果您选择继续 请确定您的应用已正确配置, 在请求中使用正确的令牌和参数。否则,该用户账号可能具有 已被删除或停用。

redirect_uri_mismatch

授权请求中传递的 redirect_uri 与已获授权的 OAuth 客户端 ID 的重定向 URI。请在以下位置查看已获授权的重定向 URI: Google API Console Credentials page

redirect_uri 参数可以是指具有以下特征的 OAuth 带外 (OOB) 流程: 已弃用,不再受支持。请参阅 迁移指南,更新您的 集成。

invalid_request

您提出的请求出了点问题。造成这种情况的原因有很多:

  • 该请求的格式不正确
  • 请求缺少必需的参数
  • 请求使用的授权方法不受 Google 支持。验证您的 OAuth 集成使用推荐的集成方法

第 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%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.readonly&
 access_type=offline&
 include_granted_scopes=true&
 state=state_parameter_passthrough_value&
 redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback&
 response_type=code&
 client_id=client_id

完成 OAuth 2.0 流程后,系统应该会将您重定向至 http://localhost/oauth2callback,这样可能会产生 404 NOT FOUND 错误,除非您的本地计算机在该地址提供文件。通过 下一步会详细介绍用户 重定向回您的应用。

第 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/youtube.force-ssl'],
    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

在回调页面上,使用 googleauth 库验证授权服务器 响应。使用 authorizer.handle_auth_callback_deferred 方法保存 授权代码,然后重定向回最初请求授权的网址。本次 通过暂时将结果保存在用户会话中来延迟代码交换。

  target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request)
  redirect target_url

Node.js

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

const url = require('url');

// Receive the callback from Google's OAuth 2.0 server.
app.get('/oauth2callback', async (req, res) => {
  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 if (q.state !== req.session.state) { //check state value
    console.log('State mismatch. Possible CSRF attack');
    res.end('State mismatch. Possible CSRF attack');
  } else { // 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获取的客户 ID Credentials page
client_secret 从 API Console获取的客户端密钥 Credentials page
code 从初始请求返回的授权代码。
grant_type 如 OAuth 2.0 中所定义, 规范,则必须将此字段的值设置为 authorization_code
redirect_uri 在 API Console Credentials page 指定 client_id

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

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 对象来响应此请求 令牌和刷新令牌 请注意,只有在您的应用设置了 access_type 时,系统才会返回刷新令牌。 参数添加到 offline(在向 Google 的 授权服务器

响应包含以下字段:

字段
access_token 您的应用为授权 Google API 请求而发送的令牌。
expires_in 访问令牌的剩余生命周期(以秒为单位)。
refresh_token 可用于获取新访问令牌的令牌。刷新令牌有效期至 用户撤消访问权限。 同样,仅当您将 access_type 设置为 true 时,此响应中才会显示 参数传递给 Google 授权服务器的初始请求中的 offline
scope access_token 授予的访问权限范围,表示为 用空格分隔、区分大小写的字符串。
token_type 返回的令牌类型。目前,此字段的值始终设置为 Bearer

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

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

错误

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

invalid_grant

提供的授权代码无效或格式不正确。请在以下日期之前申请新验证码: 重新启动 OAuth 流程以提示用户同意 。

调用 Google API

PHP

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

  1. 如果您需要将访问令牌应用于新的 Google\Client 对象,请执行以下操作: 例如,如果您将访问令牌存储在用户会话中,请使用 setAccessToken 方法:
    $client->setAccessToken($access_token);
  2. 为要调用的 API 构建服务对象。你可以通过以下方式构建服务对象: 将已获授权的 Google\Client 对象提供给您 API 的构造函数 。 例如,如需调用 YouTube Data API,请使用以下代码:
    $youtube = new Google_Service_YouTube($client);
  3. 使用 该服务对象提供的接口。 例如,要检索有关已获授权用户的 YouTube 频道的数据,请使用以下代码:
    $channel = $youtube->channels->listChannels('snippet', array('mine' => $mine));

Python

获得访问令牌后,您的应用程序就可以使用该令牌对 上的 API 请求进行授权, 代表指定的用户账号或服务账号。使用特定于用户的授权凭据 为要调用的 API 构建一个服务对象,然后使用该对象 授权的 API 请求。

  1. 为要调用的 API 构建服务对象。你可以通过以下方式构建服务对象: 调用 googleapiclient.discovery 库的 build 方法 API 的名称和版本以及用户凭据: 例如,如需调用 YouTube Data API 版本 3:
    from googleapiclient.discovery import build
    
    youtube = build('youtube', 'v3', credentials=credentials)
  2. 使用 该服务对象提供的接口。 例如,要检索有关已获授权用户的 YouTube 频道的数据,请使用以下代码:
    channel = youtube.channels().list(mine=True, part='snippet').execute()

Ruby

获得访问令牌后,您的应用程序就可以使用该令牌在 代表指定的用户账号或服务账号。使用特定于用户的授权凭据 为要调用的 API 构建一个服务对象,然后使用该对象 授权的 API 请求。

  1. 为要调用的 API 构建服务对象。 例如,如需调用 YouTube Data API 版本 3:
    youtube = Google::Apis::YoutubeV3::YouTubeService.new
  2. 设置服务的凭据:
    youtube.authorization = credentials
  3. 使用 接口 提供的对象。 例如,要检索有关已获授权用户的 YouTube 频道的数据,请使用以下代码:
    channel = youtube.list_channels(part, :mine => mine)

或者,也可以按方法提供授权,方法为提供 options 参数:

channel = youtube.list_channels(part, :mine => mine, 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 所需的访问权限范围)。为此,请添加 通过添加 access_token 查询来获取对 API 的请求中的访问令牌 参数或 Authorization HTTP 标头 Bearer 值。如有可能, 最好使用 HTTP 标头,因为查询字符串通常显示在服务器日志中。大多数 可以使用客户端库来设置对 Google API 的调用(例如, 调用 YouTube Data API)。

请注意,YouTube Data API 仅支持 YouTube 服务账号 拥有和管理多个 YouTube 频道的内容所有者,例如 唱片公司和电影制片厂

您可以访问以下网址,试用所有 Google API 并查看其作用域: OAuth 2.0 Playground

HTTP GET 示例

youtube.channels 端点(YouTube Data API),并使用 Authorization: Bearer HTTP 可能如下所示。请注意,您需要指定自己的访问令牌:

GET /youtube/v3/channels?part=snippet&mine=true HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer access_token

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

GET https://www.googleapis.com/youtube/v3/channels?access_token=access_token&part=snippet&mine=true

curl 示例

您可以使用 curl 命令行应用测试这些命令。这里有 使用 HTTP 标头选项的示例(首选):

curl -H "Authorization: Bearer access_token" https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true

或者,也可以使用查询字符串参数选项:

curl https://www.googleapis.com/youtube/v3/channels?access_token=access_token&part=snippet&mine=true

完整示例

以下示例输出了一个显示信息的 JSON 格式对象 对用户的 YouTube 频道进行身份验证和授权后 应用程序来管理用户的 YouTube 账号。

PHP

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

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

if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
  $client->setAccessToken($_SESSION['access_token']);
  $youtube = new Google_Service_YouTube($client);
  $channel = $youtube->channels->listChannels('snippet', array('mine' => $mine));
  echo json_encode($channel);
} 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_YOUTUBE::YOUTUBE_FORCE_SSL);

if (! isset($_GET['code'])) {
  // Generate and set state value
  $state = bin2hex(random_bytes(16));
  $client->setState($state);
  $_SESSION['state'] = $state;

  $auth_url = $client->createAuthUrl();
  header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
  // Check the state value
  if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['state']) {
    die('State mismatch. Possible CSRF attack.');
  }
  $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 请求。
  • 撤消当前凭据:此链接指向的网页 撤消用户已授予应用的权限。
  • Clear Flask session credentials(清除 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/youtube.force-ssl']
API_SERVICE_NAME = 'youtube'
API_VERSION = 'v3'

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'])

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

  channel = youtube.channels().list(mine=True, part='snippet').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(**channel)


@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/youtube_v3'
require 'sinatra'
require 'googleauth'
require 'googleauth/stores/redis_token_store'

configure do
  enable :sessions

  set :client_id, Google::Auth::ClientId.from_file('/path/to/client_secret.json')
  set :scope, Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY
  set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
  set :authorizer, Google::Auth::WebUserAuthorizer.new(settings.client_id, settings.scope, settings.token_store, '/oauth2callback')
end

get '/' do
  user_id = settings.client_id.id
  credentials = settings.authorizer.get_credentials(user_id, request)
  if credentials.nil?
    redirect settings.authorizer.get_authorization_url(login_hint: user_id, request: request)
  end
  youtube = Google::Apis::YoutubeV3::YouTubeService.new
  channel = youtube.list_channels(part, :mine => mine, options: { authorization: auth_client })
  
  "<pre>#{JSON.pretty_generate(channel.to_h)}</pre>"
end

get '/oauth2callback' do
  target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request)
  redirect target_url
end

Node.js

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

  1. 在 API Console中,添加 添加到重定向网址列表。例如,添加 http://localhost
  2. 确保您拥有 已安装 Node.js。
  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');
const crypto = require('crypto');
const express = require('express');
const session = require('express-session');

/**
 * 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'
];
/* 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 app = express();

  app.use(session({
    secret: 'your_secure_secret_key', // Replace with a strong secret
    resave: false,
    saveUninitialized: false,
  }));

  // Example on redirecting user to Google's OAuth 2.0 server.
  app.get('/', async (req, res) => {
    // Generate a secure random state value.
    const state = crypto.randomBytes(32).toString('hex');
    // Store state in the session
    req.session.state = state;

    // 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,
      // Include the state parameter to reduce the risk of CSRF attacks.
      state: state
    });

    res.redirect(authorizationUrl);
  });

  // Receive the callback from Google's OAuth 2.0 server.
  app.get('/oauth2callback', async (req, res) => {
    // 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 if (q.state !== req.session.state) { //check state value
      console.log('State mismatch. Possible CSRF attack');
      res.end('State mismatch. Possible CSRF attack');
    } 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
  app.get('/revoke', async (req, res) => {
    // 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();
  });


  const server = http.createServer(app);
  server.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/youtube.force-ssl'
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/youtube/v3/channels/list'
    r = requests.get(req_uri, headers=headers)
    return r.text


@app.route('/oauth2callback')
def oauth2callback():
  if 'code' not in flask.request.args:
    state = str(uuid.uuid4())
    flask.session['state'] = state
    auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
                '&client_id={}&redirect_uri={}&scope={}&state={}').format(CLIENT_ID, REDIRECT_URI,
                                                                          SCOPE, state)
    return flask.redirect(auth_uri)
  else:
    if 'state' not in flask.request.args or flask.request.args['state'] != flask.session['state']:
      return 'State mismatch. Possible CSRF attack.', 400

    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 必须遵守这些规则。请参阅 RFC 3986 第 3 节 域名、主机、路径、查询、架构和 userinfo 的定义(如下所述)。

验证规则
架构

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

主机

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

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

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

    路径

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

    查询

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

    fragment

    重定向 URI 不能包含片段组件。

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

    增量授权

    在 OAuth 2.0 协议中,您的应用会请求授权,以访问资源, 由范围标识。请求授权被视为一种最佳用户体验做法 适时获取所需资源为此,Google 的授权服务器 支持增量授权。借助此功能,您可以根据需要请求范围, 如果用户为新范围授予权限,则返回一个授权代码,而该代码可能是 交换包含用户已授予项目的所有范围的令牌。

    例如,假设某个应用可帮助用户识别有趣的本地事件。 通过该应用,用户可以观看有关活动的视频、对视频进行评分,以及向 将视频添加到播放列表。用户还可以使用该应用将活动添加到他们的 Google 日历。

    在这种情况下,登录时,应用可能不需要或请求访问 任何范围。但是,如果用户尝试对视频评分,则将视频添加到 播放列表或执行其他 YouTube 操作时,应用可能会请求访问 https://www.googleapis.com/auth/youtube.force-ssl 范围。 同样,该应用也可以请求访问 https://www.googleapis.com/auth/calendar 范围(如果用户尝试过) 来添加日历活动。

    要实施增量授权,您需要完成请求访问权限的常规流程 令牌,但要确保授权请求包含之前授予的范围。本次 方法让您的应用不必管理多个访问令牌。

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

    • 该令牌可用于访问与已纳入 新的合并授权。
    • 当您将刷新令牌用于组合授权以获取访问令牌时, 访问令牌表示合并的授权,可用于 响应中包含 scope 值。
    • 合并授权包括用户向 API 项目授予的所有范围,甚至包括 如果授权来自不同的客户端,则会发生此错误。例如,如果用户向 将一个范围授予应用的桌面客户端,然后将另一个范围授予同一应用 应用,则合并的授权将同时包含这两个授权范围。
    • 如果您撤消了某个表示组合授权的令牌,就可以访问所有这些 同时撤消代表关联用户的授权范围。

    “第 1 步:设置授权”中针对特定语言的代码示例 参数第 2 步: 重定向到 Google 的 OAuth 2.0 服务器均使用增量授权。代码示例 也显示了使用增量授权而需要添加的代码。

    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

    在此示例中,调用方应用会请求访问以检索 用户的 YouTube 数据分析数据,以及用户执行的任何其他访问权限 已授予该应用的权限。

    GET https://accounts.google.com/o/oauth2/v2/auth?
      scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyt-analytics.readonly&
      access_type=offline&
      state=security_token%3D138rk%3Btarget_url%3Dhttp...index&
      redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback&
      response_type=code&
      client_id=client_id&
      include_granted_scopes=true
    
          

    Refreshing an access token (offline access)

    Access tokens periodically expire and become invalid credentials for a related API request. You can refresh an access token without prompting the user for permission (including when the user is not present) if you requested offline access to the scopes associated with the token.

    • If you use a Google API Client Library, the client object refreshes the access token as needed as long as you configure that object for offline access.
    • If you are not using a client library, you need to set the access_type HTTP query parameter to offline when redirecting the user to Google's OAuth 2.0 server. In that case, Google's authorization server returns a refresh token when you exchange an authorization code for an access token. Then, if the access token expires (or at any other time), you can use a refresh token to obtain a new access token.

    Requesting offline access is a requirement for any application that needs to access a Google API when the user is not present. For example, an app that performs backup services or executes actions at predetermined times needs to be able to refresh its access token when the user is not present. The default style of access is called online.

    Server-side web applications, installed applications, and devices all obtain refresh tokens during the authorization process. Refresh tokens are not typically used in client-side (JavaScript) web applications.

    PHP

    If your application needs offline access to a Google API, set the API client's access type to 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`
    });
    

    客户端拥有刷新令牌后,系统会自动获取并刷新访问令牌 就会出现这种错误

    HTTP/REST

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

    字段
    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 以及错误代码。

    实施跨账号保护

    您应采取的额外措施来保护用户的个账号实现了跨账号功能 利用 Google 的跨账号保护服务进行保护。通过这项服务 订阅安全事件通知,这些通知可向您的应用提供 关于用户账号的重大变化然后,您可以根据这些信息 您决定如何响应事件。

    下面列举了一些示例来说明 Google 的跨账号保护服务发送到您应用的事件类型:

    • https://schemas.openid.net/secevent/risc/event-type/sessions-revoked
    • https://schemas.openid.net/secevent/oauth/event-type/token-revoked
    • https://schemas.openid.net/secevent/risc/event-type/account-disabled

    请参阅 使用“跨账号保护”页面保护用户账号