OpenID Connect

Google 的 OAuth 2.0 API 可用于身份验证和授权。本文档介绍了我们的 OAuth 2.0 身份验证实现,该实现符合 OpenID Connect 规范,并获得 OpenID 认证使用 OAuth 2.0 访问 Google API 中的文档也适用于此服务。如果您想以交互方式探索此协议,我们建议您使用 Google OAuth 2.0 Playground。如需有关 Stack Overflow 的帮助,请使用 'google-oauth' 标记您的问题。

设置 OAuth 2.0

您必须先在 Google API Console 中设置项目以获取 OAuth 2.0 凭据,设置重定向 URI,并(可选)自定义要在用户同意屏幕上显示的品牌信息,您的应用可以使用 Google 的 OAuth 2.0 身份验证系统进行用户登录。您还可以使用 API Console 创建服务帐号、启用结算功能、设置过滤以及执行其他任务。如需了解详情,请参阅 Google API Console帮助

获取 OAuth 2.0 凭据

您需要 OAuth 2.0 凭据(包括客户端 ID 和客户端密钥),才能对用户进行身份验证并获得 Google 的 API 访问权限。

要查看给定OAuth 2.0凭据的客户端ID和客户端密钥,请单击以下文本: 选择凭据 。在打开的窗口中,选择您的项目和所需的凭证,然后单击“ 查看”

或者,从API Console的“ 凭据”页面中查看您的客户端ID和客户端密钥:

  1. Go to the Credentials page.
  2. 单击您的凭证名称或铅笔( )图标。您的客户ID和密码位于页面顶部。

设置重定向 URI

您在 API Console 中设置的重定向 URI 决定了 Google 将身份验证请求响应发送到何处。

要创建,查看或编辑给定OAuth 2.0凭据的重定向URI,请执行以下操作:

  1. Go to the Credentials page.
  2. 在页面的OAuth 2.0客户端ID部分中,点击一个凭据。
  3. 查看或编辑重定向URI。

如果“凭据”页面上没有OAuth 2.0客户端ID部分,则您的项目没有OAuth凭据。要创建一个,点击创建凭证

自定义用户意见征求屏幕

对于您的用户,OAuth 2.0 身份验证体验包括一个同意屏幕,该屏幕说明用户即将发布的信息和适用的条款。例如,当用户登录时,系统可能会要求您向应用授予访问其电子邮件地址和基本帐号信息的权限。您可以使用 scope 参数来请求访问此信息,此参数包含在应用的身份验证请求中。您还可以使用范围来请求访问其他 Google API。

用户同意屏幕也会显示品牌信息,例如您的产品名称、徽标和首页网址。您可以在 API Console中控制品牌信息。

要启用项目的同意屏幕:

  1. Consent Screen page中打开Google API Console 。
  2. If prompted, select a project, or create a new one.
  3. 填写表格,然后点击保存

以下意见征求对话框显示了当请求中存在 OAuth 2.0 和 Google 云端硬盘范围的组合时,用户会看到的内容。(此通用对话框是使用 Google OAuth 2.0 Playground 生成的,因此不包含将在 API Console中设置的品牌信息。)

同意页面屏幕截图

访问服务

Google 和第三方提供的库可用于处理许多身份验证细节,例如对用户进行身份验证以及获取 Google API 的访问权限。示例包括适用于各种平台的 Google Identity ServicesGoogle 客户端库

如果您选择不使用库,请按照本文档的其余部分中的说明操作,其中介绍了位于可用库下的 HTTP 请求流程。

对用户进行身份验证

对用户进行身份验证涉及获取 ID 令牌和验证令牌。ID 令牌OpenID Connect 的标准化功能,用于在互联网上共享身份断言。

用于验证用户身份和获取 ID 令牌的最常用方法称为“服务器”流程和“隐式”流程。服务器流程允许应用的后端服务器使用浏览器或移动设备验证用户的身份。当客户端应用(通常是在浏览器中运行的 JavaScript 应用)需要直接访问 API,而不是通过其后端服务器访问时,系统会使用隐式流程。

