OpenID Connect

Google 的 OpenID Connect 端点已通过 OpenID 认证。

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 的 OAuth 2.0 身份验证系统进行用户登录之前,您必须在 Google Cloud 控制台中设置一个项目,以获取 OAuth 2.0 凭据、设置重定向 URI,并(可选)自定义用户在权限请求页面上看到的品牌信息。您还可以使用 Cloud 控制台创建服务账号、启用结算功能、设置过滤以及执行其他任务。如需了解详情,请参阅 Google Cloud 控制台帮助。

获取 OAuth 2.0 凭证

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

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

或者,在 Cloud 控制台的“客户端”页面中查看您的客户端 ID 和客户端密钥:

  1. 前往“客户”页面。
  2. 点击客户名称或“修改”图标 ()。 您的客户端 ID 和密钥位于页面顶部。

设置重定向 URI

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

如需为给定 OAuth 2.0 凭证创建、查看或修改重定向 URI,请执行以下操作:

  1. 前往“客户”页面。
  2. 点击相应客户。
  3. 查看或修改重定向 URI。

如果“客户端”页面中没有列出的客户端,则表示您的项目没有 OAuth 凭证。如需创建客户端,请点击创建客户端。

自定义用户意见征求界面

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

用户意见征求界面还会显示品牌信息,例如您的产品名称、徽标和首页网址。您可以在 Cloud 控制台中控制品牌信息。

如需启用项目的权限请求页面,请执行以下操作:

  1. 在 Google Cloud 控制台中打开“品牌信息”页面。
  2. 如果系统提示您选择一个项目或创建一个新项目,请视需要选择相应选项。
  3. 填写表单并点击保存。

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

意见征求页面示例
图 1. 意见征求页面屏幕截图

访问服务

Google 和第三方提供的库可用于处理对用户进行身份验证和获取 Google API 访问权限的许多实现细节。例如,Google Identity 服务 和 Google 客户端库,它们适用于各种平台。

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

对用户进行身份验证

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

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

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

服务器流程

请务必在 Cloud 控制台中设置您的应用,使其能够使用这些协议并对用户进行身份验证。当用户尝试使用 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,可从 Cloud 控制台的“客户端”页面获取。
  • response_type,在基本授权代码流程请求中,该值应为 code。(如需了解详情,请访问 response_type。)
  • scope,在基本请求中应为 openid email。(如需了解详情,请访问 scope。)
  • redirect_uri 应是服务器上将接收 Google 响应的 HTTP 端点。该值必须与您在 Cloud 控制台“凭据”页面中配置的 OAuth 2.0 客户端的某个已获授权的重定向 URI 完全一致。如果此值与授权的 URI 不匹配,请求将失败并显示 redirect_uri_mismatch 错误。
  • state 应包含防伪唯一会话令牌的值,以及在用户返回您的应用时恢复上下文所需的任何其他信息,例如起始网址。(如需了解详情,请访问 state。)
  • nonce 是由您的应用生成的随机值,如果存在,则会启用重放保护。
  • login_hint 可以是用户的邮箱或 sub 字符串,相当于用户的 Google ID。如果您未提供 login_hint 且用户已登录,则权限请求页面会包含一项请求,要求用户批准向您的应用提供其电子邮件地址。(如需了解详情,请参阅 login_hint。)
  • 使用 hd 参数为与 Google Workspace 或 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//developers.google.com/oauthplayground&
 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://developers.google.com/oauthplayground?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

根据 RFC 6749,客户端必须忽略无法识别的响应参数。在服务器上,您必须确认从 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 您从 Cloud 控制台客户端页面获取的客户端 ID,如获取 OAuth 2.0 凭据中所述。
client_secret 您从 Cloud 控制台客户端页面获取的客户端密钥,如获取 OAuth 2.0 凭证中所述。
redirect_uri 在 Cloud 控制台“客户端”页面中为给定 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//developers.google.com/oauthplayground&
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 Web Token),即经过加密签名的 Base64 编码 JSON 对象。通常,在使用 ID 令牌之前,验证 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 令牌可能包含以下字段(称为声明):

声明 提供 说明
aud 始终 此 ID 令牌的目标受众群体。它必须是应用的 OAuth 2.0 客户端 ID 之一。
exp 始终 过期时间,在此时间或之后,ID 令牌不得被接受。以 Unix 纪元时间(整数秒)表示。
iat 始终 ID 令牌的颁发时间。以 Unix 纪元时间(整数秒)表示。
iss 始终 响应颁发者的颁发者标识符。对于 Google ID 令牌,始终为 https://accounts.google.com 或 accounts.google.com。
sub 始终 用户的标识符,在所有 Google 账号中都是唯一的,并且绝不会重复使用。一个 Google 账号在不同时间点可以有多个邮箱,但 sub 值永远不会更改。在应用中使用 sub 作为用户的唯一标识符键。长度上限为 255 个区分大小写的 ASCII 字符。
amr 一个 JSON 字符串数组,用于标识登录 Google 账号时所用的身份验证方法。如果存在,则包含以下一个或多个可能的 [IANA.AMR] 值:
  • hwk使用了硬件密钥
  • mfa 已完成多重身份验证
  • pwd使用了密码
  • sms 使用了短信进行验证
  • swk使用了软件密钥(例如通行密钥)
  • tel通过电话进行了验证
