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

總覽

我們在 2022 年 2 月 16 日 宣布計畫,採用更安全的 OAuth 流程,讓 Google OAuth 互動更加安全。本指南會協助您瞭解必要的變更,以及如何從 OAuth 外頻 (OOB) 流程順利遷移至支援的替代方案。

這項防護措施可有效防範網路釣魚和應用程式假冒攻擊,避免您與 Google 的 OAuth 2.0 授權端點互動。

什麼是 OOB?

OAuth 頻外 (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. 如果會受到影響,請遷移至更安全的替代方案。

判斷您是否受到影響

此淘汰項目僅適用於正式版應用程式 (即發布狀態設為「 正式版」的應用程式)。這項流程會繼續適用於具備 測試發布狀態的應用程式。

請前往 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 適用的 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 呼叫 Google 端點,請參閱 Chrome Identity API 指南

網頁應用程式

如果您判斷自己的應用程式使用針對網頁應用程式的 OOB 流程,則應改用 Google API 用戶端程式庫。請參閱這個頁面,瞭解不同程式設計語言的用戶端程式庫。

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

伺服器端 (離線) 存取
伺服器端 (離線) 存取模式需要您執行下列操作:

以下程式碼片段是 NodeJS 範例,示範如何使用 Google Drive API,在不使用 OOB 重新導向 URI 的情況下,在伺服器端列出使用者的 Google 雲端硬碟檔案。

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) 流程