本文档介绍如何执行服务器流程以对用户进行身份验证。由于在客户端处理和使用令牌存在安全风险,隐式流程要复杂得多。如果您需要实现隐式流程,我们强烈建议您使用 Google Identity Services

服务器流

请务必在 API Console中设置您的应用,使其能够使用这些协议并对用户进行身份验证。当用户尝试通过 Google 登录时,您需要执行以下操作:

  1. 创建反假冒状态令牌
  2. 向 Google 发送身份验证请求
  3. 确认反假冒状态令牌
  4. 交换 code 以获取访问令牌和 ID 令牌
  5. 从 ID 令牌中获取用户信息
  6. 对用户进行身份验证

1. 创建反假冒状态令牌

您必须通过防止请求伪造攻击来保护用户的安全。第一步是创建一个唯一的会话令牌,用于存放您的应用与用户客户端之间的状态。 您稍后要将此唯一会话令牌与 Google OAuth 登录服务返回的身份验证响应相匹配,以验证用户发出请求的是而非恶意攻击者。这些令牌通常称为跨站请求伪造 (CSRF) 令牌。

状态令牌是一个不错的选择,是使用高质量随机数生成器构造的 30 个左右的字符串。另一个哈希是通过使用在后端保密的密钥对部分会话状态变量进行签名生成的。

以下代码演示了如何生成唯一的会话令牌。

PHP

您必须下载 PHP 版 Google API 客户端库才能使用此示例。

// Create a state token to prevent request forgery.
// Store it in the session for later validation.
$state = bin2hex(random_bytes(128/8));
$app['session']->set('state', $state);
// Set the client ID, token state, and application name in the HTML while
// serving it.
return $app['twig']->render('index.html', array(
    'CLIENT_ID' => CLIENT_ID,
    'STATE' => $state,
    'APPLICATION_NAME' => APPLICATION_NAME
));

Java

您必须下载适用于 Java 的 Google API 客户端库,才能使用此示例。

// Create a state token to prevent request forgery.
// Store it in the session for later validation.
String state = new BigInteger(130, new SecureRandom()).toString(32);
request.session().attribute("state", state);
// Read index.html into memory, and set the client ID,
// token state, and application name in the HTML before serving it.
return new Scanner(new File("index.html"), "UTF-8")
    .useDelimiter("\\A").next()
    .replaceAll("[{]{2}\\s*CLIENT_ID\\s*[}]{2}", CLIENT_ID)
    .replaceAll("[{]{2}\\s*STATE\\s*[}]{2}", state)
    .replaceAll("[{]{2}\\s*APPLICATION_NAME\\s*[}]{2}",
    APPLICATION_NAME);

Python

您必须下载 Python 版 Google API 客户端库才能使用此示例。

# Create a state token to prevent request forgery.
# Store it in the session for later validation.
state = hashlib.sha256(os.urandom(1024)).hexdigest()
session['state'] = state
# Set the client ID, token state, and application name in the HTML while
# serving it.
response = make_response(
    render_template('index.html',
                    CLIENT_ID=CLIENT_ID,
                    STATE=state,
                    APPLICATION_NAME=APPLICATION_NAME))

2. 向 Google 发送身份验证请求

下一步是使用适当的 URI 参数形成 HTTPS GET 请求。请注意,在此流程的所有步骤中都使用 HTTPS(而非 HTTP);HTTP 连接会被拒绝。您应该使用 authorization_endpoint 元数据值从发现文档中检索基本 URI。以下讨论假定基本 URI 为 https://accounts.google.com/o/oauth2/v2/auth

