带外 (OOB) 流程迁移指南

概览

2022 年 2 月 16 日,我们 宣布了如下计划:使用更安全的 OAuth 流程,让 Google OAuth 交互更安全。本指南可帮助您了解必要的更改和步骤,以便成功从 OAuth 带外 (OOB) 流程迁移到受支持的替代方案。

这是一项防范措施,可在用户与 Google 的 OAuth 2.0 授权端点互动期间防范钓鱼式攻击和应用假冒攻击。

什么是 OOB?

OAuth 带外 (OOB)(也称为手动复制/粘贴选项)是一种旧版流程,旨在支持没有重定向 URI 的原生客户端,以便在用户批准 OAuth 同意请求后接受凭据。OOB 流程存在远程钓鱼式攻击风险,客户端必须迁移到替代方法以防范此漏洞。

我们将针对所有客户端类型弃用 OOB 流程,即 Web 应用、Android、iOS、通用 Windows 平台 (UWP)、Chrome 应用、电视和受限输入设备、桌面应用。

重要合规日期

  • 2022 年 2 月 28 日 - OOB 流程禁止使用新的 OAuth
  • 2022 年 9 月 5 日 - 系统可能会向不合规的 OAuth 请求显示一条面向用户的警告消息
  • 2022 年 10 月 3 日 - 对于 2022 年 2 月 28 日之前创建的 OAuth 客户端,OOB 流程已弃用
  • 2023 年 1 月 31 日 - 所有现有客户端(包括豁免的客户端)都会被屏蔽

对于不合规的请求,系统会显示一条面向用户的错误消息。此消息将向用户说明应用已被屏蔽,同时显示您已在 Google API 控制台的 OAuth 同意屏幕中注册的支持电子邮件地址。

完成迁移过程有两个主要步骤:
  1. 确定您是否受到影响。
  2. 如果您受到影响,请迁移到更安全的替代方案。

确定您是否受到影响

此弃用仅适用于正式版应用(即发布状态设置为 正式版的应用)。此流程将继续适用于处于 测试发布状态的应用。

请在 Google API Console 的 OAuth Consent Screen page中查看发布状态。如果您在发布状态为“正式版”的项目中使用 OOB 流程,请继续下一步操作。

如何确定您的应用是否使用了 OOB 流程

检查您的应用代码去电网络调用(如果您的应用使用 OAuth 库),确定您的应用发出的 Google OAuth 授权请求是否使用了 OOB 重定向 URI 值。

检查您的应用代码

查看正在调用 Google OAuth 授权端点的应用代码部分,并确定 redirect_uri 参数是否具有以下任何值:
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto
  • redirect_uri=oob
OOB 重定向流程请求示例如下所示:
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
scope=<SCOPES>&
state=<STATE>&
redirect_uri=urn:ietf:wg:oauth:2.0:oob&
client_id=<CLIENT_ID>

检查去电网络通话

检查网络调用的方法因应用客户端类型而异。
检查网络调用时,请查找发送到 Google OAuth 授权端点的请求,并确定 redirect_uri 参数是否具有以下任何值:
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto
  • redirect_uri=oob
OOB 重定向流程请求示例将如下所示:
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
scope=<SCOPES>&
state=<STATE>&
redirect_uri=urn:ietf:wg:oauth:2.0:oob&
client_id=<CLIENT_ID>

迁移到安全的替代方案

移动客户端 (Android / iOS)

如果您确定应用使用的是适用于 Android 或 iOS OAuth 客户端类型的 OOB 流程,您应改用我们的 Google 登录移动 SDK(AndroidiOS)。

借助该 SDK,您可以轻松访问 Google API,并处理对 Google 的 OAuth 2.0 授权端点的所有调用。

以下文档链接介绍了如何使用 Google 登录 SDK 在不使用 OOB 重定向 URI 的情况下访问 Google API。

在 Android 上访问 Google API

服务器端(离线)访问
以下示例展示了如何在 Android 的服务器端访问 Google API
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {
  GoogleSignInAccount account = task.getResult(ApiException.class);
  
  // request a one-time authorization code that your server exchanges for an
  // access token and sometimes refresh token
  String authCode = account.getServerAuthCode();
  
  // Show signed-in UI
  updateUI(account);

  // TODO(developer): send code to server and exchange for access/refresh/ID tokens
} catch (ApiException e) {
  Log.w(TAG, "Sign-in failed", e);
  updateUI(null);
}