仅当身份验证请求中包含 amr 声明并在设置中启用时才会显示。
auth_time 用户身份验证发生的时间,一个 JSON 数字,表示自 Unix 纪元(1970 年 1 月 1 日 00:00:00 UTC)以来经过的秒数。当身份验证请求中包含 auth_time 声明并在设置中启用时提供。
at_hash 访问令牌哈希。验证访问令牌是否与身份令牌相关联。如果 ID 令牌是在服务器流程中通过 access_token 值颁发的,则始终会包含此声明。此声明可用作防范跨站请求伪造攻击的替代机制,但如果您按照第 1 步和第 3 步操作,则无需验证访问令牌。
azp 授权演示者的client_id。只有当请求 ID 令牌的一方与 ID 令牌的受众群体不同时,才需要此声明。在 Google,对于 Web 应用和 Android 应用具有不同的 OAuth 2.0 client_id 但共享同一 Google API 项目的混合应用,可能会出现这种情况。
email 用户的电子邮件地址。仅当您在请求中添加了 email 范围时,系统才会提供此字段。此声明的值可能并非此账号所独有,并且可能会随时间而变化,因此您不应将此值用作关联到用户记录的主要标识符。您也无法依赖 email 声明的网域来识别 Google Workspace 或 Cloud 组织的用户;请改用 hd 声明。
email_verified 如果用户的邮箱已通过验证,则为 True;否则为 False。
family_name 用户的姓氏。当存在 name 声明时,可能会提供此声明。
given_name 用户的名字。当存在 name 声明时,可能会提供此声明。
hd 与用户的 Google Workspace 或 Cloud 组织关联的网域。 仅在用户属于 Google Cloud 组织时提供。在将资源的访问权限限制为仅限特定网域的成员时,您必须检查此声明。缺少此声明表示该账号不属于 Google 托管的网域。
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 (必填) 您从 Cloud 控制台“客户端”页面获取的客户端 ID 字符串,如获取 OAuth 2.0 凭据中所述。
nonce (必填) 由您的应用生成的随机值,用于启用重放保护。
response_type (必填) 如果值为 code,则启动基本授权代码流程,需要向令牌端点发送 POST 才能获取令牌。如果值为 token id_token 或 id_token token,则会启动隐式流程,这需要在重定向 URI 处使用 JavaScript 从 URI #fragment 标识符中检索令牌。
redirect_uri (必填) 确定响应的发送位置。此参数的值必须与您在 Cloud 控制台“客户端”页面中设置的某个授权重定向值完全匹配(包括 HTTP 或 HTTPS 架构、大小写和末尾的“/”,如有)。
scope (必填)

scope 参数必须以 openid 值开头,然后包含 profile 值、email 值或同时包含这两个值。

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

如果存在 email 范围值,则 ID 令牌包含 email 和 email_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 (可选) 允许的值包括 offline 和 online。离线访问中介绍了其效果;如果请求访问令牌,则除非指定 offline 值,否则客户端不会收到刷新令牌。
claims (可选) claims 参数用于指定要包含在 userinfo 端点或身份验证请求 ID 令牌响应类型中的一个或多个可选字段。该值是一个 JSON 对象,其中包含响应类型和请求的声明。Google 服务器接受以下声明请求:
申领请求
amr 请求用户上次通过身份验证时使用的方法。如需在 ID 令牌响应中将 amr 作为字段返回,请添加 claims 请求参数:claims={"id_token":{"amr":{"essential":true}}} 必须在设置中启用。
auth_time 请求用户上次通过身份验证的时间。如需在 ID 令牌响应中将 auth_time 作为字段返回,请添加 claims 请求参数:claims={"id_token":{"auth_time":{"essential":true}}} 必须在设置中启用。
display (可选) 一个 ASCII 字符串值,用于指定授权服务器如何显示身份验证和意见征求界面页面。以下值已指定并被 Google 服务器接受,但对协议流行为没有任何影响:page、popup、touch 和 wap。
hd (可选)

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

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

include_granted_scopes (可选) 如果此参数的值为 true,并且授权请求获得批准,则授权将包含之前为此用户/应用组合授予的其他范围的任何授权;请参阅 增量授权。

请注意,您无法通过安装式应用流程进行增量授权。

login_hint (可选) 当应用知道要对哪个用户进行身份验证时,可以将此参数作为提示提供给认证服务器。传递此提示会抑制账号选择器,并预填充登录表单上的邮箱,或选择正确的会话(如果用户使用多账号登录),这有助于您避免因应用登录错误的用户账号而出现问题。 该值可以是电子邮件地址,也可以是 sub 字符串(相当于用户的 Google ID)。
prompt (可选) 一个以空格分隔的字符串值列表,用于指定授权服务器是否提示用户重新进行身份验证和同意。可能的值包括:
  • none

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

  • consent

    授权服务器会先提示用户同意,然后再将信息返回给客户端。

  • select_account

    授权服务器会提示用户选择用户账号。这样,在授权服务器上拥有多个账号的用户就可以从其当前可能拥有会话的多个账号中进行选择。

如果未指定任何值,并且用户之前未授权访问,则系统会向用户显示权限请求页面。

hl (可选) BCP 47 语言标记,用于指定登录、账号选择器和权限请求页面的显示语言。如果未提供此参数,则语言默认为用户的 Google 账号或浏览器设置。例如,如需请求英式英语界面,请将该参数设置为 en-GB。

验证 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.com 或 accounts.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。如需同时指定 profile 和 email,您可以在身份验证请求 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 请求对象除外)。