针对网络服务器应用使用 OAuth 2.0
https://developers.google.com/identity/protocols/oauth2/web-server?hl=zh-cn#offline
本文档介绍了 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 的 OAuth 2.0 端点互动时使用 OAuth 2.0 库。使用他人提供的经过充分调试的代码是最佳做法,有助于保护您自己和用户。如需了解详情,请参阅客户端库。
客户端库
本页面上特定于语言的示例使用 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 文件。将文件安全地存储在只有您的应用可以访问的位置。
重要提示:请勿将 client_secret.json 文件存储在可公开访问的位置。此外,如果您与应用共享源代码(例如,在 GitHub 上),请将 client_secret.json 文件存储在源代码树之外,以避免无意中共享您的客户端凭据。
确定访问权限范围
通过范围,您的应用可以仅请求访问所需的资源,同时还能控制用户向您的应用授予的访问量。因此,请求的范围数与征得用户同意的可能性之间可能存在逆向关系。
在开始实现 OAuth 2.0 授权之前,我们建议您确定应用需要访问权限的范围。
我们还建议您的应用通过增量授权流程请求对授权范围的访问权限,在此过程中,您的应用会在上下文中请求访问用户数据。此最佳做法可帮助用户更轻松地了解应用需要其请求的访问权限的原因。
OAuth 2.0 API 范围文档包含可用于访问 Google API 的完整范围列表。
如果您的公共应用使用了允许访问特定用户数据的范围,则必须完成验证流程。测试您的应用时,如果您在屏幕上看到未经验证的应用,您必须提交验证请求以将其移除。请详细了解未经验证的应用,并在帮助中心获取有关应用验证的常见问题解答。
特定语言要求
要运行本文档中的任何代码示例,您需要拥有 Google 帐号、互联网访问权限和网络浏览器。如果您使用的是某个 API 客户端库,另请参阅下文针对特定语言的要求。
获取 OAuth 2.0 访问令牌
以下步骤显示了您的应用如何与 Google 的 OAuth 2.0 服务器进行交互,以获取用户同意代表用户执行 API 请求。您的应用必须先获得用户同意,然后才能执行需要用户授权的 Google API 请求。
下面的列表快速总结了这些步骤:
您的应用确定所需的权限。
您的应用会将用户连同请求的权限列表一起重定向到 Google。
用户决定是否向您的应用授予权限。
您的应用会查找用户的决定。
如果用户被授予所请求的权限,您的应用将检索代表用户发出 API 请求所需的令牌。
第 1 步:设置授权参数
第一步是创建授权请求。该请求会设置用于标识您的应用的参数,并定义用户将向您的应用授予的权限。
如果您使用 Google 客户端库进行 OAuth 2.0 身份验证和授权,则需要创建并配置用于定义这些参数的对象。
如果您直接调用 Google OAuth 2.0 端点,系统会生成一个网址并针对该网址设置参数。
以下标签定义了网络服务器应用支持的授权参数。各种语言的示例还展示了如何使用客户端库或授权库来配置可设置这些参数的对象。
第 2 步:重定向到 Google 的 OAuth 2.0 服务器
将用户重定向到 Google 的 OAuth 2.0 服务器,以启动身份验证和授权流程。这通常发生在您的应用首次需要访问用户数据时。对于增量授权,当您的应用首次需要访问其尚未有权访问的其他资源时,也会执行此步骤。
Google 的 OAuth 2.0 服务器对用户进行身份验证,并在征得用户同意后允许您的应用访问所请求的范围。系统会使用您指定的重定向网址将响应发送回您的应用。
第 3 步:Google 提示用户同意
在此步骤中,用户可以决定是否向应用请求访问权限。在此阶段,Google 会显示一个意见征求窗口,其中会显示应用的名称以及使用用户的授权凭据请求访问权限的 Google API 服务以及要授予的访问权限范围的摘要。然后,用户可以同意向您的应用所请求的一个或多个范围授予访问权限,或拒绝该请求。
在此阶段,您的应用会等待 Google 的 OAuth 2.0 服务器响应(表明是否授予了任何访问权限),因此在此阶段无需执行任何操作。下一步中说明了该响应。
第 4 步:处理 OAuth 2.0 服务器响应
OAuth 2.0 服务器使用请求中指定的网址响应您应用的访问请求。
如果用户批准了该访问请求,响应中将包含授权代码。如果用户未批准该请求,则响应中会包含错误消息。返回网络服务器的授权代码或错误消息会显示在查询字符串上,如下所示:
第 5 步:使用授权代码交换刷新令牌和访问令牌
网络服务器收到授权代码后,可以使用授权代码换取访问令牌。
调用 Google API
重定向 URI 验证规则
Google 会应用以下验证规则来重定向 URI,以帮助开发者确保其应用安全无虞。您的重定向 URI 必须遵守这些规则。如需了解网域、主机、路径、查询、架构和用户信息的定义,请参阅 RFC 3986 第 3 节。
增量授权
在 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 重定向网址均使用增量授权。下面的代码示例还显示了您需要添加才能使用增量授权的代码。
刷新访问令牌(离线访问)
访问令牌会定期过期,并成为相关 API 请求的无效凭据。如果您请求离线访问与此令牌相关联的范围,则可以刷新访问令牌,而无需提示用户授予权限(包括用户不存在时)。
- 如果您使用 Google API 客户端库,则客户端对象会根据需要刷新访问令牌,前提是您将该对象配置为可离线访问。
- 如果您未使用客户端库,则需要在将用户重定向到 Google 的 OAuth 2.0 服务器时将
access_type
HTTP 查询参数设为offline
。在这种情况下,当您用授权代码交换访问令牌时,Google 的授权服务器会返回刷新令牌。然后,如果访问令牌过期(或其他任何时间),您可以使用刷新令牌来获取新的访问令牌。
对于需要在用户不存在时访问 Google API 的任何应用,请求离线访问是必需的。例如,如果应用在预定时间执行备份服务或执行操作,那么在用户不存在时必须能够刷新其访问令牌。默认访问方式称为 online
。
服务器端 Web 应用、已安装的应用和设备均在授权过程中获取刷新令牌。刷新令牌通常不用于客户端 (JavaScript) Web 应用。
撤消令牌
在某些情况下,用户可能会想撤消授予某个应用的访问权限。用户可以通过访问帐号设置来撤消访问权限。如需了解详情,请参阅移除有权访问您帐号的第三方网站和应用的网站或应用访问权限部分。
应用也能够以编程方式撤消其访问权限。在用户退订、移除应用或应用所需的 API 资源发生了显著变化的情况下,程序化撤消非常重要。换言之,移除流程的一部分可能包括 API 请求,以确保之前授予应用的权限被移除。
注意:成功撤消后,可能需要一些时间才能完全撤消。
完整示例
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()
问题: 为什么先请求授权码,然后用授权码获取访问令牌。 为什么不直接返回访问令牌。