请查看服务器端访问指南,了解如何从服务器端访问 Google API。

在 iOS 应用中访问 Google API

客户端访问

下例显示了如何在 iOS 客户端上访问 Google API

user.authentication.do { authentication, error in
  guard error == nil else { return }
  guard let authentication = authentication else { return }
  
  // Get the access token to attach it to a REST or gRPC request.
  let accessToken = authentication.accessToken
  
  // Or, get an object that conforms to GTMFetcherAuthorizationProtocol for
  // use with GTMAppAuth and the Google APIs client library.
  let authorizer = authentication.fetcherAuthorizer()
}

使用访问令牌调用 API,方法是在 REST 或 gRPC 请求的标头中添加访问令牌 (Authorization: Bearer ACCESS_TOKEN),或者将提取程序授权程序 (GTMFetcherAuthorizationProtocol) 与 适用于 REST 的 Objective-C 版 Google API 客户端库结合使用。

请参阅客户端访问指南,了解如何在客户端访问 Google API。 了解如何在客户端访问 Google API。

服务器端(离线)访问
以下示例展示了如何在服务器端访问 Google API 以支持 iOS 客户端。
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
  guard error == nil else { return }
  guard let user = user else { return }
  
  // request a one-time authorization code that your server exchanges for
  // an access token and refresh token
  let authCode = user.serverAuthCode
}

如需了解如何从服务器端访问 Google API,请查看服务器端访问指南

Chrome 应用客户端

如果您确定自己的应用在 Chrome 应用客户端上使用 OOB 流程,则应改用 Chrome Identity API

以下示例展示了如何在不使用 OOB 重定向 URI 的情况下获取所有用户联系人。

window.onload = function() {
  document.querySelector('button').addEventListener('click', function() {

  
  // retrieve access token
  chrome.identity.getAuthToken({interactive: true}, function(token) {
  
  // ..........


  // the example below shows how to use a retrieved access token with an appropriate scope
  // to call the Google People API contactGroups.get endpoint

  fetch(
    'https://people.googleapis.com/v1/contactGroups/all?maxMembers=20&key=API_KEY',
    init)
    .then((response) => response.json())
    .then(function(data) {
      console.log(data)
    });
   });
 });
};

请参阅 Chrome Identity API 指南,详细了解如何使用 Chrome Identity API 访问用户身份和调用 Google 端点。

Web 应用

如果您确定自己的应用对 Web 应用使用的是 OOB 流程,则应改用我们的某个 Google API 客户端库。此处列出了适用于不同编程语言的客户端库。

借助这些库,您可以轻松访问 Google API 并处理对 Google 端点的所有调用。

服务器端(离线)访问
服务器端(离线)访问模式要求您执行以下操作:
  • 建立一台服务器,并定义一个可公开访问的端点(重定向 URI)来接收授权代码。
  • 在 Google API Console的 Credentials page 中配置 重定向 URI

以下代码段展示了一个 NodeJS 示例,该示例使用 Google Drive API 在服务器端列出用户的 Google 云端硬盘文件,而不使用 OOB 重定向 URI。

async function main() {
  const server = http.createServer(async function (req, res) {

  if (req.url.startsWith('/oauth2callback')) {
    let q = url.parse(req.url, true).query;

    if (q.error) {
      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);

      // 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) => {
        // TODO(developer): Handle response / error.
      });
    }
  }
}

查看 服务器端 Web 应用指南,了解如何从服务器端访问 Google API。

客户端访问

以下 JavaScript 代码段显示了使用 Google API 在客户端访问用户的日历活动的示例。


// initTokenClient() initializes a new token client with your
// web app's client ID and the scope you need access to

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  
  // callback function to handle the token response
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) { 
      gapi.client.setApiKey('YOUR_API_KEY');
      gapi.client.load('calendar', 'v3', listUpcomingEvents);
    }
  },
});

function listUpcomingEvents() {
  gapi.client.calendar.events.list(...);
}

查看 客户端 Web 应用指南,了解如何从客户端访问 Google API。

桌面客户端

如果您确定应用在桌面客户端上使用 OOB 流程,则应改用 环回 IP 地址(localhost127.0.0.1)流程