架構外 (OOB) 流程遷移指南

總覽

我們在 2022 年 2 月 16 日 宣布,將採用更安全的 OAuth 流程,讓 Google OAuth 互動更安全。這份指南可協助您瞭解從 OAuth 額外流程 (OOB) 成功遷移至支援的替代方案所需的變更和步驟。

這項措施是防範網路釣魚和應用程式冒用攻擊的防護措施,可在與 Google OAuth 2.0 授權端點互動時發揮作用。

什麼是 OOB?

OAuth out-of-band (OOB) (也稱為手動複製/貼上選項) 是一種舊版流程,可支援原生用戶端,這些用戶端在使用者核准 OAuth 同意要求後,沒有重新導向 URI 來接受憑證。OOB 流程會造成遠端網路釣魚風險,因此用戶端必須改用其他方法來防範這項安全漏洞。

所有用戶端類型 (例如網頁應用程式、Android、iOS、通用 Windows 平台 (UWP)、Chrome 應用程式、電視和輸入功能受限的裝置、電腦應用程式) 的 OOB 流程都已淘汰。

重要法規遵循日期

  • 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. 如果受到影響,請改用更安全的替代方案。

判斷是否受到影響

這項淘汰作業僅適用於正式版應用程式 (即發布狀態設為「正式版」的應用程式)。對於具有 測試發布狀態的應用程式,這項流程仍會繼續運作。

請查看 OAuth 的 發布狀態,如果您在「正式版」發布狀態的專案中使用 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)

如果您判斷應用程式使用 OOB 流程,且採用 Android 或 iOS OAuth 用戶端類型,則應改用建議的 SDK (AndroidiOS)。

這個 SDK 可讓您輕鬆存取 Google API,並處理所有對 Google OAuth 2.0 授權端點的呼叫。

下方的說明文件連結提供資訊,說明如何使用建議的 SDK 存取 Google API,而不需要使用 OOB 重新導向 URI。

在 Android 上存取 Google API

用戶端存取

以下範例說明如何使用建議的 Google Identity 服務 Android 程式庫,在 Android 用戶端存取 Google API

  List requestedScopes = Arrays.asList(DriveScopes.DRIVE_APPDATA);
    AuthorizationRequest authorizationRequest = AuthorizationRequest.builder().setRequestedScopes(requestedScopes).build();
    Identity.getAuthorizationClient(activity)
            .authorize(authorizationRequest)
            .addOnSuccessListener(
                authorizationResult -> {
                  if (authorizationResult.hasResolution()) {
                    // Access needs to be granted by the user
                    PendingIntent pendingIntent = authorizationResult.getPendingIntent();
                    try {
    startIntentSenderForResult(pendingIntent.getIntentSender(),
    REQUEST_AUTHORIZE, null, 0, 0, 0, null);
                    } catch (IntentSender.SendIntentException e) {
                    Log.e(TAG, "Couldn't start Authorization UI: " + e.getLocalizedMessage());
                    }
                  } else {
                    // Access already granted, continue with user action
                    saveToDriveAppFolder(authorizationResult);
                  }
                })
            .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));

authorizationResult 傳遞至您定義的方法,即可將內容儲存至使用者的雲端硬碟資料夾。authorizationResult 具有會傳回存取權杖的 getAccessToken() 方法。

伺服器端 (離線) 存取
以下範例說明如何在 Android 的伺服器端存取 Google API。
  List requestedScopes = Arrays.asList(DriveScopes.DRIVE_APPDATA);
    AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .requestOfflineAccess(webClientId)
            .setRequestedScopes(requestedScopes)
            .build();
    Identity.getAuthorizationClient(activity)
            .authorize(authorizationRequest)
            .addOnSuccessListener(
                authorizationResult -> {
                  if (authorizationResult.hasResolution()) {
                    // Access needs to be granted by the user
                    PendingIntent pendingIntent = authorizationResult.getPendingIntent();
                    try {
    startIntentSenderForResult(pendingIntent.getIntentSender(),
    REQUEST_AUTHORIZE, null, 0, 0, 0, null);
                    } catch (IntentSender.SendIntentException e) {
                    Log.e(TAG, "Couldn't start Authorization UI: " + e.getLocalizedMessage());
                    }
                  } else {
                    String authCode = authorizationResult.getServerAuthCode();
                  }
                })
            .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));

authorizationResult 具有 getServerAuthCode() 方法,可傳回授權碼,您可以將該授權碼傳送至後端,以取得存取權和更新權杖。

在 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) 搭配 Objective-C 適用的 REST 用途專用 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 端點。

網頁應用程式

如果您判斷應用程式是使用網路應用程式的 OOB 流程,應改用 Google API 用戶端程式庫。如要查看不同程式設計語言的用戶端程式庫,請參閱這裡

這些程式庫可讓您輕鬆存取 Google API,並處理所有對 Google 端點的呼叫。

伺服器端 (離線) 存取權
伺服器端 (離線) 存取模式需要您執行以下操作:
  • 啟用伺服器,並定義可供公開存取的端點 (重新導向 URI),以便接收授權碼。
  • 在 的 中設定 重新導向 URI

以下程式碼片段顯示 NodeJS 範例,說明如何使用 Google 雲端硬碟 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.
      });
    }
  }
}

請參閱 伺服器端網頁應用程式指南,瞭解如何從伺服器端存取 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(...);
}

請參閱 用戶端網頁應用程式指南,瞭解如何從用戶端存取 Google API。

電腦版用戶端

如果您判斷應用程式在電腦版用戶端上使用 OOB 流程,應改用 迴送 IP 位址 (localhost127.0.0.1) 流程