对于基本请求,请指定以下参数:

  • client_id(从 API ConsoleCredentials page获取)。
  • response_type,在基本授权代码流程中的请求应为 code。(如需了解详情,请访问 response_type。)
  • scope,在基本请求中应为 openid email。(如需了解详情,请访问 scope。)
  • redirect_uri 应该是您服务器上用于接收来自 Google 的响应的 HTTP 端点。该值必须与您在 API ConsoleCredentials page中配置的 OAuth 2.0 客户端的某个已获授权的重定向 URI 完全匹配。如果此值与已获授权的 URI 不匹配,则请求将失败并显示 redirect_uri_mismatch 错误。
  • state 应包含防伪造唯一会话令牌的值,以及在用户返回您的应用时恢复上下文所需的任何其他信息(例如起始网址)。(如需了解详情,请访问 state。)
  • nonce 是您的应用生成的一个随机值,可在出现重放保护时使用该保护。
  • login_hint 可以是用户的电子邮件地址,也可以是等效于用户的 Google ID 的 sub 字符串。如果您未提供 login_hint,并且用户当前已登录,则同意屏幕上会显示一条批准请求,要求将用户的电子邮件地址发布到您的应用。(如需了解详情,请访问 login_hint。)
  • 使用 hd 参数为与 Google Cloud 组织关联的特定网域的用户优化 OpenID Connect 流程。(如需了解详情,请访问 hd。)

下面是完整的 OpenID Connect 身份验证 URI 示例,其中换行符和空格都便于阅读:

https://accounts.google.com/o/oauth2/v2/auth?
 response_type=code&
 client_id=424911365001.apps.googleusercontent.com&
 scope=openid%20email&
 redirect_uri=https%3A//oauth2.example.com/code&
 state=security_token%3D138r5719ru3e1%26url%3Dhttps%3A%2F%2Foauth2-login-demo.example.com%2FmyHome&
 login_hint=jsmith@example.com&
 nonce=0394852-3190485-2490358&
 hd=example.com

如果您的应用需要关于用户的任何新信息,或者应用请求了他们之前未批准的帐号访问权限,则需要征得用户同意。

3.确认反假冒状态令牌

响应将发送到您在请求中指定的 redirect_uri。所有响应都会在查询字符串中返回,如下所示:

https://oauth2.example.com/code?state=security_token%3D138r5719ru3e1%26url%3Dhttps%3A%2F%2Foa2cb.example.com%2FmyHome&code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&scope=openid%20email%20https://www.googleapis.com/auth/userinfo.email

在服务器上,您必须确认从 Google 收到的 state 与您在第 1 步中创建的会话令牌相匹配。这种往返验证有助于确保用户发出请求,而不是恶意脚本。

以下代码演示了如何确认您在第 1 步中创建的会话令牌:

PHP

您必须下载 PHP 版 Google API 客户端库才能使用此示例。

// Ensure that there is no request forgery going on, and that the user
// sending us this connect request is the user that was supposed to.
if ($request->get('state') != ($app['session']->get('state'))) {
  return new Response('Invalid state parameter', 401);
}

Java

您必须下载适用于 Java 的 Google API 客户端库,才能使用此示例。

// Ensure that there is no request forgery going on, and that the user
// sending us this connect request is the user that was supposed to.
if (!request.queryParams("state").equals(
    request.session().attribute("state"))) {
  response.status(401);
  return GSON.toJson("Invalid state parameter.");
}

Python

您必须下载 Python 版 Google API 客户端库才能使用此示例。

# Ensure that the request is not a forgery and that the user sending
# this connect request is the expected user.
if request.args.get('state', '') != session['state']:
  response = make_response(json.dumps('Invalid state parameter.'), 401)
  response.headers['Content-Type'] = 'application/json'
  return response

4.将 code 换成访问令牌和 ID 令牌

响应中包含 code 参数,您的服务器可将其用于交换访问令牌和 ID 令牌的一次性授权代码。您的服务器通过发送 HTTPS POST 请求进行此交换。POST 请求会发送到令牌端点,您应使用 token_endpoint 元数据值从发现文档中检索该端点。以下讨论假定端点为 https://oauth2.googleapis.com/token。该请求必须在 POST 正文中包含以下参数:

