本文档介绍了 Web 服务器应用如何使用 Google API 客户端库或 Google OAuth 2.0 端点来实现 OAuth 2.0 授权以访问 Google API。
OAuth 2.0 可让用户与应用共享特定数据,同时保持用户名、密码和其他信息的私密性。例如,应用可以使用 OAuth 2.0 向用户请求在其 Google 云端硬盘中存储文件的权限。
此 OAuth 2.0 流程专门用于用户授权。它专为可以存储机密信息并维护状态的应用而设计。适当授权的网络服务器应用可以在用户与应用互动时或用户离开应用后访问 API。
Web 服务器应用通常还会使用服务帐号向 API 请求授权,尤其是在调用 Cloud API 来访问基于项目的数据时,而不是访问特定于用户的数据时。网络服务器应用可以将服务帐号与用户授权结合使用。
客户端库
本页面上特定于语言的示例使用 Google API 客户端库来实现 OAuth 2.0 授权。要运行代码示例,您必须先安装适用于您的语言的客户端库。
当您使用 Google API 客户端库处理应用的 OAuth 2.0 流程时,客户端库会执行应用需要自行处理的许多操作。例如,它会决定应用何时可以使用或刷新存储的访问令牌,以及应用何时必须重新征得用户同意。客户端库还会生成正确的重定向网址,并有助于实现使用授权代码交换访问令牌的重定向处理程序。
适用于服务器端应用的 Google API 客户端库支持以下语言:
前提条件
为您的项目启用 API
任何调用 Google API 的应用都需要在 API Console中启用这些 API。
如需为您的项目启用该 API,请按以下步骤操作:
- Open the API Library (在 Google API Console中)。
- If prompted, select a project, or create a new one.
- API Library 列出了所有可用的 API,按产品系列和热门程度分组。如果列表中没有显示您要启用的 API,请使用搜索功能查找该 API,或点击该 API 所属的产品系列中的查看全部。
- 选择您要启用的 API,然后点击启用按钮。
- If prompted, enable billing.
- If prompted, read and accept the API's Terms of Service.
创建授权凭据
任何使用 OAuth 2.0 访问 Google API 的应用都必须使用授权凭据向 Google 的 OAuth 2.0 服务器标识该应用。以下步骤说明了如何为项目创建凭据。然后,您的应用可以使用这些凭据访问为该项目启用的 API。
- Go to the Credentials page.
- 依次点击创建凭据 > OAuth 客户端 ID。
- 选择 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 授权之前,我们建议您确定应用需要访问权限的范围。
我们还建议您的应用通过增量授权流程请求对授权范围的访问权限,在此过程中,您的应用会在上下文中请求访问用户数据。此最佳做法可帮助用户更轻松地了解应用需要其请求的访问权限的原因。
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-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
Ruby
如需运行本文档中的 Ruby 代码示例,您需要:
- Ruby 2.2.2 或更高版本
-
适用于 Ruby 的 Google API 客户端库:
gem install google-api-client
-
Sinatra Ruby Web 应用框架。
gem install sinatra
Node.js
如需运行本文档中的 Node.js 代码示例,您需要:
- 维护 LTS、活跃 LTS 或 Node.js 的当前版本。
-
Google API Node.js 客户端:
npm install googleapis
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 端点,系统会生成一个网址并针对该网址设置参数。
以下标签定义了网络服务器应用支持的授权参数。各种语言的示例还展示了如何使用客户端库或授权库来配置可设置这些参数的对象。
PHP
以下代码段会创建一个 Google\Client()
对象,用于定义授权请求中的参数。
该对象使用 client_secret.json 文件中的信息来标识您的应用。(如需详细了解该文件,请参阅创建授权凭据)。该对象还标识了您的应用请求访问权限的范围,以及应用的身份验证端点的网址,该端点将处理来自 Google OAuth 2.0 服务器的响应。最后,代码会设置可选的 access_type
和 include_granted_scopes
参数。
例如,以下代码请求以只读方式访问用户的 Google 云端硬盘:
$client = new Google\Client(); $client->setAuthConfig('client_secret.json'); $client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY); $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php'); // offline access will give you both an access and refresh token so that // your app can refresh the access token without user interaction. $client->setAccessType('offline'); // Using "consent" ensures that your application always receives a refresh token. // If you are not using offline access, you can omit this. $client->setApprovalPrompt('consent'); $client->setIncludeGrantedScopes(true); // incremental auth
该请求指定了以下信息:
参数 | |||||||
---|---|---|---|---|---|---|---|
client_id |
必需
应用的客户端 ID。您可以在 API ConsoleCredentials page中找到此值。 在 PHP 中,调用 $client = new Google\Client(); $client->setAuthConfig('client_secret.json'); |
||||||
redirect_uri |
必需
确定 API 服务器在用户完成授权流程后将用户重定向到何处。该值必须与您在客户端的 API ConsoleCredentials page中配置的 OAuth 2.0 客户端的某个已获授权的重定向 URI 完全匹配。如果此值与提供的 请注意, 如需在 PHP 中设置此值,请调用 $client->setRedirectUri('https://oauth2.example.com/code'); |
||||||
scope |
必需
以空格分隔的范围列表,用于标识您的应用代表用户可以访问的资源。这些值会通知 Google 向用户显示的同意屏幕。 通过范围,您的应用可以仅请求访问所需的资源,同时还可以控制用户向应用授予的访问权限大小。因此,请求的范围数与征得用户同意的可能性之间存在反向关系。 如需在 PHP 中设置此值,请调用 $client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY); 我们建议您的应用尽可能在上下文中请求对授权范围的访问权限。通过在上下文中通过增量授权请求访问用户数据,您可以帮助用户更轻松地了解应用需要请求访问的原因。 |
||||||
access_type |
推荐
指示当用户不在浏览器上时,您的应用是否可以刷新访问令牌。有效的参数值包括 如果您的应用不在浏览器上,则需要刷新访问令牌,请将该值设置为 如需在 PHP 中设置此值,请调用 $client->setAccessType('offline'); |
||||||
state |
推荐
指定应用在授权请求与授权服务器响应之间保持状态的任何字符串值。
在用户同意或拒绝您的应用的访问请求后,服务器会返回您在 此参数可用于多种用途,例如将用户定向到应用中的正确资源、发送 Nonce 以及减少跨网站伪造请求。由于您的 如需在 PHP 中设置此值,请调用 $client->setState($sample_passthrough_value); |
||||||
include_granted_scopes |
可选
允许应用使用增量授权在上下文中请求访问其他范围。如果您将此参数的值设为 如需在 PHP 中设置此值,请调用 $client->setIncludeGrantedScopes(true); |
||||||
login_hint |
可选
如果您的应用知道哪个用户正尝试进行身份验证,它可以使用此参数来向 Google Authentication 服务器提供提示。服务器会利用提示来简化登录流程,具体方法是在登录表单中预先填充电子邮件字段,或选择相应的多帐号登录会话。 将参数值设为电子邮件地址或 如需在 PHP 中设置此值,请调用 $client->setLoginHint('None'); |
||||||
prompt |
可选
一系列以空格分隔且区分大小写的提示语句,用于向用户显示。如果您不指定此参数,则系统只会在您的项目首次请求访问权限时提示用户。如需了解详情,请参阅提示重新同意。 如需在 PHP 中设置此值,请调用 $client->setApprovalPrompt('consent'); 可能的值包括:
|
Python
以下代码段使用 google-auth-oauthlib.flow
模块构建授权请求。
该代码会构建一个 Flow
对象,该对象使用您在创建授权凭据后下载的 client_secret.json 文件中的信息来识别您的应用。该对象还标识了您的应用请求访问的范围,以及应用的身份验证端点的网址,该端点将处理来自 Google OAuth 2.0 服务器的响应。最后,代码会设置可选的 access_type
和 include_granted_scopes
参数。
例如,以下代码请求以只读方式访问用户的 Google 云端硬盘:
import google.oauth2.credentials import google_auth_oauthlib.flow # Use the client_secret.json file to identify the application requesting # authorization. The client ID (from that file) and access scopes are required. flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( 'client_secret.json', scopes=['https://www.googleapis.com/auth/drive.metadata.readonly']) # Indicate where the API server will redirect the user after the user completes # the authorization flow. The redirect URI is required. The value must exactly # match one of the authorized redirect URIs for the OAuth 2.0 client, which you # configured in the API Console. If this value doesn't match an authorized URI, # you will get a 'redirect_uri_mismatch' error. flow.redirect_uri = 'https://www.example.com/oauth2callback' # Generate URL for request to Google's OAuth 2.0 server. # Use kwargs to set optional request parameters. authorization_url, state = flow.authorization_url( # Enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. access_type='offline', # Enable incremental authorization. Recommended as a best practice. include_granted_scopes='true')
该请求指定了以下信息:
参数 | |||||||
---|---|---|---|---|---|---|---|
client_id |
必需
应用的客户端 ID。您可以在 API ConsoleCredentials page中找到此值。 在 Python 中,调用 flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( 'client_secret.json', scopes=['https://www.googleapis.com/auth/drive.metadata.readonly']) |
||||||
redirect_uri |
必需
确定 API 服务器在用户完成授权流程后将用户重定向到何处。该值必须与您在客户端的 API ConsoleCredentials page中配置的 OAuth 2.0 客户端的某个已获授权的重定向 URI 完全匹配。如果此值与提供的 请注意, 如需在 Python 中设置此值,请设置 flow.redirect_uri = 'https://oauth2.example.com/code' |
||||||
scope |
必需
范围列表,用于标识您的应用可以代表用户访问的资源。这些值会通知 Google 向用户显示的同意屏幕。 通过范围,您的应用可以仅请求访问所需的资源,同时还可以控制用户向应用授予的访问权限大小。因此,请求的范围数与征得用户同意的可能性之间存在反向关系。 在 Python 中,使用设置 flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( 'client_secret.json', scopes=['https://www.googleapis.com/auth/drive.metadata.readonly']) 我们建议您的应用尽可能在上下文中请求对授权范围的访问权限。通过在上下文中通过增量授权请求访问用户数据,您可以帮助用户更轻松地了解应用需要请求访问的原因。 |
||||||
access_type |
推荐
指示当用户不在浏览器上时,您的应用是否可以刷新访问令牌。有效的参数值包括 如果您的应用不在浏览器上,则需要刷新访问令牌,请将该值设置为 在 Python 中,可以在调用 authorization_url, state = flow.authorization_url( access_type='offline', include_granted_scopes='true') |
||||||
state |
推荐
指定应用在授权请求与授权服务器响应之间保持状态的任何字符串值。
在用户同意或拒绝您的应用的访问请求后,服务器会返回您在 此参数可用于多种用途,例如将用户定向到应用中的正确资源、发送 Nonce 以及减少跨网站伪造请求。由于您的 在 Python 中,可以在调用 authorization_url, state = flow.authorization_url( access_type='offline', state=sample_passthrough_value, include_granted_scopes='true') |
||||||
include_granted_scopes |
可选
允许应用使用增量授权在上下文中请求访问其他范围。如果您将此参数的值设为 在 Python 中,可以在调用 authorization_url, state = flow.authorization_url( access_type='offline', include_granted_scopes='true') |
||||||
login_hint |
可选
如果您的应用知道哪个用户正尝试进行身份验证,它可以使用此参数来向 Google Authentication 服务器提供提示。服务器会利用提示来简化登录流程,具体方法是在登录表单中预先填充电子邮件字段,或选择相应的多帐号登录会话。 将参数值设为电子邮件地址或 在 Python 中,可以在调用 authorization_url, state = flow.authorization_url( access_type='offline', login_hint='None', include_granted_scopes='true') |
||||||
prompt |
可选
一系列以空格分隔且区分大小写的提示语句,用于向用户显示。如果您不指定此参数,则系统只会在您的项目首次请求访问权限时提示用户。如需了解详情,请参阅提示重新同意。 在 Python 中,可以在调用 authorization_url, state = flow.authorization_url( access_type='offline', prompt='consent', include_granted_scopes='true') 可能的值包括:
|
Ruby
使用您创建的 client_secrets.json 文件配置应用中的客户端对象。配置客户端对象时,您需要指定应用需要访问的范围,以及应用的身份验证端点的网址,该端点将处理来自 OAuth 2.0 服务器的响应。
例如,以下代码请求以只读方式访问用户的 Google 云端硬盘:
require 'google/apis/drive_v2' require 'google/api_client/client_secrets' client_secrets = Google::APIClient::ClientSecrets.load auth_client = client_secrets.to_authorization auth_client.update!( :scope => 'https://www.googleapis.com/auth/drive.metadata.readonly', :redirect_uri => 'http://www.example.com/oauth2callback', :additional_parameters => { "access_type" => "offline", # offline access "include_granted_scopes" => "true" # incremental auth } )
您的应用使用客户端对象执行 OAuth 2.0 操作,例如生成授权请求网址并将访问令牌应用于 HTTP 请求。
Node.js
以下代码段会创建一个 google.auth.OAuth2
对象,用于定义授权请求中的参数。
该对象使用 client_secret.json 文件中的信息来标识您的应用。如需请求用户检索访问令牌,您可以将用户重定向到用户意见征求页面。 要创建用户意见征求页面网址,请执行以下操作:
const {google} = require('googleapis'); /** * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI * from the client_secret.json file. To get these credentials for your application, visit * https://console.cloud.google.com/apis/credentials. */ const oauth2Client = new google.auth.OAuth2( YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, YOUR_REDIRECT_URL ); // Access scopes for read-only Drive activity. const scopes = [ 'https://www.googleapis.com/auth/drive.metadata.readonly' ]; // Generate a url that asks permissions for the Drive activity scope const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as a best practice. include_granted_scopes: true });
重要提示 - refresh_token
仅在首次授权时返回。如需了解详情,请点击此处。
HTTP/REST
Google 的 OAuth 2.0 端点位于 https://accounts.google.com/o/oauth2/v2/auth
。此端点只能通过 HTTPS 访问。普通 HTTP 连接会被拒绝。
对于网络服务器应用,Google 授权服务器支持以下查询字符串参数:
参数 | |||||||
---|---|---|---|---|---|---|---|
client_id |
必需
应用的客户端 ID。您可以在 API ConsoleCredentials page中找到此值。 |
||||||
redirect_uri |
必需
确定 API 服务器在用户完成授权流程后将用户重定向到何处。该值必须与您在客户端的 API ConsoleCredentials page中配置的 OAuth 2.0 客户端的某个已获授权的重定向 URI 完全匹配。如果此值与提供的 请注意, |
||||||
response_type |
必需
确定 Google OAuth 2.0 端点是否会返回授权代码。 对于网络服务器应用,请将此参数值设为 |
||||||
scope |
必需
以空格分隔的范围列表,用于标识您的应用代表用户可以访问的资源。这些值会通知 Google 向用户显示的同意屏幕。 通过范围,您的应用可以仅请求访问所需的资源,同时还可以控制用户向应用授予的访问权限大小。因此,请求的范围数与征得用户同意的可能性之间存在反向关系。 我们建议您的应用尽可能在上下文中请求对授权范围的访问权限。通过在上下文中通过增量授权请求访问用户数据,您可以帮助用户更轻松地了解应用需要请求访问的原因。 |
||||||
access_type |
推荐
指示当用户不在浏览器上时,您的应用是否可以刷新访问令牌。有效的参数值包括 如果您的应用不在浏览器上,则需要刷新访问令牌,请将该值设置为 |
||||||
state |
推荐
指定应用在授权请求与授权服务器响应之间保持状态的任何字符串值。
在用户同意或拒绝您的应用的访问请求后,服务器会返回您在 此参数可用于多种用途,例如将用户定向到应用中的正确资源、发送 Nonce 以及减少跨网站伪造请求。由于您的 |
||||||
include_granted_scopes |
可选
允许应用使用增量授权在上下文中请求访问其他范围。如果您将此参数的值设为 |
||||||
login_hint |
可选
如果您的应用知道哪个用户正尝试进行身份验证,它可以使用此参数来向 Google Authentication 服务器提供提示。服务器会利用提示来简化登录流程,具体方法是在登录表单中预先填充电子邮件字段,或选择相应的多帐号登录会话。 将参数值设为电子邮件地址或 |
||||||
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 = auth_client.authorization_uri.to_s
- 将用户重定向到
auth_uri
。
Node.js
-
使用在第 1 步生成的
generateAuthUrl
方法生成的网址authorizationUrl
向 Google 的 OAuth 2.0 服务器请求访问权限。 -
将用户重定向到
authorizationUrl
。res.writeHead(301, { "Location": authorizationUrl });
HTTP/REST
Sample redirect to Google's authorization server
An example URL is shown below, with line breaks and spaces for readability.
https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly& access_type=offline& include_granted_scopes=true& response_type=code& state=state_parameter_passthrough_value& redirect_uri=https%3A//oauth2.example.com/code& client_id=client_id
创建请求网址后,将用户重定向到该网址。
Google 的 OAuth 2.0 服务器对用户进行身份验证,并在征得用户同意后允许您的应用访问所请求的范围。系统会使用您指定的重定向网址将响应发送回您的应用。
第 3 步:Google 提示用户同意
在此步骤中,用户可以决定是否向应用请求访问权限。在此阶段,Google 会显示一个意见征求窗口,其中会显示应用的名称以及使用用户的授权凭据请求访问权限的 Google API 服务以及要授予的访问权限范围的摘要。然后,用户可以同意向您的应用所请求的一个或多个范围授予访问权限,或拒绝该请求。
在此阶段,您的应用会等待 Google 的 OAuth 2.0 服务器响应(表明是否授予了任何访问权限),因此在此阶段无需执行任何操作。下一步中说明了该响应。
错误
向 Google 的 OAuth 2.0 授权端点发送请求可能会显示面向用户的错误消息,而不是预期的身份验证和授权流程。下面列出了常见的错误代码和建议的解决方法。
admin_policy_enforced
根据其 Google Workspace 管理员的政策,Google 帐号无法向所请求的一个或多个范围授权。请参阅 Google Workspace 管理员帮助文章控制哪些第三方应用和内部应用访问 Google Workspace 数据,详细了解管理员可以如何限制对所有范围的访问,或者限制敏感范围和受限范围,直到系统向您的 OAuth 客户端 ID 明确授予访问权限。
disallowed_useragent
授权端点显示在 Google #29.0 政策所禁止的嵌入式用户代理中。
Android
Android 开发者在 android.webkit.WebView
中打开授权请求时可能会遇到此错误消息。开发者应改用 Android 库,如 Android 版 Google 登录或 OpenID Foundation' 的 AppAuth for Android。
当 Android 应用打开嵌入式用户代理中的常规网页链接,并且用户从您的网站导航到 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 授权端点,网站开发者可能会遇到此错误。开发者应允许打开操作系统的默认链接处理程序中的常规链接,其中包括通用链接处理程序或默认浏览器应用。SFSafariViewController
库也是一个受支持的选项。
org_internal
请求中的 OAuth 客户端 ID 属于限制对特定 Google Cloud 组织中的 Google 帐号的访问的项目。如需详细了解此配置选项,请参阅“设置 OAuth 权限请求页面”帮助文章中的用户类型部分。
redirect_uri_mismatch
授权请求中传递的 redirect_uri
与 OAuth 客户端 ID 的授权重定向 URI 不匹配。查看 Google API Console Credentials page中已获授权的重定向 URI。
第 4 步:处理 OAuth 2.0 服务器响应
OAuth 2.0 服务器使用请求中指定的网址响应您应用的访问请求。
如果用户批准了该访问请求,响应中将包含授权代码。如果用户未批准该请求,则响应中会包含错误消息。返回网络服务器的授权代码或错误消息会显示在查询字符串上,如下所示:
错误响应:
https://oauth2.example.com/auth?error=access_denied
授权代码响应:
https://oauth2.example.com/auth?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7
OAuth 2.0 服务器响应示例
您可以点击以下示例网址(请求以只读权限查看您 Google 云端硬盘中文件的元数据)测试此流程:
https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly& access_type=offline& include_granted_scopes=true& response_type=code& state=state_parameter_passthrough_value& redirect_uri=https%3A//oauth2.example.com/code& client_id=client_id
完成 OAuth 2.0 流程后,系统应该会将您重定向到 http://localhost/oauth2callback
,否则,除非您的本地计算机在该地址提供文件,否则很可能会出现 404 NOT FOUND
错误。下一步会更详细地介绍当用户重定向回您的应用时在 URI 中返回的信息。
第 5 步:使用授权代码交换刷新令牌和访问令牌
网络服务器收到授权代码后,可以使用授权代码换取访问令牌。
PHP
如需用授权代码交换访问令牌,请使用 authenticate
方法:
$client->authenticate($_GET['code']);
您可以使用 getAccessToken
方法检索访问令牌:
$access_token = $client->getAccessToken();
Python
在回调页面上,使用 google-auth
库验证授权服务器响应。然后,使用 flow.fetch_token
方法用该响应中的授权代码换取访问令牌:
state = flask.session['state'] flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( 'client_secret.json', scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'], state=state) flow.redirect_uri = flask.url_for('oauth2callback', _external=True) authorization_response = flask.request.url flow.fetch_token(authorization_response=authorization_response) # Store the credentials in the session. # ACTION ITEM for developers: # Store user's access and refresh tokens in your data store if # incorporating this code into your real app. credentials = flow.credentials flask.session['credentials'] = { 'token': credentials.token, 'refresh_token': credentials.refresh_token, 'token_uri': credentials.token_uri, 'client_id': credentials.client_id, 'client_secret': credentials.client_secret, 'scopes': credentials.scopes}
Ruby
如需用授权代码交换访问令牌,请使用 fetch_access_token!
方法:
auth_client.code = auth_code auth_client.fetch_access_token!
Node.js
如需用授权代码交换访问令牌,请使用 getToken
方法:
const url = require('url'); // Receive the callback from Google's OAuth 2.0 server. if (req.url.startsWith('/oauth2callback')) { // Handle the OAuth 2.0 server response let q = url.parse(req.url, true).query; // Get access and refresh tokens (if access_type is offline) let { tokens } = await oauth2Client.getToken(q.code); oauth2Client.setCredentials(tokens); }
HTTP/REST
如需用授权代码换取访问令牌,请调用 https://oauth2.googleapis.com/token
端点并设置以下参数:
字段 | |
---|---|
client_id |
从 API ConsoleCredentials page获取的客户端 ID。 |
client_secret |
从 API ConsoleCredentials page获取的客户端密钥。 |
code |
初始请求返回的授权代码。 |
grant_type |
如 OAuth 2.0 规范中所定义,此字段的值必须设置为 authorization_code 。 |
redirect_uri |
针对给定 client_id ,在 API ConsoleCredentials page 中为您的项目列出的其中一个重定向 URI。 |
以下代码段展示了一个示例请求:
POST /token HTTP/1.1 Host: oauth2.googleapis.com Content-Type: application/x-www-form-urlencoded code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7& client_id=your_client_id& client_secret=your_client_secret& redirect_uri=https%3A//oauth2.example.com/code& grant_type=authorization_code
Google 通过返回包含短期访问令牌和刷新令牌的 JSON 对象来响应此请求。请注意,只有在向 Google 的授权服务器发出初始请求时,您的应用将 access_type
参数设置为 offline
,才会返回刷新令牌。
响应包含以下字段:
字段 | |
---|---|
access_token |
您的应用发送的用于授权 Google API 请求的令牌。 |
expires_in |
访问令牌的剩余生命周期(以秒为单位)。 |
refresh_token |
可用于获取新访问令牌的令牌。刷新令牌在用户撤消访问权限之前一直有效。
同样,仅当在向 Google 授权服务器发出的初始请求中将 access_type 参数设置为 offline 时,此字段中才会显示此字段。
|
scope |
access_token 授予的访问权限范围,表示为以空格分隔且区分大小写的字符串列表。 |
token_type |
返回的令牌类型。此时,此字段的值始终设置为 Bearer 。 |
以下代码段展示了一个响应示例:
{ "access_token": "1/fFAGRNJru1FTz70BzhT3Zg", "expires_in": 3920, "token_type": "Bearer", "scope": "https://www.googleapis.com/auth/drive.metadata.readonly", "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" }
调用 Google API
PHP
完成以下步骤,使用访问令牌调用 Google API:
- 如果您需要将访问令牌应用于新的
Google\Client
对象(例如,如果您将访问令牌存储在用户会话中),请使用setAccessToken
方法:$client->setAccessToken($access_token);
- 为您要调用的 API 构建服务对象。您可以通过为要调用的 API 的构造函数提供已获授权的
Google\Client
对象来构建服务对象。例如,如需调用 Drive API:$drive = new Google\Service\Drive($client);
- 使用服务对象提供的接口向 API 服务发出请求。
例如,如需列出经过身份验证的用户的 Google 云端硬盘中的文件,请按以下步骤操作:
$files = $drive->files->listFiles(array())->getItems();
Python
获取访问令牌后,您的应用可以使用该令牌代表指定的用户帐号或服务帐号对 API 请求进行授权。使用特定于用户的授权凭据为您要调用的 API 构建服务对象,然后使用该对象发出已获授权的 API 请求。
- 为您要调用的 API 构建服务对象。您可以使用 API 的名称和用户凭据调用
googleapiclient.discovery
库的build
方法来构建服务对象: 例如,调用 Drive API 的版本 2:from googleapiclient.discovery import build drive = build('drive', 'v2', credentials=credentials)
- 使用服务对象提供的接口向 API 服务发出请求。
例如,如需列出经过身份验证的用户的 Google 云端硬盘中的文件,请按以下步骤操作:
files = drive.files().list().execute()
Ruby
完成以下步骤,使用 auth_client
对象调用 Google API:
- 为您要调用的 API 构建服务对象。
例如,调用 Drive API 版本 2:
drive = Google::Apis::DriveV2::DriveService.new
- 为服务设置凭据:
drive.authorization = auth_client
- 使用服务对象提供的接口向 API 服务发出请求。
例如,如需列出经过身份验证的用户的 Google 云端硬盘中的文件,请按以下步骤操作:
files = drive.list_files
或者,您也可以通过向方法提供 options
参数,为每个方法提供授权:
files = drive.list_files(options: { authorization: auth_client })
Node.js
获取访问令牌并将其设置为 OAuth2
对象后,使用该对象调用 Google API。您的应用可以使用该令牌代表指定的用户帐号或服务帐号对 API 请求进行授权。为您要调用的 API 构建服务对象。
const { google } = require('googleapis'); // Example of using Google Drive API to list filenames in user's Drive. const drive = google.drive('v3'); drive.files.list({ auth: oauth2Client, pageSize: 10, fields: 'nextPageToken, files(id, name)', }, (err1, res1) => { if (err1) return console.log('The API returned an error: ' + err1); const files = res1.data.files; if (files.length) { console.log('Files:'); files.map((file) => { console.log(`${file.name} (${file.id})`); }); } else { console.log('No files found.'); } });
HTTP/REST
在您的应用获得访问令牌后,您可以使用该令牌代表指定用户帐号调用 Google API(如果已授予 API 所需的访问权限范围)。为此,请在向 API 发出的请求中添加访问令牌,具体方法是添加 access_token
查询参数或 Authorization
HTTP 标头 Bearer
值。最好使用 HTTP 标头,因为查询字符串往往会显示在服务器日志中。在大多数情况下,您可以使用客户端库设置对 Google API 的调用(例如,在调用 Drive Files API 时)。
您可以访问 OAuth 2.0 Playground,试用所有 Google API 及其范围。
HTTP GET 示例
使用 Authorization: Bearer
HTTP 标头对 drive.files
端点 (Drive Files API) 的调用可能如下所示。请注意,您需要指定自己的访问令牌:
GET /drive/v2/files HTTP/1.1 Host: www.googleapis.com Authorization: Bearer access_token
以下是使用 access_token
查询字符串参数对经过身份验证的用户的同一 API 的调用:
GET https://www.googleapis.com/drive/v2/files?access_token=access_token
curl
示例
您可以使用 curl
命令行应用测试这些命令。下面是一个使用 HTTP 标头选项(首选)的示例:
curl -H "Authorization: Bearer access_token" https://www.googleapis.com/drive/v2/files
或者,查询字符串参数选项:
curl https://www.googleapis.com/drive/v2/files?access_token=access_token
完整示例
以下示例在用户完成身份验证并同意应用访问用户的云端硬盘元数据后,输出用户 Google 云端硬盘中 JSON 格式的文件列表。
PHP
如需运行此示例,请执行以下操作:
- 在 API Console中,将本地机器的网址添加到重定向网址列表中。例如,添加
http://localhost:8080
。 - 创建一个新目录并切换到该目录。例如:
mkdir ~/php-oauth2-example cd ~/php-oauth2-example
。 - 使用 Composer 安装适用于 PHP 的 Google API 客户端库:
composer require google/apiclient:^2.10
- 使用以下内容创建
index.php
和oauth2callback.php
文件。 - 使用配置为提供 PHP 的网络服务器运行该示例。如果您使用的是 PHP 5.6 或更高版本,则可以使用 PHP 的内置测试 Web 服务器:
php -S localhost:8080 ~/php-oauth2-example
index.php
<?php require_once __DIR__.'/vendor/autoload.php'; session_start(); $client = new Google\Client(); $client->setAuthConfig('client_secrets.json'); $client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY); if (isset($_SESSION['access_token']) && $_SESSION['access_token']) { $client->setAccessToken($_SESSION['access_token']); $drive = new Google\Service\Drive($client); $files = $drive->files->listFiles(array())->getItems(); echo json_encode($files); } else { $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php'; header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL)); }
oauth2callback.php
<?php require_once __DIR__.'/vendor/autoload.php'; session_start(); $client = new Google\Client(); $client->setAuthConfigFile('client_secrets.json'); $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php'); $client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY); if (! isset($_GET['code'])) { $auth_url = $client->createAuthUrl(); header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL)); } else { $client->authenticate($_GET['code']); $_SESSION['access_token'] = $client->getAccessToken(); $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/'; header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL)); }
Python
此示例使用 Flask 框架。它会在 http://localhost:8080
中运行 Web 应用,让您可以测试 OAuth 2.0 流程。如果您转到该网址,应该会看到四个链接:
- 测试 API 请求:此链接指向尝试执行示例 API 请求的页面。必要时,它会启动授权流程。如果成功,该页面会显示 API 响应。
- 直接测试身份验证流程:此链接指向一个尝试通过授权流程将用户引导至的页面。应用请求代表用户提交已获授权的 API 请求的权限。
- 撤消当前凭据:此链接指向的页面会撤消用户已授予应用的权限。
- 清除 Flask 会话凭据:此链接会清除 Flask 会话中存储的授权凭据。这样一来,您就可以了解如果已经向您的应用授予权限的用户尝试在新会话中执行 API 请求会发生什么情况。此外,如果用户已撤消向您的应用授予的权限,并且您的应用仍尝试使用已撤消的访问令牌授权请求,您还可以查看应用收到的 API 响应。
# -*- coding: utf-8 -*- import os import flask import requests import google.oauth2.credentials import google_auth_oauthlib.flow import googleapiclient.discovery # This variable specifies the name of a file that contains the OAuth 2.0 # information for this application, including its client_id and client_secret. CLIENT_SECRETS_FILE = "client_secret.json" # This OAuth 2.0 access scope allows for full read/write access to the # authenticated user's account and requires requests to use an SSL connection. SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'] API_SERVICE_NAME = 'drive' API_VERSION = 'v2' app = flask.Flask(__name__) # Note: A secret key is included in the sample so that it works. # If you use this code in your application, replace this with a truly secret # key. See https://flask.palletsprojects.com/quickstart/#sessions. app.secret_key = 'REPLACE ME - this value is here as a placeholder.' @app.route('/') def index(): return print_index_table() @app.route('/test') def test_api_request(): if 'credentials' not in flask.session: return flask.redirect('authorize') # Load credentials from the session. credentials = google.oauth2.credentials.Credentials( **flask.session['credentials']) drive = googleapiclient.discovery.build( API_SERVICE_NAME, API_VERSION, credentials=credentials) files = drive.files().list().execute() # Save credentials back to session in case access token was refreshed. # ACTION ITEM: In a production app, you likely want to save these # credentials in a persistent database instead. flask.session['credentials'] = credentials_to_dict(credentials) return flask.jsonify(**files) @app.route('/authorize') def authorize(): # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps. flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=SCOPES) # The URI created here must exactly match one of the authorized redirect URIs # for the OAuth 2.0 client, which you configured in the API Console. If this # value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch' # error. flow.redirect_uri = flask.url_for('oauth2callback', _external=True) authorization_url, state = flow.authorization_url( # Enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. access_type='offline', # Enable incremental authorization. Recommended as a best practice. include_granted_scopes='true') # Store the state so the callback can verify the auth server response. flask.session['state'] = state return flask.redirect(authorization_url) @app.route('/oauth2callback') def oauth2callback(): # Specify the state when creating the flow in the callback so that it can # verified in the authorization server response. state = flask.session['state'] flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=SCOPES, state=state) flow.redirect_uri = flask.url_for('oauth2callback', _external=True) # Use the authorization server's response to fetch the OAuth 2.0 tokens. authorization_response = flask.request.url flow.fetch_token(authorization_response=authorization_response) # Store credentials in the session. # ACTION ITEM: In a production app, you likely want to save these # credentials in a persistent database instead. credentials = flow.credentials flask.session['credentials'] = credentials_to_dict(credentials) return flask.redirect(flask.url_for('test_api_request')) @app.route('/revoke') def revoke(): if 'credentials' not in flask.session: return ('You need to <a href="/authorize">authorize</a> before ' + 'testing the code to revoke credentials.') credentials = google.oauth2.credentials.Credentials( **flask.session['credentials']) revoke = requests.post('https://oauth2.googleapis.com/revoke', params={'token': credentials.token}, headers = {'content-type': 'application/x-www-form-urlencoded'}) status_code = getattr(revoke, 'status_code') if status_code == 200: return('Credentials successfully revoked.' + print_index_table()) else: return('An error occurred.' + print_index_table()) @app.route('/clear') def clear_credentials(): if 'credentials' in flask.session: del flask.session['credentials'] return ('Credentials have been cleared.<br><br>' + print_index_table()) def credentials_to_dict(credentials): return {'token': credentials.token, 'refresh_token': credentials.refresh_token, 'token_uri': credentials.token_uri, 'client_id': credentials.client_id, 'client_secret': credentials.client_secret, 'scopes': credentials.scopes} def print_index_table(): return ('<table>' + '<tr><td><a href="/test">Test an API request</a></td>' + '<td>Submit an API request and see a formatted JSON response. ' + ' Go through the authorization flow if there are no stored ' + ' credentials for the user.</td></tr>' + '<tr><td><a href="/authorize">Test the auth flow directly</a></td>' + '<td>Go directly to the authorization flow. If there are stored ' + ' credentials, you still might not be prompted to reauthorize ' + ' the application.</td></tr>' + '<tr><td><a href="/revoke">Revoke current credentials</a></td>' + '<td>Revoke the access token associated with the current user ' + ' session. After revoking credentials, if you go to the test ' + ' page, you should see an <code>invalid_grant</code> error.' + '</td></tr>' + '<tr><td><a href="/clear">Clear Flask session credentials</a></td>' + '<td>Clear the access token currently stored in the user session. ' + ' After clearing the token, if you <a href="/test">test the ' + ' API request</a> again, you should go back to the auth flow.' + '</td></tr></table>') if __name__ == '__main__': # When running locally, disable OAuthlib's HTTPs verification. # ACTION ITEM for developers: # When running in production *do not* leave this option enabled. os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # Specify a hostname and port that are set as a valid redirect URI # for your API project in the Google API Console. app.run('localhost', 8080, debug=True)
Ruby
此示例使用 Sinatra 框架。
require 'google/apis/drive_v2' require 'google/api_client/client_secrets' require 'json' require 'sinatra' enable :sessions set :session_secret, 'setme' get '/' do unless session.has_key?(:credentials) redirect to('/oauth2callback') end client_opts = JSON.parse(session[:credentials]) auth_client = Signet::OAuth2::Client.new(client_opts) drive = Google::Apis::DriveV2::DriveService.new files = drive.list_files(options: { authorization: auth_client }) "<pre>#{JSON.pretty_generate(files.to_h)}</pre>" end get '/oauth2callback' do client_secrets = Google::APIClient::ClientSecrets.load auth_client = client_secrets.to_authorization auth_client.update!( :scope => 'https://www.googleapis.com/auth/drive.metadata.readonly', :redirect_uri => url('/oauth2callback')) if request['code'] == nil auth_uri = auth_client.authorization_uri.to_s redirect to(auth_uri) else auth_client.code = request['code'] auth_client.fetch_access_token! auth_client.client_secret = nil session[:credentials] = auth_client.to_json redirect to('/') end end
Node.js
如需运行此示例,请执行以下操作:
-
在 API Console中,将本地机器的网址添加到重定向网址列表中。例如,添加
http://localhost
。 - 请确保您已安装维护 LTS、有效 LTS 或已安装 Node.js 的当前版本。
-
创建一个新目录并切换到该目录。例如:
mkdir ~/nodejs-oauth2-example cd ~/nodejs-oauth2-example
-
Install the
Google API Client
Library
for Node.js using npm:
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'); /** * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI. * To get these credentials for your application, visit * https://console.cloud.google.com/apis/credentials. */ const oauth2Client = new google.auth.OAuth2( YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, YOUR_REDIRECT_URL ); // Access scopes for read-only Drive activity. const scopes = [ 'https://www.googleapis.com/auth/drive.metadata.readonly' ]; // Generate a url that asks permissions for the Drive activity scope const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as a best practice. include_granted_scopes: true }); /* Global variable that stores user credential in this code example. * ACTION ITEM for developers: * Store user's refresh token in your data store if * incorporating this code into your real app. * For more information on handling refresh tokens, * see https://github.com/googleapis/google-api-nodejs-client#handling-refresh-tokens */ let userCredential = null; async function main() { const server = http.createServer(async function (req, res) { // Example on redirecting user to Google's OAuth 2.0 server. if (req.url == '/') { res.writeHead(301, { "Location": authorizationUrl }); } // Receive the callback from Google's OAuth 2.0 server. if (req.url.startsWith('/oauth2callback')) { // Handle the OAuth 2.0 server response let q = url.parse(req.url, true).query; if (q.error) { // An error response e.g. error=access_denied console.log('Error:' + q.error); } else { // Get access and refresh tokens (if access_type is offline) let { tokens } = await oauth2Client.getToken(q.code); oauth2Client.setCredentials(tokens); /** Save credential to the global variable in case access token was refreshed. * ACTION ITEM: In a production app, you likely want to save the refresh token * in a secure persistent database instead. */ userCredential = tokens; // Example of using Google Drive API to list filenames in user's Drive. const drive = google.drive('v3'); drive.files.list({ auth: oauth2Client, pageSize: 10, fields: 'nextPageToken, files(id, name)', }, (err1, res1) => { if (err1) return console.log('The API returned an error: ' + err1); const files = res1.data.files; if (files.length) { console.log('Files:'); files.map((file) => { console.log(`${file.name} (${file.id})`); }); } else { console.log('No files found.'); } }); } } // Example on revoking a token if (req.url == '/revoke') { // Build the string for the POST request let postData = "token=" + userCredential.access_token; // Options for POST request to Google's OAuth 2.0 server to revoke a token let postOptions = { host: 'oauth2.googleapis.com', port: '443', path: '/revoke', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) } }; // Set up the request const postReq = https.request(postOptions, function (res) { res.setEncoding('utf8'); res.on('data', d => { console.log('Response: ' + d); }); }); postReq.on('error', error => { console.log(error) }); // Post the request with data postReq.write(postData); postReq.end(); } res.end(); }).listen(80); } main().catch(console.error);
HTTP/REST
此 Python 示例使用 Flask 框架和 Requests 库来演示 OAuth 2.0 Web 流程。对于此流程,我们建议使用 Python 版 Google API 客户端库。(Python 标签页中的示例确实使用了客户端库。)
import json import flask import requests app = flask.Flask(__name__) CLIENT_ID = '123456789.apps.googleusercontent.com' CLIENT_SECRET = 'abc123' # Read from a file or environmental variable in a real app SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly' REDIRECT_URI = 'http://example.com/oauth2callback' @app.route('/') def index(): if 'credentials' not in flask.session: return flask.redirect(flask.url_for('oauth2callback')) credentials = json.loads(flask.session['credentials']) if credentials['expires_in'] <= 0: return flask.redirect(flask.url_for('oauth2callback')) else: headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])} req_uri = 'https://www.googleapis.com/drive/v2/files' r = requests.get(req_uri, headers=headers) return r.text @app.route('/oauth2callback') def oauth2callback(): if 'code' not in flask.request.args: auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code' '&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE) return flask.redirect(auth_uri) else: auth_code = flask.request.args.get('code') data = {'code': auth_code, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'redirect_uri': REDIRECT_URI, 'grant_type': 'authorization_code'} r = requests.post('https://oauth2.googleapis.com/token', data=data) flask.session['credentials'] = r.text return flask.redirect(flask.url_for('index')) if __name__ == '__main__': import uuid app.secret_key = str(uuid.uuid4()) app.debug = False app.run()
重定向 URI 验证规则
Google 会应用以下验证规则来重定向 URI,以帮助开发者确保其应用安全无虞。您的重定向 URI 必须遵守这些规则。如需了解网域、主机、路径、查询、架构和用户信息的定义,请参阅 RFC 3986 第 3 节。
验证规则 | |
---|---|
架构 |
重定向 URI 必须使用 HTTPS 架构,而不是普通的 HTTP。本地主机 URI(包括 localhost IP 地址 URI)不受此规则限制。 |
主机 |
主机不能是原始 IP 地址。本地主机 IP 地址不受此规则限制。 |
网域 |
“googleusercontent.com” 。goo.gl ),除非应用拥有此网域。此外,如果拥有短域名的网域应用选择重定向到该网域,则该重定向 URI 必须在其路径中包含 “/google-callback/” 或以 “/google-callback” 结尾。 |
用户信息 |
重定向 URI 不能包含 userinfo 子组件。 |
路径 |
重定向 URI 不能包含路径遍历(也称为目录回溯),由 |
查询 |
重定向 URI 不能包含开放式重定向。 |
fragment |
重定向 URI 不能包含 fragment 组件。 |
字符 |
重定向 URI 不能包含某些字符,包括:
|
增量授权
在 OAuth 2.0 协议中,您的应用会请求访问由范围标识的资源。这被认为是一种最佳的用户体验做法,即在您需要时对资源进行授权。为了启用这种做法,Google 的授权服务器支持增量授权。此功能可让您根据需要请求范围,如果用户为新范围授予权限,则会返回授权代码,该代码可能会交换包含用户向项目授予的所有范围的令牌。
例如,如果应用支持用户试听曲目和创建合辑,在登录时可能需要的资源可能很少,可能只有登录用户的名字。但是,保存已完成的合辑需要访问其 Google 云端硬盘。多数人会在应用确实需要的时候访问他们的 Google 云端硬盘,自然会发现应用很自然。
在这种情况下,在登录时,应用可能会请求 openid
和 profile
范围来执行基本登录,然后在第一次请求保存组合时请求 https://www.googleapis.com/auth/drive.file
范围。
要实现增量授权,您需要完成请求访问令牌的常规流程,但请确保授权请求包含之前授予的范围。这种方法可让您的应用不必管理多个访问令牌。
以下规则适用于通过增量授权获得的访问令牌:
- 此令牌可用于访问与新合并的授权中任何一个范围对应的资源。
- 当您使用合并授权的刷新令牌获取访问令牌时,访问令牌代表合并授权,可用于响应中包含的任何
scope
值。 - 合并授权包含用户向 API 项目授予的所有范围,即使授权来自不同的客户端也是如此。例如,如果用户使用应用的桌面客户端授予对一个范围的访问权限,然后通过移动客户端向同一应用授予另一个范围的访问权限,则合并授权将同时包括这两个范围。
- 如果您撤消代表合并授权的令牌,则代表关联用户同时撤消该授权范围的所有访问权限。
第 1 步:设置授权参数中特定于语言的代码示例以及第 2 步:重定向至 Google 的 OAuth 2.0 服务器中的示例 HTTP/REST 重定向网址均使用增量授权。下面的代码示例还显示了您需要添加才能使用增量授权的代码。
PHP
$client->setIncludeGrantedScopes(true);
Python
在 Python 中,将 include_granted_scopes
关键字参数设置为 true
,以确保授权请求包含先前授予的范围。极有可能是因为 include_granted_scopes
不是您设置的唯一关键字参数,如以下示例所示。
authorization_url, state = flow.authorization_url( # Enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. access_type='offline', # Enable incremental authorization. Recommended as a best practice. include_granted_scopes='true')
Ruby
auth_client.update!( :additional_parameters => {"include_granted_scopes" => "true"} )
Node.js
const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as a best practice. include_granted_scopes: true });
HTTP/REST
GET https://accounts.google.com/o/oauth2/v2/auth? client_id=your_client_id& response_type=code& state=state_parameter_passthrough_value& scope=https%3A//www.googleapis.com/auth/drive.file& redirect_uri=https%3A//oauth2.example.com/code& prompt=consent& include_granted_scopes=true
刷新访问令牌(离线访问)
访问令牌会定期过期,并成为相关 API 请求的无效凭据。如果您请求离线访问与此令牌相关联的范围,则可以刷新访问令牌,而无需提示用户授予权限(包括用户不存在时)。
- 如果您使用 Google API 客户端库,则客户端对象会根据需要刷新访问令牌,前提是您将该对象配置为可离线访问。
- 如果您未使用客户端库,则需要在将用户重定向到 Google 的 OAuth 2.0 服务器时将
access_type
HTTP 查询参数设为offline
。在这种情况下,当您用授权代码交换访问令牌时,Google 的授权服务器会返回刷新令牌。然后,如果访问令牌过期(或其他任何时间),您可以使用刷新令牌来获取新的访问令牌。
对于需要在用户不存在时访问 Google API 的任何应用,请求离线访问是必需的。例如,如果应用在预定时间执行备份服务或执行操作,那么在用户不存在时必须能够刷新其访问令牌。默认访问方式称为 online
。
服务器端 Web 应用、已安装的应用和设备均在授权过程中获取刷新令牌。刷新令牌通常不用于客户端 (JavaScript) Web 应用。
PHP
如果您的应用需要离线访问 Google API,请将 API 客户端的访问权限类型设置为 offline
:
$client->setAccessType("offline");
在用户针对请求的作用域授予离线访问权限后,您可以在用户离线时继续使用 API 客户端代表用户访问 Google API。客户端对象将根据需要刷新访问令牌。
Python
在 Python 中,将 access_type
关键字参数设置为 offline
,以确保您可以刷新访问令牌,而无需重新提示用户授予权限。access_type
很可能不是您设置的唯一关键字参数,如以下示例所示。
authorization_url, state = flow.authorization_url( # Enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. access_type='offline', # Enable incremental authorization. Recommended as a best practice. include_granted_scopes='true')
在用户针对请求的作用域授予离线访问权限后,您可以在用户离线时继续使用 API 客户端代表用户访问 Google API。客户端对象将根据需要刷新访问令牌。
Ruby
如果您的应用需要离线访问 Google API,请将 API 客户端的访问权限类型设置为 offline
:
auth_client.update!( :additional_parameters => {"access_type" => "offline"} )
在用户针对请求的作用域授予离线访问权限后,您可以在用户离线时继续使用 API 客户端代表用户访问 Google API。客户端对象将根据需要刷新访问令牌。
Node.js
如果您的应用需要离线访问 Google API,请将 API 客户端的访问权限类型设置为 offline
:
const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as a best practice. include_granted_scopes: true });
在用户针对请求的作用域授予离线访问权限后,您可以在用户离线时继续使用 API 客户端代表用户访问 Google API。客户端对象将根据需要刷新访问令牌。
访问令牌会过期。此库将在即将过期时自动使用刷新令牌来获取新的访问令牌。若要确保始终存储最新的令牌,一种简单的方法是使用令牌事件:
oauth2Client.on('tokens', (tokens) => { if (tokens.refresh_token) { // store the refresh_token in your secure persistent database console.log(tokens.refresh_token); } console.log(tokens.access_token); });
此令牌事件仅在第一次授权中发生,在调用 generateAuthUrl
方法以接收刷新令牌时,您需要将 access_type
设置为 offline
。如果您已为应用授予必要的权限,但未设置适当的限制来接收刷新令牌,则需要重新向应用授权以接收新的刷新令牌。
如需稍后设置 refresh_token
,您可以使用 setCredentials
方法:
oauth2Client.setCredentials({ refresh_token: `STORED_REFRESH_TOKEN` });
客户端获得刷新令牌后,系统就会获取访问令牌并在下次调用该 API 时自动刷新。
HTTP/REST
如需刷新访问令牌,您的应用会向 Google 的授权服务器 (https://oauth2.googleapis.com/token
) 发送 HTTPS POST
请求,其中包含以下参数:
字段 | |
---|---|
client_id |
从 API Console获取的客户端 ID。 |
client_secret |
从 API Console获取的客户端密钥。 |
grant_type |
根据 OAuth 2.0 规范中所定义,此字段的值必须设置为 refresh_token 。 |
refresh_token |
从授权代码交换中返回的刷新令牌。 |
以下代码段展示了一个示例请求:
POST /token HTTP/1.1 Host: oauth2.googleapis.com Content-Type: application/x-www-form-urlencoded client_id=your_client_id& client_secret=your_client_secret& refresh_token=refresh_token& grant_type=refresh_token
只要用户尚未撤消授予应用的访问权限,令牌服务器就会返回一个包含新访问令牌的 JSON 对象。以下代码段是一个示例响应:
{ "access_token": "1/fFAGRNJru1FTz70BzhT3Zg", "expires_in": 3920, "scope": "https://www.googleapis.com/auth/drive.metadata.readonly", "token_type": "Bearer" }
请注意,将颁发的刷新令牌数量有限;每个客户端/用户组合各有一个限制,而所有客户端上每个用户都各有一个限制。您应将刷新令牌保存在长期存储空间中,并只要这些令牌保持有效状态,就会继续使用它们。如果您的应用请求过多的刷新令牌,可能会受这些限制的影响,在这种情况下,较旧的刷新令牌将停止运行。
撤消令牌
在某些情况下,用户可能会想撤消授予某个应用的访问权限。用户可以通过访问帐号设置来撤消访问权限。如需了解详情,请参阅移除有权访问您帐号的第三方网站和应用的网站或应用访问权限部分。
应用也能够以编程方式撤消其访问权限。在用户退订、移除应用或应用所需的 API 资源发生了显著变化的情况下,程序化撤消非常重要。换言之,移除流程的一部分可能包括 API 请求,以确保之前授予应用的权限被移除。
PHP
如需以编程方式撤消令牌,请调用 revokeToken()
:
$client->revokeToken();
Python
如需以编程方式撤消令牌,请向 https://oauth2.googleapis.com/revoke
发出请求,将该令牌作为参数并设置 Content-Type
标头:
requests.post('https://oauth2.googleapis.com/revoke', params={'token': credentials.token}, headers = {'content-type': 'application/x-www-form-urlencoded'})
Ruby
如需以编程方式撤消令牌,请向 oauth2.revoke
端点发出 HTTP 请求:
uri = URI('https://oauth2.googleapis.com/revoke') response = Net::HTTP.post_form(uri, 'token' => auth_client.access_token)
该令牌可以是访问令牌,也可以是刷新令牌。如果令牌是访问令牌,并且具有相应的刷新令牌,则刷新令牌也会被撤消。
如果成功处理了撤消,则响应的状态代码为 200
。对于错误情况,系统会返回状态代码 400
以及错误代码。
Node.js
如需以编程方式撤消令牌,请向 /revoke
端点发出 HTTPS POST 请求:
const https = require('https'); // Build the string for the POST request let postData = "token=" + userCredential.access_token; // Options for POST request to Google's OAuth 2.0 server to revoke a token let postOptions = { host: 'oauth2.googleapis.com', port: '443', path: '/revoke', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) } }; // Set up the request const postReq = https.request(postOptions, function (res) { res.setEncoding('utf8'); res.on('data', d => { console.log('Response: ' + d); }); }); postReq.on('error', error => { console.log(error) }); // Post the request with data postReq.write(postData); postReq.end();
令牌参数可以是访问令牌或刷新令牌。如果令牌是访问令牌,并且具有相应的刷新令牌,则刷新令牌也会被撤消。
如果成功处理了撤消,则响应的状态代码为 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
以及错误代码。