本文档介绍了 Web 服务器应用如何使用 Google API 客户端库或 Google OAuth 2.0 端点来进行 OAuth 2.0 授权,从而实现对 YouTube Data API 的访问。
OAuth 2.0 可让用户与应用共享特定数据,同时保持其用户名、密码和其他信息的私密性。 例如,应用可以使用 OAuth 2.0 获取向用户 YouTube 频道上传视频的权限。
此 OAuth 2.0 流程专门用于用户授权。它专为可以存储机密信息并保持状态的应用而设计。经过适当授权的 Web 服务器应用可以在用户与应用互动时或在用户离开应用后访问 API。
Web 服务器应用通常还会使用
服务账号来授权 API 请求,尤其是在调用 Cloud API 来访问基于项目的数据(而非特定于用户的数据)时。Web 服务器应用可以将服务账号与用户授权结合使用。
请注意,YouTube Data API 仅支持拥有和管理多个 YouTube 频道的 YouTube 内容所有者的服务账号流程。
具体而言,内容所有者可以使用服务账号来调用支持 onBehalfOfContentOwner
请求参数的 API 方法。
客户端库
本页面上特定于某语言的示例使用的是 Google API 客户端库来实现 OAuth 2.0 授权。要想运行代码示例,您必须先安装适用于您的语言的客户端库。
当您使用 Google API 客户端库来处理应用的 OAuth 2.0 流程时,客户端库会执行许多应用原本需要自行处理的操作。例如,它会确定应用何时可以使用或刷新存储的访问令牌,以及应用何时必须重新征得用户同意。客户端库还会生成正确的重定向网址,并帮助实现将授权代码换成访问令牌的重定向处理程序。
Google API 客户端库(适用于服务器端应用)支持以下语言:
前提条件
为您的项目启用 API
任何调用 Google API 的应用都需要在 API Console中启用这些 API。
如需为您的项目启用该 API,请按以下步骤操作:
- Google API Console中的Open the API Library 。
- If prompted, select a project, or create a new one.
- 使用库页面查找并启用 YouTube Data API。找到您的应用将使用的任何其他 API,并启用这些 API。
创建授权凭据
任何使用 OAuth 2.0 访问 Google API 的应用都必须具有授权凭据,以向 Google 的 OAuth 2.0 服务器表明应用的身份。以下步骤说明了如何为项目创建凭据。然后,您的应用可以使用这些凭据来访问您为相应项目启用的 API。
- Go to the Clients page.
- 点击创建客户端。
- 选择 Web 应用应用类型。
- 填写表单并点击创建。使用 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 授权之前,我们建议您确定应用需要访问权限的范围。
我们还建议您的应用通过增量授权流程请求授权范围的访问权限,在此流程中,您的应用会在用户执行相关操作时请求访问用户数据。此最佳实践有助于用户更轻松地了解您的应用为何需要其请求的访问权限。
YouTube Data API v3 使用以下范围:
范围 | 说明 |
---|---|
https://www. |
管理您的 YouTube 账号 |
https://www. |
查看包含以下信息的列表:当前活跃的频道会员、其当前级别以及其成为会员的时间 |
https://www. |
查看、修改以及永久删除您的 YouTube 视频、评分、评论和字幕 |
https://www. |
查看您的 YouTube 账号 |
https://www. |
管理您的 YouTube 视频 |
https://www. |
查看和管理您在 YouTube 上的资源和关联内容 |
https://www. |
查看您的 YouTube 频道中关于 YouTube 合作伙伴试演的隐私信息 |
OAuth 2.0 API 范围文档包含您可能用于访问 Google API 的范围的完整列表。
语言专属要求
如需运行本文档中的任何代码示例,您需要拥有 Google 账号、互联网访问权限和网络浏览器。如果您使用的是某个 API 客户端库,另请参阅下文中的特定于语言的要求。
PHP
如需运行本文档中的 PHP 代码示例,您需要:
- PHP 8.0 或更高版本,并安装了命令行界面 (CLI) 和 JSON 扩展程序。
- Composer 依赖项管理工具。
-
适用于 PHP 的 Google API 客户端库:
composer require google/apiclient:^2.15.0
如需了解详情,请参阅 PHP 版 Google API 客户端库。
Python
如需运行本文档中的 Python 代码示例,您需要:
- Python 3.7 或更高版本
- pip 软件包管理工具。
- 适用于 Python 的 Google API 客户端库 2.0 版发布:
pip install --upgrade google-api-python-client
- 用于用户授权的
google-auth
、google-auth-oauthlib
和google-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
如果您无法升级 Python,请查看 Google API Python 客户端库版本说明和相关迁移指南。
Ruby
如需运行本文档中的 Ruby 代码示例,您需要:
- Ruby 2.6 或更高版本
-
Ruby 版 Google 身份验证库:
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 请求。
下表简要总结了这些步骤:
- 您的应用会确定所需的权限。
- 您的应用会将用户重定向到 Google,同时提供所请求的权限列表。
- 用户决定是否向您的应用授予相应权限。
- 您的应用会了解用户的决定。
- 如果用户授予了所请求的权限,您的应用会检索代表用户发出 API 请求所需的令牌。
第 1 步:设置授权参数
第一步是创建授权请求。该请求会设置用于标识您的应用并定义用户将被要求向您的应用授予的权限的参数。
- 如果您使用 Google 客户端库进行 OAuth 2.0 身份验证和授权,则需要创建并配置一个定义这些参数的对象。
- 如果您直接调用 Google OAuth 2.0 端点,则会生成一个网址,并在该网址上设置参数。
以下标签页介绍了 Web 服务器应用支持的授权参数。特定于语言的示例还展示了如何使用客户端库或授权库来配置设置这些参数的对象。
PHP
以下代码段创建了一个 Google\Client()
对象,用于定义授权请求中的参数。
该对象使用 client_secret.json 文件中的信息来标识您的应用。(如需详细了解该文件,请参阅创建授权凭据。)该对象还会标识您的应用请求访问权限的范围,以及应用身份验证端点的网址,该端点将处理来自 Google OAuth 2.0 服务器的响应。最后,代码设置了可选的 access_type
和 include_granted_scopes
参数。
例如,以下代码请求离线访问权限,以管理用户的 YouTube 账号:
use Google\Client; $client = new 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 文件中的信息来标识您的应用。该对象还标识了您的应用请求访问权限的范围,以及应用身份验证端点的网址,该端点将处理来自 Google OAuth 2.0 服务器的响应。最后,代码设置了可选的 access_type
和 include_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 'googleauth' require 'googleauth/web_user_authorizer' require 'googleauth/stores/redis_token_store' require 'google/apis/youtube_v3' # Required, call the from_file method to retrieve the client ID from a # client_secret.json file. client_id = Google::Auth::ClientId.from_file('/path/to/client_secret.json') # Required, scope value scope = 'https://www.googleapis.com/auth/youtube.force-ssl' # Required, Authorizers require a storage instance to manage long term persistence of # access and refresh tokens. token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) # 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. callback_uri = '/oauth2callback' # 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. authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, callback_uri)
您的应用使用客户端对象执行 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 YouTube API const scopes = [ 'https://www.googleapis.com/auth/youtube.force-ssl' ]; // 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 and Google Calendar 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 授权服务器支持以下适用于 Web 服务器应用的查询字符串参数:
参数 | |||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
client_id |
必需
应用的客户端 ID。您可以在 Cloud Console Clients page中找到此值。 |
||||||||||||||||
redirect_uri |
必需
确定 API 服务器在用户完成授权流程后将用户重定向到的位置。该值必须与您在客户端的 Cloud Console
Clients page中配置的 OAuth 2.0 客户端的某个已获授权的重定向 URI 完全一致。如果此值与所提供 请注意, |
||||||||||||||||
response_type |
必需
确定 Google OAuth 2.0 端点是否返回授权代码。 将参数值设置为 |
||||||||||||||||
scope |
必需
一个以空格分隔的范围列表,用于标识应用可以代表用户访问的资源。这些值会告知 Google 向用户显示的同意屏幕。 有了这一范围,您不但可以让应用仅请求访问所需的资源,而且还可以让用户控制其向您的应用授予的访问权限大小。因此,所请求的授权范围数量与获得用户同意的可能性之间存在反比关系。 YouTube Data API v3 使用以下范围:
OAuth 2.0 API 范围文档提供了您可能用于访问 Google API 的范围的完整列表。 我们建议您的应用尽可能在上下文中请求对授权范围的访问权限。通过渐进式授权在用户执行相关操作时请求访问用户数据,有助于用户更轻松地了解您的应用为何需要其请求的访问权限。 |
||||||||||||||||
access_type |
建议
指示应用在用户不在浏览器前时是否可以刷新访问令牌。有效的参数值为 如果您的应用需要在用户不在浏览器前时刷新访问令牌,请将该值设置为 |
||||||||||||||||
state |
建议
指定应用用于在授权请求与授权服务器的响应之间保持状态的任何字符串值。
在用户同意或拒绝您的应用访问请求后,服务器会返回您在 您可以使用此参数实现多种目的,例如将用户引导至应用中的正确资源、发送随机数以及缓解跨站请求伪造。由于您的 |
||||||||||||||||
include_granted_scopes |
可选
使应用能够使用增量授权在上下文中请求对其他范围的访问权限。如果您将此形参的值设置为 |
||||||||||||||||
enable_granular_consent |
可选
默认为 当 Google 为应用启用精细权限时,此参数将不再有任何影响。 |
||||||||||||||||
login_hint |
可选
如果您的应用知道哪个用户正在尝试进行身份验证,则可以使用此参数向 Google 身份验证服务器提供提示。服务器会使用提示来简化登录流程,方法是预填充登录表单中的电子邮件地址字段或选择适当的多重登录会话。 将参数值设置为电子邮件地址或 |
||||||||||||||||
prompt |
可选
以空格分隔的提示列表(区分大小写),用于向用户显示。如果您未指定此参数,则系统仅在您的项目首次请求访问权限时提示用户。如需了解详情,请参阅 提示用户重新征得同意。 可能的值包括:
|
第 2 步:重定向到 Google 的 OAuth 2.0 服务器
将用户重定向到 Google 的 OAuth 2.0 服务器,以启动身份验证和授权流程。通常,这种情况会在您的应用首次需要访问用户数据时发生。对于增量授权,当应用首次需要访问尚未获得访问权限的其他资源时,也会执行此步骤。
PHP
- 生成一个网址,用于向 Google 的 OAuth 2.0 服务器请求访问权限:
$auth_url = $client->createAuthUrl();
- 将用户重定向到
$auth_url
:header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
Python
此示例展示了如何使用 Flask Web 应用框架将用户重定向到授权网址:
return flask.redirect(authorization_url)
Ruby
- 生成一个网址,用于向 Google 的 OAuth 2.0 服务器请求访问权限:
auth_uri = authorizer.get_authorization_url(request: request)
- 将用户重定向到
auth_uri
。
Node.js
-
使用在第 1 步中生成的网址
authorizationUrl
通过generateAuthUrl
方法向 Google 的 OAuth 2.0 服务器请求访问权限。 -
将用户重定向到
authorizationUrl
。res.redirect(authorizationUrl);
HTTP/REST
重定向到 Google 授权服务器的示例
以下示例网址请求对允许访问用户 YouTube 账号的范围进行离线访问 (access_type=offline
)。它使用增量授权来确保新访问令牌涵盖用户之前向应用授予的任何范围。该网址还为必需的 redirect_uri
、response_type
和 client_id
参数以及 state
参数设置了值。为了便于阅读,网址包含换行符和空格。
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 Workspace 管理员的政策,相应 Google 账号无法授权一个或多个请求的范围。如需详细了解管理员如何限制对所有范围或敏感范围和受限范围的访问权限,直到明确授予您的 OAuth 客户端 ID 访问权限为止,请参阅 Google Workspace 管理员帮助文章 控制哪些第三方应用和内部应用可以访问 Google Workspace 数据。
disallowed_useragent
授权端点显示在 Google 的 OAuth 2.0 政策禁止使用的嵌入式用户代理中。
Android
Android 开发者在 android.webkit.WebView
中打开授权请求时,可能会遇到此错误消息。
开发者应改为使用 Android 库,例如 Google Sign-In for Android 或 OpenID Foundation 的 AppAuth for Android。
当 Android 应用在嵌入式用户代理中打开常规 Web 链接,并且用户从您的网站导航到 Google 的 OAuth 2.0 授权端点时,Web 开发者可能会遇到此错误。开发者应允许在操作系统(包括 Android 应用链接处理程序或默认浏览器应用)的默认链接处理程序中打开常规链接。Android 自定义标签页库也是受支持的选项。
iOS
iOS 和 macOS 开发者在 WKWebView
中打开授权请求时,可能会遇到此错误。
开发者应改用 iOS 库,例如 Google Sign-In for iOS 或 OpenID Foundation 的 AppAuth for iOS。
当 iOS 或 macOS 应用在嵌入式用户代理中打开常规网页链接,并且用户从您的网站导航到 Google 的 OAuth 2.0 授权端点时,Web 开发者可能会遇到此错误。开发者应允许在操作系统默认的链接处理程序中打开常规链接,包括 Universal Links 处理程序或默认浏览器应用。SFSafariViewController
库也是受支持的选项。
org_internal
相应请求中的 OAuth 客户端 ID 属于一个项目,该项目限制对特定 Google Cloud 组织中的 Google 账号的访问权限。 如需详细了解此配置选项,请参阅“设置 OAuth 权限请求页面”帮助文章中的用户类型部分。
invalid_client
OAuth 客户端密钥不正确。查看 OAuth 客户端配置,包括用于此请求的客户端 ID 和密钥。
deleted_client
用于发出请求的 OAuth 客户端已被删除。删除操作可以手动执行,也可以在出现未使用的客户端 时自动执行。已删除的客户可以在删除后的 30 天内恢复。 了解详情 。
invalid_grant
刷新访问令牌或使用增量授权时,令牌可能已过期或失效。 再次验证用户身份,并征得用户同意以获取新令牌。如果您仍然看到此错误,请确保您的应用已正确配置,并且您在请求中使用了正确的令牌和参数。否则,用户账号可能已被删除或停用。
redirect_uri_mismatch
授权请求中传递的 redirect_uri
与 OAuth 客户端 ID 的已获授权的重定向 URI 不匹配。在 Google Cloud Console中查看已获授权的重定向 URI Clients 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 云端硬盘中文件的元数据,并请求只读权限以查看您的 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
错误。下一步将详细介绍当用户被重定向回您的应用时,URI 中返回的信息。
第 5 步:将授权代码换成刷新令牌和访问令牌
Web 服务器收到授权代码后,可以用该授权代码来换取访问令牌。
PHP
如需将授权代码兑换为访问令牌,请使用 fetchAccessTokenWithAuthCode
方法:
$access_token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
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 browser session storage, but for security: client_id, client_secret, # and token_uri are instead stored only on the backend server. credentials = flow.credentials flask.session['credentials'] = { 'token': credentials.token, 'refresh_token': credentials.refresh_token, 'granted_scopes': credentials.granted_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 |
从 Cloud Console Clients page获取的客户端 ID。 |
client_secret |
从 Cloud Console Clients page获取的客户端密钥。 |
code |
从初始请求返回的授权代码。 |
grant_type |
根据 OAuth 2.0 规范中的定义,此字段的值必须设置为 authorization_code 。 |
redirect_uri |
Cloud Console中为您的项目列出的重定向 URI 之一
Clients 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 对象,以响应此请求。
请注意,只有当您的应用在向 Google 的授权服务器发出的初始请求中将 access_type
参数设置为 offline
时,才会返回刷新令牌。
响应包含以下字段:
字段 | |
---|---|
access_token |
您的应用发送的用于授权 Google API 请求的令牌。 |
expires_in |
访问令牌的剩余生命周期(以秒为单位)。 |
refresh_token |
可用于获取新访问令牌的令牌。刷新令牌在用户撤消访问权限或刷新令牌过期之前一直有效。
同样,只有在您向 Google 的授权服务器发送的初始请求中将 access_type 参数设置为 offline 时,此字段才会出现在此响应中。
|
refresh_token_expires_in |
刷新令牌的剩余有效期(以秒为单位)。仅当用户授予基于时间的访问权限时,才会设置此值。 |
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 流程来请求新代码,以再次提示用户授予同意。
第 6 步:检查用户授予了哪些范围
当您请求多项权限(范围)时,用户可能不会向您的应用授予对所有这些权限的访问权限。您的应用必须验证实际授予了哪些范围,并妥善处理某些权限被拒绝的情况,通常是通过停用依赖于这些被拒绝范围的功能。
不过,也有例外情况。具有全网域授权的 Google Workspace 企业应用或标记为受信任的应用会绕过精细权限同意页面。对于这些应用,用户不会看到精细权限同意屏幕。相反,您的应用要么会获得所有请求的范围,要么不会获得任何范围。
如需了解更详细的信息,请参阅如何处理精细权限。
PHP
如需检查用户已授予哪些范围,请使用 getGrantedScope()
方法:
// Space-separated string of granted scopes if it exists, otherwise null. $granted_scopes = $client->getOAuth2Service()->getGrantedScope();
Python
返回的 credentials
对象具有 granted_scopes
属性,该属性是用户已向您的应用授予的范围的列表。
credentials = flow.credentials flask.session['credentials'] = { 'token': credentials.token, 'refresh_token': credentials.refresh_token, 'granted_scopes': credentials.granted_scopes}
Ruby
同时请求多个范围时,请通过 credentials
对象的 scope
属性检查哪些范围已获授权。
# User authorized the request. Now, check which scopes were granted. if credentials.scope.include?(Google::Apis::YoutubeV3::AUTH_YOUTUBE_FORCE_SSL) # User authorized permission to see, edit, and permanently delete the # YouTube videos, ratings, comments and captions. # Calling the APIs, etc else # User didn't authorize the permission. # Update UX and application accordingly end
Node.js
同时请求多个范围时,请通过 tokens
对象的 scope
属性检查哪些范围已获授权。
// User authorized the request. Now, check which scopes were granted. if (tokens.scope.includes('https://www.googleapis.com/auth/youtube.force-ssl')) { // User authorized permission to see, edit, and permanently delete the // YouTube videos, ratings, comments and captions. // Calling the APIs, etc. } else { // User didn't authorize read-only Drive activity permission. // Update UX and application accordingly }
HTTP/REST
如需检查用户是否已向您的应用授予对特定范围的访问权限,请检查访问令牌响应中的 scope
字段。access_token 授予的访问权限范围,以空格分隔且区分大小写的字符串列表表示。
例如,以下示例访问令牌响应表明,用户已向您的应用授予查看、修改和永久删除用户 YouTube 视频、评分、评论和字幕的权限:
{ "access_token": "1/fFAGRNJru1FTz70BzhT3Zg", "expires_in": 3920, "token_type": "Bearer", "scope": "https://www.googleapis.com/auth/youtube.force-ssl", "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" }
调用 Google API
PHP
如需使用访问令牌调用 Google API,请完成以下步骤:
- 如果您需要将访问令牌应用于新的
Google\Client
对象(例如,如果您将访问令牌存储在用户会话中),请使用setAccessToken
方法:$client->setAccessToken($access_token);
- 为要调用的 API 构建服务对象。您可以通过向要调用的 API 的构造函数提供已获授权的
Google\Client
对象来构建服务对象。例如,如需调用 YouTube Data API,请执行以下操作:$youtube = new Google_Service_YouTube($client);
- 使用服务对象提供的接口向 API 服务发出请求。
例如,如需检索有关已获授权用户的 YouTube 频道的数据,请执行以下操作:
$channel = $youtube->channels->listChannels('snippet', array('mine' => $mine));
Python
获取访问令牌后,您的应用可以使用该令牌代表指定的用户账号或服务账号授权 API 请求。使用特定于用户的授权凭据为要调用的 API 构建服务对象,然后使用该对象发出已获授权的 API 请求。
- 为要调用的 API 构建服务对象。您可以通过调用
googleapiclient.discovery
库的build
方法来构建服务对象,并提供 API 的名称和版本以及用户凭据: 例如,如需调用 YouTube Data API 的第 3 版,请执行以下操作:from googleapiclient.discovery import build youtube = build('youtube', 'v3', credentials=credentials)
- 使用服务对象提供的接口向 API 服务发出请求。
例如,如需检索有关已获授权用户的 YouTube 频道的数据,请执行以下操作:
channel = youtube.channels().list(mine=True, part='snippet').execute()
Ruby
获取访问令牌后,您的应用可以使用该令牌代表指定的用户账号或服务账号发出 API 请求。使用特定于用户的授权凭据为要调用的 API 构建服务对象,然后使用该对象发出已获授权的 API 请求。
- 为要调用的 API 构建服务对象。
例如,如需调用 YouTube Data API 的版本 3,请执行以下操作:
youtube = Google::Apis::YoutubeV3::YouTubeService.new
- 在服务上设置凭据:
youtube.authorization = credentials
- 使用服务对象提供的接口向 API 服务发出请求。
例如,如需检索有关已获授权用户的 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 构建服务对象。
例如,以下代码使用 Google 云端硬盘 API 列出用户云端硬盘中的文件名。
const { google } = require('googleapis'); // Example of using YouTube API to list channels. var service = google.youtube('v3'); service.channels.list({ auth: oauth2Client, part: 'snippet,contentDetails,statistics', forUsername: 'GoogleDevelopers' }, function (err, response) { if (err) { console.log('The API returned an error: ' + err); return; } var channels = response.data.items; if (channels.length == 0) { console.log('No channel found.'); } else { console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + 'it has %s views.', channels[0].id, channels[0].snippet.title, channels[0].statistics.viewCount); } });
HTTP/REST
应用获得访问令牌后,如果 API 所需的访问范围已获授权,您就可以使用该令牌代表指定的用户账号调用 Google API。为此,请在向 API 发出的请求中添加访问令牌,方法是添加 access_token
查询参数或 Authorization
HTTP 标头 Bearer
值。如果可以,最好使用 HTTP 标头,因为查询字符串往往会显示在服务器日志中。在大多数情况下,您可以使用客户端库来设置对 Google API 的调用(例如,调用 YouTube Data API 时)。
请注意,YouTube Data API 仅支持拥有和管理多个 YouTube 频道的 YouTube 内容所有者(例如唱片公司和电影制片厂)的服务账号。
您可以在 OAuth 2.0 Playground 中试用所有 Google API 并查看其权限范围。
HTTP GET 示例
使用 Authorization: Bearer
HTTP 标头对
youtube.channels
端点(YouTube Data API)的调用可能如下所示。请注意,您需要指定自己的访问令牌:
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
完整示例
以下示例在用户完成身份验证并授权应用管理其 YouTube 账号后,会输出一个 JSON 格式的对象,其中包含有关该用户 YouTube 频道的信息。
PHP
如需运行此示例,请执行以下操作:
- 在 API Console中,将本地计算机的网址添加到重定向网址列表中。例如,添加
http://localhost:8080
。 - 创建一个新目录,并切换到该目录。例如:
mkdir ~/php-oauth2-example cd ~/php-oauth2-example
- 使用 Composer 安装 PHP 版 Google API 客户端库:
composer require google/apiclient:^2.15.0
- 创建包含以下内容的
index.php
和oauth2callback.php
文件。 - 使用 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_secret.json'); // User granted permission as an access token is in the session. 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 users to outh2call.php which redirects users to Google OAuth 2.0 $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(); // Required, call the setAuthConfig function to load authorization credentials from // client_secret.json file. $client->setAuthConfigFile('client_secret.json'); $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST']. $_SERVER['PHP_SELF']); // Required, to set the scope value, call the addScope function. $client->addScope(GOOGLE_SERVICE_YOUTUBE::YOUTUBE_FORCE_SSL); // Enable incremental authorization. Recommended as a best practice. $client->setIncludeGrantedScopes(true); // 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"); // Generate a URL for authorization as it doesn't contain code and error if (!isset($_GET['code']) && !isset($_GET['error'])) { // Generate and set state value $state = bin2hex(random_bytes(16)); $client->setState($state); $_SESSION['state'] = $state; // Generate a url that asks permissions. $auth_url = $client->createAuthUrl(); header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL)); } // User authorized the request and authorization code is returned to exchange access and // refresh tokens. if (isset($_GET['code'])) { // Check the state value if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['state']) { die('State mismatch. Possible CSRF attack.'); } // Get access and refresh tokens (if access_type is offline) $token = $client->fetchAccessTokenWithAuthCode($_GET['code']); /** Save access and refresh token to the session variables. * ACTION ITEM: In a production app, you likely want to save the * refresh token in a secure persistent storage instead. */ $_SESSION['access_token'] = $token; $_SESSION['refresh_token'] = $client->getRefreshToken(); $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/'; header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL)); } // An error response e.g. error=access_denied if (isset($_GET['error'])) { echo "Error: ". $_GET['error']; } ?>
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 json 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" # The OAuth 2.0 access scope allows for 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.') # Load client secrets from the server-side file. with open(CLIENT_SECRETS_FILE, 'r') as f: client_config = json.load(f)['web'] # Load user-specific credentials from the session. session_credentials = flask.session['credentials'] # Reconstruct the credentials object. credentials = google.oauth2.credentials.Credentials( refresh_token=session_credentials.get('refresh_token'), scopes=session_credentials.get('granted_scopes'), token=session_credentials.get('token'), client_id=client_config.get('client_id'), client_secret=client_config.get('client_secret'), token_uri=client_config.get('token_uri')) 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: # Clear the user's session credentials after successful revocation if 'credentials' in flask.session: del flask.session['credentials'] del flask.session['features'] 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, 'granted_scopes': credentials.granted_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="/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' # This disables the requested scopes and granted scopes check. # If users only grant partial request, the warning would not be thrown. os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '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 'googleauth' require 'googleauth/web_user_authorizer' require 'googleauth/stores/redis_token_store' require 'google/apis/youtube_v3' require 'sinatra' configure do enable :sessions # Required, call the from_file method to retrieve the client ID from a # client_secret.json file. set :client_id, Google::Auth::ClientId.from_file('/path/to/client_secret.json') # Required, scope value # Access scopes for retrieving data about the user's YouTube channel. scope = 'Google::Apis::YoutubeV3::AUTH_YOUTUBE_FORCE_SSL' # Required, Authorizers require a storage instance to manage long term persistence of # access and refresh tokens. set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) # 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. set :callback_uri, '/oauth2callback' # 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. set :authorizer, Google::Auth::WebUserAuthorizer.new(settings.client_id, settings.scope, settings.token_store, callback_uri: settings.callback_uri) end get '/' do # NOTE: Assumes the user is already authenticated to the app user_id = request.session['user_id'] # Fetch stored credentials for the user from the given request session. # nil if none present credentials = settings.authorizer.get_credentials(user_id, request) if credentials.nil? # Generate a url that asks the user to authorize requested scope(s). # Then, redirect user to the url. redirect settings.authorizer.get_authorization_url(request: request) end # User authorized read-only YouTube Data API permission. # Example of using YouTube Data API to list user's YouTube channel 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 # Receive the callback from Google's OAuth 2.0 server. get '/oauth2callback' do # Handle the result of the oauth callback. Defers the exchange of the code by # temporarily stashing the results in the user's session. target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) redirect target_url end
Node.js
如需运行此示例,请执行以下操作:
-
在 API Console中,将本地计算机的网址添加到重定向网址列表中。例如,添加
http://localhost
。 - 确保您已安装维护 LTS、活跃 LTS 或当前版本的 Node.js。
-
创建一个新目录,并切换到该目录。例如:
mkdir ~/nodejs-oauth2-example cd ~/nodejs-oauth2-example
-
使用 npm 安装适用于 Node.js 的 Google API 客户端库:
npm install googleapis
-
创建包含以下内容的文件
main.js
。 -
运行示例:
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 YouTube API const scopes = [ 'https://www.googleapis.com/auth/youtube.force-ssl' ]; /* 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 and Google Calendar 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 YouTube API to list channels. var service = google.youtube('v3'); service.channels.list({ auth: oauth2Client, part: 'snippet,contentDetails,statistics', forUsername: 'GoogleDevelopers' }, function (err, response) { if (err) { console.log('The API returned an error: ' + err); return; } var channels = response.data.items; if (channels.length == 0) { console.log('No channel found.'); } else { console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + 'it has %s views.', channels[0].id, channels[0].snippet.title, channels[0].statistics.viewCount); } }); } }); // 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(8080); } 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__) # To get these credentials (CLIENT_ID CLIENT_SECRET) and for your application, visit # https://console.cloud.google.com/apis/credentials. CLIENT_ID = '123456789.apps.googleusercontent.com' CLIENT_SECRET = 'abc123' # Read from a file or environmental variable in a real app # Access scopes for YouTube API SCOPE = 'https://www.googleapis.com/auth/youtube.force-ssl' # 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. 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 # Generate a url that asks permissions for the Drive activity # and Google Calendar scope. Then, redirect user to the url. 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'} # Exchange authorization code for access and refresh tokens (if access_type is offline) 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(包括本地主机 IP 地址 URI)不受此规则的约束。 |
主机 |
主机不能是原始 IP 地址。本地主机 IP 地址不受此规则的限制。 |
网域 |
“googleusercontent.com” 。goo.gl ),除非应用拥有该网域。此外,如果拥有缩短器网域的应用选择重定向到该网域,则该重定向 URI 必须在其路径中包含 “/google-callback/” 或以 “/google-callback” 结尾。 |
Userinfo |
重定向 URI 不得包含 userinfo 子组件。 |
路径 |
重定向 URI 不能包含路径遍历(也称为目录回溯),即不能包含 |
查询 |
重定向 URI 不得包含开放式重定向。 |
fragment |
重定向 URI 不得包含片段组件。 |
字符数 |
重定向 URI 不得包含某些字符,包括:
|
增量授权
在 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 服务器中的示例 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
在此示例中,调用应用请求访问权限以检索用户的 YouTube Analytics 数据,此外还请求用户已授予该应用的任何其他访问权限。
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 tooffline
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。客户端对象会根据需要刷新访问令牌。
访问令牌会过期。如果访问令牌即将过期,此库会自动使用刷新令牌来获取新的访问令牌。确保始终存储最新令牌的一种简单方法是使用 tokens 事件:
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();
令牌参数可以是访问令牌,也可以是刷新令牌。如果令牌是访问令牌,并且具有对应的刷新令牌,则刷新令牌也会被撤消。
如果撤消成功处理,则响应的状态代码为 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 产品会提供基于时间的访问权限,让用户可以选择授予有限时长的访问权限。例如, Data Portability API 可实现一次性数据转移。
当用户向您的应用授予基于时间的访问权限时,刷新令牌将在指定时长后过期。请注意,在特定情况下,刷新令牌可能会提前失效;如需了解详情,请参阅这些情况。在授权码交换响应中返回的 refresh_token_expires_in
字段表示在这些情况下刷新令牌的剩余有效时间。
实现跨账号保护
为保护用户账号,您还应采取一项措施,即利用 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
如需详细了解如何实现跨账号保护,以及查看可用事件的完整列表,请参阅 使用跨账号保护功能保护用户账号 页面。