字段
code 初始请求返回的授权代码。
client_id 您从 API ConsoleCredentials page获取的客户端 ID,如获取 OAuth 2.0 凭据中所述。
client_secret 您从 API ConsoleCredentials page获取的客户端密钥,如获取 OAuth 2.0 凭据中所述。
redirect_uri 为 API ConsoleCredentials page中指定的给定 client_id 授权的重定向 URI,如设置重定向 URI 中所述。
grant_type 此字段必须包含 authorization_code, 的值,如 OAuth 2.0 规范中定义。

实际请求可能如以下示例所示:

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

此请求的成功响应包含 JSON 数组中的以下字段:

字段
access_token 可发送给 Google API 的令牌。
expires_in 访问令牌的剩余生命周期(以秒为单位)。
id_token JWT,其中包含由 Google 进行数字签名的用户的身份信息。
scope access_token 授予的访问权限范围,表示为以空格分隔且区分大小写的字符串列表。
token_type 用于标识返回的令牌类型。目前,此字段始终具有值 Bearer
refresh_token (可选)

仅当 access_type 参数在身份验证请求中设为 offline 时,此字段才会显示。有关详情,请参阅刷新令牌

5. 从 ID 令牌中获取用户信息

ID 令牌是 JWT(JSON 网络令牌),即以加密方式签名的 Base64 编码的 JSON 对象。通常,您必须在使用前先验证 ID 令牌,但由于由于您是通过中间方的 HTTPS 渠道直接与 Google 通信并使用客户端密钥向 Google 进行身份验证的,因此可以确信自己收到的令牌确实来自 Google,并且有效。如果您的服务器将该 ID 令牌传递给应用的其他组件,在使用之前,其他组件必须验证该令牌

由于大多数 API 库都会将验证与解码 base64url 编码值并解析其中的 JSON 的工作结合使用,因此,在访问 ID 令牌中的声明时,您最终仍可能验证令牌。

ID 令牌的载荷

ID 令牌是包含一组名称/值对的 JSON 对象。下面是一个格式,便于阅读的示例:

{
  "iss": "https://accounts.google.com",
  "azp": "1234987819200.apps.googleusercontent.com",
  "aud": "1234987819200.apps.googleusercontent.com",
  "sub": "10769150350006150715113082367",
  "at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
  "hd": "example.com",
  "email": "jsmith@example.com",
  "email_verified": "true",
  "iat": 1353601026,
  "exp": 1353604926,
  "nonce": "0394852-3190485-2490358"
}

Google ID 令牌可能包含以下字段(称为声明):

Claim 已提供 说明
aud 始终 此 ID 令牌的目标受众群体。它必须是应用的 OAuth 2.0 客户端 ID 之一。
exp 始终 过期时间不得晚于 ID 令牌。以 Unix 时间(整数秒数)表示。
iat 始终 ID 令牌的发放时间。以 Unix 时间(整数秒数)表示。
iss 始终 响应发行机构的发行机构标识符。对于 Google ID 令牌,始终为 https://accounts.google.comaccounts.google.com
sub 始终 用户的标识符,在所有 Google 帐号中具有唯一性,且不得重复使用。一个 Google 帐号在不同时间点可以有多个电子邮件地址,但 sub 值永远不会改变。在您的应用中使用 sub 作为用户的唯一标识符键。长度上限为 255 个区分大小写的 ASCII 字符。
at_hash 访问令牌哈希。验证访问令牌是否与身份令牌相关联。如果在服务器流程中发送的 ID 令牌带有 access_token 值,此声明始终会包含在内。此声明可用作防范跨网站请求伪造攻击的备用机制,但如果您执行第 1 步第 3 步,则无需验证访问令牌。
azp 已获授权的演示者的 client_id。只有在请求 ID 令牌的一方与 ID 令牌的受众群体不同时,才需要此声明。对于混合式应用,如果 Web 应用和 Android 应用的 OAuth 2.0 client_id 不同,但共用一个 Google API 项目,那么 Google 可能会出现这种情况。
email 用户的电子邮件地址。此值可能不是此用户的唯一值,不适合用作主键。仅在您的范围包含 email 范围值时提供。
email_verified 如果用户的电子邮件地址已通过验证,则为 true;否则为 false。
family_name 用户的姓氏。如果存在 name 声明,则可能会提供。
given_name 用户的名字或名字。如果存在 name 声明,则可能会提供。
hd 与用户的 Google Cloud 组织关联的网域。仅当用户属于 Google Cloud 组织时提供。
locale 用户的语言区域,由 BCP 47 语言标记表示。如果存在 name 声明,则可能会提供。
name 用户的全名,以可显示的形式显示。在以下情况下,可能会提供:
  • 请求范围包括字符串 "profile"
  • ID 令牌通过令牌刷新返回

