借助 Google Identity 服务库,用户可以使用基于浏览器的弹出式窗口或重定向用户体验流程向 Google 请求授权代码。这会启动安全的 OAuth 2.0 流程,并生成一个访问令牌,用于代表用户调用 Google API。
OAuth 2.0 授权代码流程摘要:
- Google 帐号所有者在浏览器中通过按钮点击等手势向 Google 请求授权代码。
- Google 会做出响应,会向用户浏览器中运行的 JavaScript Web 应用中的回调函数发送唯一的授权代码,或者使用浏览器重定向直接调用您的授权代码端点。
- 您的后端平台托管授权代码端点并接收该代码。验证完成后,系统会使用向 Google 令牌端点发出的请求来交换每个用户的访问权限和刷新令牌。
- Google 会验证授权代码,确认来自您的安全平台的请求,颁发访问令牌和刷新令牌,并通过调用您的平台托管的登录端点返回令牌。
- 您的登录端点会接收访问令牌和刷新令牌,从而安全地存储刷新令牌以供日后使用。
初始化代码客户端
google.accounts.oauth2.initCodeClient()
方法可初始化代码客户端。
弹出或重定向模式
您可以选择使用重定向或弹出式模式用户流来共享身份验证代码。使用重定向模式时,您会在自己的服务器上托管 OAuth2 授权端点,Google 会将用户代理重定向到此端点,并以网址参数的形式共享身份验证代码。对于弹出模式,您可以定义一个 JavaScript 回调方法来将授权代码发送到您的服务器。弹出式窗口模式可用于提供顺畅的用户体验,而无需访问者离开您的网站。
如需初始化客户端以执行以下操作:
重定向用户体验流程,将
ux_mode
设置为redirect
,并将redirect_uri
的值设置为平台的授权代码端点。该值必须与您在 API 控制台中配置的 OAuth 2.0 客户端的某个已授权重定向 URI 完全匹配。它还必须符合我们的重定向 URI 验证规则。弹出用户体验流程,将
ux_mode
设置为popup
,并将callback
的值设置为将用于向平台发送授权代码的函数的名称。
防止 CSRF 攻击
为帮助防止跨站请求伪造 (CSRF) 攻击,我们为重定向和弹出模式用户体验流程采用了略有不同的技术。对于重定向模式,系统会使用 OAuth 2.0 state 参数。如需详细了解如何生成和验证 state 参数,请参阅 RFC6749 第 10.12 节:跨站请求伪造。在弹出模式下,您可以向请求添加自定义 HTTP 标头,然后在服务器上确认其是否与预期值和来源匹配。
选择一种用户体验模式,以查看显示身份验证代码和 CSRF 处理方式的代码段:
重定向模式
初始化客户端,Google 会将用户的浏览器重定向到您的身份验证端点,并以网址参数的形式共享身份验证代码。
const client = google.accounts.oauth2.initCodeClient({
client_id: 'YOUR_GOOGLE_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
ux_mode: 'redirect',
redirect_uri: "https://your.domain/code_callback_endpoint",
state: "YOUR_BINDING_VALUE"
});
弹出式模式
初始化客户端,用户的浏览器从 Google 接收身份验证代码并将其发送到您的服务器。
const client = google.accounts.oauth2.initCodeClient({
client_id: 'YOUR_GOOGLE_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
ux_mode: 'popup',
callback: (response) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', code_receiver_uri, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Set custom header for CRSF
xhr.setRequestHeader('X-Requested-With', 'XmlHttpRequest');
xhr.onload = function() {
console.log('Auth code response: ' + xhr.responseText);
};
xhr.send('code=' + response.code);
},
});
触发 OAuth 2.0 代码流程
调用代码客户端的 requestCode()
方法以触发用户流:
<button onclick="client.requestCode();">Authorize with Google</button>
这样一来,用户必须先登录 Google 帐号并同意共享各个范围,然后才能将授权代码返回到重定向端点或回调处理程序。
授权代码处理
Google 会为每位用户生成一个唯一的授权代码,您将在后端服务器上接收并验证该代码。
对于弹出模式,在用户浏览器中运行的 callback
指定的处理程序会将授权代码中继到由您的平台托管的端点。
对于重定向模式,系统会将 GET
请求发送到 redirect_url
指定的端点,并共享网址 code 参数中的授权代码。如需接收授权代码,请执行以下操作:
创建一个新的授权端点(如果您尚无实现);或者
更新您的现有端点以接受
GET
请求和网址参数。之前使用的是PUT
请求,且在载荷中包含授权代码值。
授权端点
您的授权代码端点必须处理包含以下网址查询字符串参数的 GET
请求:
名称 | 值 |
---|---|
授权用户 | 请求进行用户登录身份验证 |
验证码 | Google 生成的 OAuth2 授权代码 |
高清 | 用户账号的托管域 |
提示符 | 用户意见征求对话框 |
范围 | 要授权的一个或多个 OAuth2 范围的列表(以空格分隔) |
state | CRSF 状态变量 |
使用网址参数向名为 auth-code 且由 example.com 托管的端点发送的示例 GET
请求:
Request URL: https://www.example.com/auth-code?state=42a7bd822fe32cc56&code=4/0AX4XfWiAvnXLqxlckFUVao8j0zvZUJ06AMgr-n0vSPotHWcn9p-zHCjqwr47KHS_vDvu8w&scope=email%20profile%20https://www.googleapis.com/auth/calendar.readonly%20https://www.googleapis.com/auth/photoslibrary.readonly%20https://www.googleapis.com/auth/contacts.readonly%20openid%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile&authuser=0&hd=example.com&prompt=consent
如果授权代码流程由较早的 JavaScript 库启动,或直接调用 Google OAuth 2.0 端点,则会使用 POST
请求。
POST
请求示例:在 HTTP 请求正文中包含授权代码作为载荷:
Request URL: https://www.example.com/auth-code
Request Payload: 4/0AX4XfWhll-BMV82wi4YwbrSaTPaRpUGpKqJ4zBxQldU\_70cnIdh-GJOBZlyHU3MNcz4qaw
验证请求
在服务器上执行以下操作,帮助避免 CSRF 攻击。
针对重定向模式,检查 state 参数的值。
确认 X-Requested-With: XmlHttpRequest
标头已设置为弹出式模式。
然后,仅当您首先成功验证了身份验证代码请求后,才应继续从 Google 获取刷新令牌和访问令牌。
获取访问令牌和刷新令牌
在您的后端平台收到来自 Google 的授权代码并验证请求后,使用该身份验证代码从 Google 获取访问令牌和刷新令牌,以便进行 API 调用。
按照为 Web 服务器应用使用 OAuth 2.0 指南中的第 5 步:交换刷新令牌和访问令牌的授权代码按照说明执行操作。
管理令牌
您的平台会以安全的方式存储刷新令牌。在移除用户帐号、google.accounts.oauth2.revoke
或直接从 https://myaccount.google.com/permissions 撤消用户意见征求时,删除存储的刷新令牌。
(可选)您可以考虑使用 RISC 使用跨帐号保护功能保护用户帐号。
通常,您的后端平台将使用访问令牌调用 Google API。如果您的 Web 应用还将直接从用户的浏览器调用 Google API,那么您必须实现一种与 Web 应用共享访问令牌的方法,那么这样做不在本指南的讨论范围内。按照此方法操作并使用 JavaScript 版 Google API 客户端库时,请使用 gapi.client.SetToken()
将访问令牌临时存储在浏览器内存中,并使该库能够调用 Google API。