如果存在 name 声明,您可以使用它们来更新应用的用户记录。请注意,我们无法保证此版权主张确实存在。

nonce 您的应用在身份验证请求中提供的 nonce 值。您应确保它只出现一次,以防范重放攻击。
picture 用户个人资料照片的网址。在以下情况下,可能会提供:
  • 请求范围包括字符串 "profile"
  • ID 令牌通过令牌刷新返回

如果存在 picture 声明,您可以使用它们来更新应用的用户记录。请注意,我们无法保证此版权主张确实存在。

profile 用户的个人资料页面的网址。在以下情况下,可能会提供:
  • 请求范围包括字符串 "profile"
  • ID 令牌通过令牌刷新返回

如果存在 profile 声明,您可以使用它们来更新应用的用户记录。请注意,我们无法保证此版权主张确实存在。

6. 对用户进行身份验证

从 ID 令牌获取用户信息后,您应查询您应用的用户数据库。 如果数据库中已存在该用户,则当 Google API 响应满足所有登录要求时,您应为该用户启动应用会话。

如果用户数据库中不存在用户,您应将用户重定向到新用户注册流程。您或许能够根据从 Google 收到的信息来自动注册用户,或者至少能够预填充您在注册表单中要求的许多字段。除了 ID 令牌中的信息之外,您还可以在我们的用户个人资料端点获取额外的用户个人资料信息

高级主题

以下各部分更详细地介绍了 Google OAuth 2.0 API。本文面向对身份验证和授权有高级要求的开发者。

访问其他 Google API

使用 OAuth 2.0 进行身份验证的一个好处是,您的应用可以在您验证用户身份时,代表用户同时使用其他 Google API(例如 YouTube、Google 云端硬盘、日历或通讯录)。为此,请在发送给 Google 的身份验证请求中添加您需要的其他范围。例如,如需向身份验证请求添加用户的年龄段,请传递 openid email https://www.googleapis.com/auth/profile.agerange.read 的作用域参数。系统会在同意屏幕上提示用户。通过从 Google 发回的访问令牌,您可以访问与请求的访问权限相关且已获得访问权限的所有 API。

刷新令牌

在您的 API 访问权限请求中,您可以请求在 code 交换期间返回刷新令牌。刷新令牌可让您的应用持续访问 Google API,而用户并未出现在您的应用中。如需请求刷新令牌,请在身份验证请求中将 access_type 参数设置为 offline

注意事项:

  • 请务必安全且永久存储刷新令牌,因为您只能在首次执行代码交换流程时获取刷新令牌。
  • 颁发的刷新令牌数量有限:每个客户端/用户组合各有一个限制,所有客户端上每位用户都各有一个限制。如果您的应用请求过多的刷新令牌,则可能会受到这些限制,在这种情况下,较早的刷新令牌会停止运行。

如需了解详情,请参阅刷新访问令牌(离线访问)

您可以通过在身份验证请求中将 prompt 参数设置为 consent 来提示用户重新授权您的应用。添加 prompt=consent 后,每当您的应用请求访问权限范围授权时,系统都会显示同意屏幕,即使之前已向 Google API 项目授予了所有范围。因此,请仅在必要时添加 prompt=consent

如需详细了解 prompt 参数,请参阅身份验证 URI 参数表中的 prompt

身份验证 URI 参数

下表更全面地介绍了 Google 的 OAuth 2.0 身份验证 API 接受的参数。

参数 必需 说明
client_id (必填) 您从 API ConsoleCredentials page获取的客户端 ID 字符串,如获取 OAuth 2.0 凭据中所述。
nonce (必填) 您的应用生成的用于启用重放保护的随机值。
response_type (必填) 如果值为 code,则会启动基本授权代码流,需要 POST 到令牌端点才能获取令牌。如果值为 token id_tokenid_token token,则启动隐式流程,要求在重定向 URI 中使用 JavaScript 从 URI #fragment 标识符中检索令牌。
redirect_uri (必填) 确定将响应发送到何处。此参数的值必须与您在 API ConsoleCredentials page 中设置的某个已获授权的重定向值(包括 HTTP 或 HTTPS 架构、大小写和尾随 '/' 等)完全一致。
scope (必填)

范围参数必须以 openid 值开头,然后包含 profile 值和/或 email 值。

如果存在 profile 范围值,则 ID 令牌可能包含用户的默认 profile 声明(但不保证一定会如此)。

如果存在 email 范围值,则 ID 令牌包含 emailemail_verified 声明。

除了这些特定于 OpenID 的范围之外,您的范围参数还可以包含其他范围值。所有范围值都必须用空格分隔。例如,如果您希望按文件访问用户的 Google 云端硬盘,则范围参数可能是 openid profile email https://www.googleapis.com/auth/drive.file

如需了解可用范围,请参阅 Google API 的 OAuth 2.0 范围或您要使用的 Google API 文档。

state (可选,但强烈推荐)

协议中往返的不透明字符串;也就是说,它作为 URI 参数在基本流中和在隐式流中的 URI #fragment 标识符中返回。

state 可用于关联请求和响应。因为您的 redirect_uri 是可以猜到的,所以使用 state 值可以提高确保传入连接是应用发起的身份验证请求的结果。如果您在此 state 变量中生成随机字符串或对某个客户端状态(例如 Cookie)的哈希值进行编码,则可以验证响应以进一步确保该请求和响应来自同一浏览器。这样可以防范跨网站请求伪造等攻击。

access_type (选填) 允许的值为 offlineonline。该效果记录在离线访问中;如果正在请求访问令牌,除非指定的值为 offline,否则客户端不会收到刷新令牌。
display (选填) ASCII 字符串值,用于指定授权服务器如何显示身份验证和用户意见征求界面页面。Google 服务器会指定并接受以下值,但它们的行为不会受到任何影响:pagepopuptouchwap
hd (选填)

简化 Google Cloud 组织拥有的帐号的登录流程。通过添加 Google Cloud 组织网域(例如 mycollege.edu),您可以指明应针对该网域中的帐号优化帐号选择界面。如需通常针对 Google Cloud 组织帐号进行优化,而不仅仅是针对一个 Google Cloud 组织网域进行优化,请设置一个星号 (*) 值:hd=*

请勿依赖此界面优化来控制哪些用户可以访问您的应用,因为客户端请求是可以修改的。请务必验证返回的 ID 令牌的 hd 声明值是否符合您的预期(例如 mycolledge.edu)。与请求参数不同,ID 令牌 hd 声明包含在 Google 的安全令牌中,因此该值可信。

include_granted_scopes (选填) 如果将此参数提供给值 true,且授权请求被授予,则授权将包括之前向此用户/应用组合授予的其他范围的所有授权;请参阅增量授权

请注意,您无法对已安装的应用流程进行额外授权。

login_hint (选填) 当您的应用知道自己正尝试对哪些用户进行身份验证时,它可以将此参数作为提示提供给身份验证服务器。传递此提示会禁止帐号选择器,并且会在登录表单中预先填充电子邮件地址,或者选择正确的会话(如果用户使用多帐号登录),这样可以帮助您避免在应用错误的用户帐号中出现问题。 值可以是电子邮件地址或 sub 字符串,相当于用户的 Google ID。
prompt (选填) 以空格分隔的字符串值列表,用于指定授权服务器是否提示用户重新进行身份验证和表示同意。可能的值包括:
  • none

    授权服务器不会显示任何身份验证屏幕或用户同意屏幕;如果用户尚未通过身份验证且尚未针对请求的范围预配置同意,服务器将返回错误。您可以使用 none 检查现有身份验证和/或用户意见征求。

  • consent

    授权服务器会在将信息返回给客户端之前提示用户同意。

  • select_account

    授权服务器会提示用户选择用户帐号。这样,在授权服务器上拥有多个帐号的用户便可从可能拥有当前会话的多个帐号中选择。

如果未指定任何值,且用户之前未授予访问权限,则系统会向用户显示同意屏幕。

验证 ID 令牌

您需要验证服务器上的所有 ID 令牌,除非您知道它们直接来自 Google。例如,您的服务器必须验证从客户端应用收到的所有 ID 令牌是否真实可信。

在下列情况下,您可能会向服务器发送 ID 令牌:

  • 发送需要进行身份验证的请求的 ID 令牌。ID 令牌会告知您发出请求的特定用户以及该 ID 令牌被授予哪个客户端。

ID 令牌属于敏感信息,如果被拦截,可能会被滥用。您必须确保仅通过 HTTPS 传输这些令牌,并且只能通过 POST 数据或在请求标头内传输这些令牌。如果您将 ID 令牌存储在服务器上,则还必须安全地存储这些令牌。

ID 令牌的用处在于,您可以将它们传递给应用的不同组件。这些组件可以使用 ID 令牌作为轻量级身份验证机制来对应用和用户进行身份验证。不过,您必须先验证 ID 令牌中的信息,或将其用作用户已通过身份验证的断言,然后才能进行使用。

验证 ID 令牌需要执行以下几个步骤:

  1. 验证发卡机构是否对 ID 令牌进行了正确签名。Google 签发的令牌使用在发现文档jwks_uri 元数据值中指定的 URI 处获得的证书进行签名。
  2. 验证 ID 令牌中 iss 声明的值等于 https://accounts.google.comaccounts.google.com
  3. 验证 ID 令牌中 aud 声明的值是否与应用的客户端 ID 相等。
  4. 验证 ID 令牌的过期时间(exp 个声明)未过。
  5. 如果您在请求中指定了 hd 参数值,请验证 ID 令牌是否具有与 Google Cloud 组织关联的已接受网域匹配的 hd 声明。

第 2 步到第 5 步仅涉及非常简单的字符串和日期比较,因此我们在此不作详细说明。

第一步比较复杂,涉及加密签名检查。出于调试目的,您可以使用 Google 的 tokeninfo 端点与在服务器或设备上实现的本地处理进行比较。假设您的 ID 令牌值为 XYZ123。然后,您可以解除对 URI https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 的引用。如果令牌签名有效,则响应为解码后的 JSON 对象形式的 JWT 载荷。

tokeninfo 端点对于调试非常有用,但对于生产环境,请从密钥端点检索 Google 的公钥并在本地执行验证。您应该使用 jwks_uri 元数据值从发现文档中检索密钥 URI。发送到调试端点的请求可能会受到限制或受到间歇性错误的影响。

由于 Google 不会频繁更改公钥,因此您可以使用 HTTP 响应的缓存指令缓存这些公钥,并且在大多数情况下,与使用 tokeninfo 端点相比,更高效地执行本地验证。此验证需要检索和解析证书,并进行适当的加密调用以检查签名。幸运的是,有许多经过调试的库支持多种语言(请参阅 jwt.io)。

获取用户个人资料信息

如需获取有关用户的其他个人资料信息,您可以使用访问令牌(您的应用在身份验证流程期间收到的访问令牌)和 OpenID Connect 标准:

  1. 为了符合 OpenID 标准,您必须在身份验证请求中包含 openid profile 范围值。

    如果您希望包含用户的电子邮件地址,则可以指定额外的范围值 email。 如需同时指定 profileemail,您可以在身份验证请求 URI 中添加以下参数:

    scope=openid%20profile%20email
  2. 将访问令牌添加到授权标头,并向 userinfo 端点发出 HTTPS GET 请求,您应使用 userinfo_endpoint 元数据值从发现文档中检索该请求。userinfo 响应包含用户的相关信息,如 OpenID Connect Standard Claims 和发现文档的 claims_supported 元数据值中所述。用户或其组织可能会选择提供或拒绝某些字段,因此您可能无法获得授权访问权限范围的每个字段的信息。

探索文档

OpenID Connect 协议需要使用多个端点来验证用户身份以及请求资源(包括令牌、用户信息和公钥)。

为简化实现并提高灵活性,OpenID Connect 允许使用位于发现位置的 JSON 文档,该文档包含键值对,提供有关 OpenID Connect 提供商配置的详细信息,包括授权、令牌、撤消、用户信息和公钥端点的 URI。可以从以下位置检索 Google OpenID Connect 服务的发现文档:

https://accounts.google.com/.well-known/openid-configuration

要使用 Google OpenID Connect 服务,您应该将发现文档 URI (https://accounts.google.com/.well-known/openid-configuration) 硬编码到应用中。您的应用提取文档,在响应中应用缓存规则,然后根据需要从该文档检索端点 URI。例如,为了对用户进行身份验证,您的代码会检索 authorization_endpoint 元数据值(以下示例中的 https://accounts.google.com/o/oauth2/v2/auth)作为发送给 Google 的身份验证请求的基本 URI。

以下是此类文档的示例;字段名称为 OpenID Connect Discovery 1.0 中指定的名称(如需了解具体含义,请参阅该文档)。 这些值仅作说明之用,并且可能会有所变化,不过是从实际 Google 探索文档的最新版本复制而来:

{
  "issuer": "https://accounts.google.com",
  "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
  "device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
  "token_endpoint": "https://oauth2.googleapis.com/token",
  "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
  "revocation_endpoint": "https://oauth2.googleapis.com/revoke",
  "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
  "response_types_supported": [
    "code",
    "token",
    "id_token",
    "code token",
    "code id_token",
    "token id_token",
    "code token id_token",
    "none"
  ],
  "subject_types_supported": [
    "public"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "scopes_supported": [
    "openid",
    "email",
    "profile"
  ],
  "token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "client_secret_basic"
  ],
  "claims_supported": [
    "aud",
    "email",
    "email_verified",
    "exp",
    "family_name",
    "given_name",
    "iat",
    "iss",
    "locale",
    "name",
    "picture",
    "sub"
  ],
  "code_challenge_methods_supported": [
    "plain",
    "S256"
  ]
}

您可以通过缓存发现文档中的值来避免 HTTP 往返。使用标准 HTTP 缓存标头,且应遵守。

客户端库

以下客户端库可通过与热门框架集成来简化 OAuth 2.0 的实现:

OpenID Connect 合规性

Google 的 OAuth 2.0 身份验证系统支持 OpenID Connect Core 规范中的必需功能。任何旨在使用 OpenID Connect 的客户端都应与此服务互操作(OpenID 请求对象除外)。