針對伺服器對伺服器應用程式使用 OAuth 2.0

Google OAuth 2.0 系統支援伺服器對伺服器的互動,例如網頁應用程式和 Google 服務之間的互動。在這種情況下,您需要一個服務帳戶,該帳戶屬於應用程式,而不是屬於個別使用者的帳戶。應用程式會代表服務帳戶呼叫 Google API,因此使用者並不會直接參與其中。這種情況有時稱為「雙足式 OAuth」(2LO)。(「三足式 OAuth」相關術語是指應用程式代表使用者呼叫 Google API,有時需要取得使用者同意聲明)。

一般來說,應用程式會使用服務帳戶來搭配自己的資料 (而非使用者資料) 使用。舉例來說,使用 Google Cloud Datastore 持續保留資料的應用程式會使用服務帳戶來驗證對 Google Cloud Datastore API 的呼叫。

Google Workspace 網域管理員也可以授予全網域權限,代表網域內使用者存取使用者資料。

本文件會說明應用程式如何使用 Google API 用戶端程式庫 (建議做法) 或 HTTP,完成伺服器對伺服器 OAuth 2.0 流程。

總覽

如要支援伺服器對伺服器的互動,請先在 API Console中為專案建立服務帳戶。如果想存取 Google Workspace 帳戶中使用者的使用者資料,請將整個網域的存取權委派給服務帳戶。

接著,您的應用程式會準備建立經過授權的 API 呼叫,方法是使用服務帳戶的憑證向 OAuth 2.0 驗證伺服器要求存取權杖。

最後,應用程式可以使用存取權杖呼叫 Google API。

建立服務帳戶

服務帳戶憑證包含系統產生的電子郵件地址,且該電子郵件地址不得重複,且至少一個公開/私密金鑰組。如果已啟用全網域委派功能,用戶端 ID 也會成為服務帳戶憑證的一部分。

如果應用程式在 Google App Engine 上執行,系統會在您建立專案時自動設定服務帳戶。

如果應用程式在 Google Compute Engine 上執行,建立專案時也會自動設定服務帳戶,但您必須在建立 Google Compute Engine 執行個體時指定應用程式需要存取的範圍。詳情請參閱「準備執行個體以使用服務帳戶」一文。

如果您的應用程式不是在 Google App Engine 或 Google Compute Engine 上執行,您必須在 Google API Console中取得這些憑證。如要產生服務帳戶憑證,或查看已產生的公開憑證,請執行下列操作:

首先,創建一個服務帳戶:

  1. 打開 Service accounts page
  2. If prompted, select a project, or create a new one.
  3. 單擊創建服務帳戶
  4. Service account details下,鍵入服務帳戶的名稱、ID 和描述,然後點擊Create and continue
  5. 可選:在Grant this service account access to project下,選擇要授予服務帳戶的 IAM 角色。
  6. 單擊繼續
  7. 可選:在Grant users access to this service account下,添加允許使用和管理服務帳戶的用戶或組。
  8. 單擊完成

接下來,創建一個服務帳戶密鑰:

  1. 單擊您創建的服務帳戶的電子郵件地址。
  2. 單擊密鑰選項卡。
  3. 添加密鑰下拉列表中,選擇創建新密鑰
  4. 單擊創建

您的新公鑰/私鑰對已生成並下載到您的機器上;它作為私鑰的唯一副本。您有責任安全地存儲它。如果您丟失了這個密鑰對,您將需要生成一個新的。

您隨時可以返回 API Console 查看電子郵件地址、公開金鑰指紋和其他資訊,或是產生其他公開/私密金鑰組。如要進一步瞭解 API Console中的服務帳戶憑證,請參閱 API Console說明檔案中的服務帳戶

記下服務帳戶的電子郵件地址,並將服務帳戶的私密金鑰檔案儲存在應用程式可存取的位置。您的應用程式需要透過憑證執行已授權的 API 呼叫。

將全網域授權委派給服務帳戶

機構的 Workspace 管理員可以使用 Google Workspace 帳戶,授權應用程式代表 Google Workspace 網域中的使用者存取 Workspace 使用者資料。舉例來說,如果應用程式透過 Google Calendar API,將活動新增至 Google Workspace 網域中所有使用者的日曆,就可以使用服務帳戶代表使用者存取 Google Calendar API。這種授權服務帳戶代表特定網域的使用者存取資料,有時也稱為「委派全網域授權」給服務帳戶。

如要將全網域授權委派給服務帳戶,Google Workspace 網域的超級管理員必須完成下列步驟:

  1. 在 Google Workspace 網域的 管理控制台中,依序前往主選單 > 存取權與資料控管 > API 控制項
  2. 在「全網域委派」窗格中,選取「管理全網域委派作業」
  3. 按一下 [Add new] (新增)
  4. 在「用戶端 ID」欄位中,輸入服務帳戶的用戶端 ID。您可以在 Service accounts page中找到服務帳戶的用戶端 ID。
  5. 在「OAuth 範圍 (逗號分隔)」欄位中,輸入應用程式應有權存取的範圍清單。舉例來說,如果應用程式需要 Google Drive API 和 Google Calendar API 的全網域完整存取權,請輸入:https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/calendar
  6. 按一下「授權」。

您的應用程式現在已有權以您的 Workspace 網域使用者 (「模擬」使用者) 的身分執行 API 呼叫。準備進行這些委派 API 呼叫時,您必須明確指定要模擬的使用者。

準備發出委派 API 呼叫

Java

從 API Console取得用戶端電子郵件地址和私密金鑰之後,請使用 Java 適用的 Google API 用戶端程式庫,透過服務帳戶憑證和應用程式需要存取的範圍建立 GoogleCredential 物件。例如:

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.services.sqladmin.SQLAdminScopes;

// ...

GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json"))
    .createScoped(Collections.singleton(SQLAdminScopes.SQLSERVICE_ADMIN));

如果您在 Google Cloud Platform 開發應用程式,可以改用應用程式預設憑證,簡化相關程序。

委派全網域授權

如果您已委派服務帳戶的全網域存取權,並想模擬使用者帳戶,請使用 GoogleCredential 物件的 createDelegated 方法指定使用者帳戶的電子郵件地址。例如:

GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json"))
    .createScoped(Collections.singleton(SQLAdminScopes.SQLSERVICE_ADMIN))
    .createDelegated("workspace-user@example.com");

上述程式碼使用 GoogleCredential 物件呼叫其 createDelegated() 方法。createDelegated() 方法的引數必須是屬於您 Workspace 帳戶的使用者。發出要求的程式碼將使用這個憑證,使用您的服務帳戶呼叫 Google API。

Python

從 API Console取得用戶端電子郵件地址和私密金鑰之後,請使用 Python 適用的 Google API 用戶端程式庫完成下列步驟:

  1. 透過服務帳戶的憑證和應用程式需要存取的範圍建立 Credentials 物件。例如:
    from google.oauth2 import service_account
    
    SCOPES = ['https://www.googleapis.com/auth/sqlservice.admin']
    SERVICE_ACCOUNT_FILE = '/path/to/service.json'
    
    credentials = service_account.Credentials.from_service_account_file(
            SERVICE_ACCOUNT_FILE, scopes=SCOPES)

    如果您在 Google Cloud Platform 開發應用程式,可以改用應用程式預設憑證,簡化相關程序。

  2. 委派全網域授權

    如果您已委派服務帳戶的全網域存取權,並且想要模擬使用者帳戶,請使用現有 ServiceAccountCredentials 物件的 with_subject 方法。例如:

    delegated_credentials = credentials.with_subject('user@example.org')

使用憑證物件在應用程式中呼叫 Google API。

HTTP/REST

從 API Console取得用戶端 ID 和私密金鑰後,應用程式必須完成下列步驟:

  1. 建立 JSON Web Token (JWT、發音、「jot」),其中包含標頭、憑證附加資訊集和簽名。
  2. 向 Google OAuth 2.0 授權伺服器要求存取權杖。
  3. 處理授權伺服器傳回的 JSON 回應。

以下各節將說明如何完成這些步驟。

如果回應包含存取權杖,您可以使用存取權杖呼叫 Google API。(如果回應不含存取權杖,可能是您的 JWT 和權杖要求格式有誤,或是服務帳戶沒有存取要求範圍的權限)。

存取權杖過期時,應用程式會產生另一個 JWT 並加以簽署,然後要求另一個存取權杖。

您的伺服器應用程式會透過 JWT 向 Google 授權伺服器要求權杖,然後使用該權杖呼叫 Google API 端點。不會涉及使用者。

本節其餘內容將詳細說明如何建立 JWT、簽署 JWT、組成存取權杖要求,以及處理回應。

建立 JWT

JWT 由標頭、憑證附加資訊集和簽名這三個部分組成,標頭和憑證附加資訊集是 JSON 物件。這些 JSON 物件會序列化為 UTF-8 位元組,然後使用 Base64url 編碼進行編碼。這種編碼可讓編碼作業因編碼作業重複而產生彈性。標頭、憑證附加資訊集和簽名會以半形句號 (.) 字元串連在一起。

JWT 的組成元素如下:

{Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}

簽名的基本字串如下:

{Base64url encoded header}.{Base64url encoded claim set}
設定 JWT 標頭

標頭由三個欄位組成,欄位會指出簽署演算法、斷言的格式,以及用於簽署 JWT 的 [服務帳戶金鑰的金鑰 ID](https://cloud.google.com/iam/docs/reference/rest/v1/projects.serviceAccounts.keys)。演算法和格式為必要項目,且每個欄位都只有一個值。隨著其他演算法和格式導入,這個標頭也會跟著變更。金鑰 ID 為選用項目。如果指定的金鑰 ID 不正確,GCP 會嘗試驗證該服務帳戶,並在找不到有效金鑰時拒絕權杖。Google 日後有權拒絕金鑰 ID 不正確的權杖。

服務帳戶依賴 RSA SHA-256 演算法和 JWT 權杖格式。因此,標頭的 JSON 表示法如下:

{"alg":"RS256","typ":"JWT", "kid":"370ab79b4513eb9bad7c9bd16a95cb76b5b2a56a"}

此的 Base64url 表示法如下:

          eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsICJraWQiOiIzNzBhYjc5YjQ1MTNlYjliYWQ3YzliZDE2YTk1Y2I3NmI1YjJhNTZhIn0=
設立 JWT 憑證附加資訊集

JWT 憑證附加資訊集含有 JWT 的相關資訊,包括要求的權限 (範圍)、權杖的目標、核發者、核發權杖的時間,以及權杖的生命週期。大部分欄位都是必填。如同 JWT 標頭,JWT 憑證附加資訊集是 JSON 物件,也會用於計算簽名。

必要的版權聲明

以下是 JWT 憑證附加資訊集中的必要憑證附加資訊。內容可能會以任何順序顯示在版權聲明集中。

名稱 說明
iss 服務帳戶的電子郵件地址。
scope 應用程式要求的權限清單,以空格分隔。
aud 斷言目標的描述元。提出存取權杖要求時,這個值一律為 https://oauth2.googleapis.com/token
exp 斷言的到期時間,從世界標準時間 1970 年 1 月 1 日 00:00:00 開始指定。這個值最長不得超過發出時間的 1 小時。
iat 發出斷言的時間,指定自世界標準時間 1970 年 1 月 1 日 00:00:00 起計算的秒數。

以下是 JWT 憑證附加資訊集內必要欄位的 JSON 表示法:

{
  "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
  "scope": "https://www.googleapis.com/auth/devstorage.read_only",
  "aud": "https://oauth2.googleapis.com/token",
  "exp": 1328554385,
  "iat": 1328550785
}
其他版權聲明

在某些情況下,應用程式可以使用全網域委派功能,代表機構中的特定使用者執行相關動作。您必須先授予執行這類模擬行為的權限,應用程式才能模擬使用者,且通常是由超級管理員處理。詳情請參閱「使用全網域委派功能控管 API 存取權」。

如要取得存取權杖,將應用程式委派存取權給應用程式,請在 JWT 憑證附加資訊 (設為 sub 欄位的值) 中加入使用者的電子郵件地址。

名稱 說明
sub 應用程式要求委派存取權的使用者電子郵件地址。

如果應用程式沒有模擬使用者的權限,則對包含 sub 欄位的存取權杖要求的回應會是 error

包含 sub 欄位的 JWT 憑證附加資訊集範例如下:

{
  "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
  "sub": "some.user@example.com",
  "scope": "https://www.googleapis.com/auth/prediction",
  "aud": "https://oauth2.googleapis.com/token",
  "exp": 1328554385,
  "iat": 1328550785
}
為 JWT 憑證附加資訊集編碼

與 JWT 標頭一樣,JWT 憑證附加資訊集應序列化為 UTF-8 和 Base64url 安全編碼。以下是 JWT 憑證附加資訊集的 JSON 表示法範例:

{
  "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
  "scope": "https://www.googleapis.com/auth/prediction",
  "aud": "https://oauth2.googleapis.com/token",
  "exp": 1328554385,
  "iat": 1328550785
}
計算簽名

JSON Web Signature (JWS) 則是引導 JWT 產生簽名的機制。簽章的輸入內容是下列內容的位元組陣列:

{Base64url encoded header}.{Base64url encoded claim set}

計算簽名時,必須使用 JWT 標頭中的簽署演算法。Google OAuth 2.0 授權伺服器僅支援採用 SHA-256 雜湊演算法的 RSA,在 JWT 標頭的 alg 欄位中,以 RS256 表示。

使用從 Google API Console取得的私密金鑰,使用 SHA256withRSA (也稱為 RSASSA-PKCS1-V1_5-SIGN 的 SHA-256 雜湊函式簽署) 簽署輸入的 UTF-8 表示法。輸出將為位元組陣列。

簽章必須採用 Base64url 編碼。標頭、憑證附加資訊集和簽名會以半形句號 (.) 字元串連在一起。結果會產生 JWT。它應該如下 (新增換行符號,讓畫面清楚易懂):

{Base64url encoded header}.
{Base64url encoded claim set}.
{Base64url encoded signature}

以下是採用 Base64url 編碼之前的 JWT 範例:

{"alg":"RS256","typ":"JWT"}.
{
"iss":"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
"scope":"https://www.googleapis.com/auth/prediction",
"aud":"https://oauth2.googleapis.com/token",
"exp":1328554385,
"iat":1328550785
}.
[signature bytes]

以下是已簽署且可以傳輸的 JWT 範例:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92NC90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ.UFUt59SUM2_AW4cRU8Y0BYVQsNTo4n7AFsNrqOpYiICDu37vVt-tw38UKzjmUKtcRsLLjrR3gFW3dNDMx_pL9DVjgVHDdYirtrCekUHOYoa1CMR66nxep5q5cBQ4y4u2kIgSvChCTc9pmLLNoIem-ruCecAJYgI9Ks7pTnW1gkOKs0x3YpiLpzplVHAkkHztaXiJdtpBcY1OXyo6jTQCa3Lk2Q3va1dPkh_d--GU2M5flgd8xNBPYw4vxyt0mP59XZlHMpztZt0soSgObf7G3GXArreF_6tpbFsS3z2t5zkEiHuWJXpzcYr5zWTRPDEHsejeBSG8EgpLDce2380ROQ

提出存取權杖要求

產生已簽署的 JWT 後,應用程式即可使用該 JWT 要求存取權杖。這個存取權杖要求是 HTTPS POST 要求,主體是網址編碼。網址如下所示:

https://oauth2.googleapis.com/token

HTTPS POST 要求中必須包含下列參數:

名稱 說明
grant_type 視需要使用下列網址編碼編碼的字串:urn:ietf:params:oauth:grant-type:jwt-bearer
assertion JWT,包括簽名。

以下是在存取權杖要求中使用的 HTTPS POST 要求原始傾印:

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU3MzM4MSwiaWF0IjoxMzI4NTY5NzgxfQ.ixOUGehweEVX_UKXv5BbbwVEdcz6AYS-6uQV6fGorGKrHf3LIJnyREw9evE-gs2bmMaQI5_UbabvI4k-mQE4kBqtmSpTzxYBL1TCd7Kv5nTZoUC1CmwmWCFqT9RE6D7XSgPUh_jF1qskLa2w0rxMSjwruNKbysgRNctZPln7cqQ

以下是使用 curl 的相同要求:

curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU3MzM4MSwiaWF0IjoxMzI4NTY5NzgxfQ.RZVpzWygMLuL-n3GwjW1_yhQhrqDacyvaXkuf8HcJl8EtXYjGjMaW5oiM5cgAaIorrqgYlp4DPF_GuncFqg9uDZrx7pMmCZ_yHfxhSCXru3gbXrZvAIicNQZMFxrEEn4REVuq7DjkTMyCMGCY1dpMa8aWfTQFt3Eh7smLchaZsU
' https://oauth2.googleapis.com/token

處理回應

如果 JWT 和存取權杖要求的格式正確無誤,且服務帳戶具備執行作業的權限,則授權伺服器的 JSON 回應會包含存取權杖。以下是回應範例:

{
  "access_token": "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
  "scope": "https://www.googleapis.com/auth/prediction"
  "token_type": "Bearer",
  "expires_in": 3600
}

您可在 expires_in 值指定的持續期間內重複使用存取權杖。

呼叫 Google API

Java

完成下列步驟,使用 GoogleCredential 物件呼叫 Google API:

  1. 為您要使用 GoogleCredential 物件的 API 建立服務物件。例如:
    SQLAdmin sqladmin =
        new SQLAdmin.Builder(httpTransport, JSON_FACTORY, credential).build();
  2. 使用服務物件提供的介面向 API 服務發出要求。舉例來說,如要列出 good-example-123 專案中的 Cloud SQL 資料庫執行個體,請執行下列操作:
    SQLAdmin.Instances.List instances =
        sqladmin.instances().list("exciting-example-123").execute();

Python

完成下列步驟,即可使用已授權的 Credentials 物件呼叫 Google API:

  1. 為您要呼叫的 API 建立服務物件。只要使用 API 名稱和版本以及已授權的 Credentials 物件呼叫 build 函式,即可建構服務物件。例如,如要呼叫 Cloud SQL Administration API 1beta3 版:
    import googleapiclient.discovery
    
    sqladmin = googleapiclient.discovery.build('sqladmin', 'v1beta3', credentials=credentials)
  2. 使用服務物件提供的介面向 API 服務發出要求。舉例來說,如要列出 good-example-123 專案中的 Cloud SQL 資料庫執行個體,請執行下列操作:
    response = sqladmin.instances().list(project='exciting-example-123').execute()

HTTP/REST

應用程式取得存取權杖後,如果已授予 API 所需的存取權範圍,您可以使用權杖代表指定的服務帳戶或使用者帳戶呼叫 Google API。方法是加入 access_token 查詢參數或 Authorization HTTP 標頭 Bearer 值,將存取權杖加入 API 要求中。可以的話,建議您使用 HTTP 標頭,因為查詢字串通常會顯示在伺服器記錄中。在大多數情況下,您可以使用用戶端程式庫設定對 Google API 的呼叫 (例如在呼叫 Drive Files API 時)。

您可以在 OAuth 2.0 Playground 中試用所有 Google API 並查看其範圍。

HTTP GET 範例

使用 Authorization: Bearer HTTP 標頭呼叫 drive.files 端點 (Drive Files API) 時可能如下所示。請注意,您必須指定自己的存取權杖:

GET /drive/v2/files HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer access_token

以下說明如何使用 access_token 查詢字串參數,對已驗證的使用者呼叫同一個 API:

GET https://www.googleapis.com/drive/v2/files?access_token=access_token

curl 範例

您可以使用 curl 指令列應用程式測試這些指令。以下為使用 HTTP 標頭選項的範例 (建議選項):

curl -H "Authorization: Bearer access_token" https://www.googleapis.com/drive/v2/files

或者,您也可以使用查詢字串參數選項:

curl https://www.googleapis.com/drive/v2/files?access_token=access_token

存取權杖失效時

Google OAuth 2.0 授權伺服器核發的存取權杖會在 expires_in 值提供的時間長度過後到期。存取權杖到期後,應用程式應產生其他 JWT 並加以簽署,然後要求另一個存取權杖。

JWT 錯誤代碼

error 欄位 error_description 欄位 意義 如何解決
unauthorized_client Unauthorized client or scope in request. 如果您嘗試使用全網域委派功能,代表服務帳戶未在使用者網域的管理控制台中獲得授權。

確認 sub 憑證附加資訊 (欄位) 已在管理控制台的「 全網域委派」頁面中已授權給使用者。

授權作業通常會在幾分鐘內完成,但最多可能需要 24 小時,才會全面套用至您 Google 帳戶中的所有使用者。

unauthorized_client Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested. 服務帳戶是透過用戶端電子郵件地址 (而非管理控制台中的數字) 進行授權。 前往管理控制台的「 全網域委派」頁面,移除用戶端,然後使用數字 ID 再次新增用戶端。
access_denied (任何值) 如果您使用的是全網域委派功能,表示管理控制台中有一或多個要求的範圍未獲得授權。

確認已在管理控制台的「 全網域委派」頁面中,為 sub 憑證附加資訊 (欄位) 中的使用者獲得授權,並包含您在 JWT 憑證附加資訊的 scope 要求的所有範圍。

授權作業通常會在幾分鐘內完成,但最多可能需要 24 小時,才會全面套用至您 Google 帳戶中的所有使用者。

admin_policy_enforced (任何值) Google 帳戶因 Google Workspace 管理員的政策而無法授權一或多個要求的範圍。

請參閱 Google Workspace 管理員說明文章「控管哪些第三方應用程式和內部應用程式可存取 Google Workspace 資料」,進一步瞭解在未明確授予 OAuth 用戶端 ID 存取權之前,管理員可如何限制所有範圍、敏感和受限制範圍的存取權。

invalid_client (任何值)

OAuth 用戶端或 JWT 權杖無效或設定錯誤。

詳情請參閱錯誤說明。

請確認 JWT 權杖有效且包含正確的憑證附加資訊。

確認 OAuth 用戶端和服務帳戶設定正確無誤,而且使用的電子郵件地址正確無誤。

請檢查 JWT 憑證是否正確,且是否已針對要求中的用戶端 ID 核發該憑證。

invalid_grant Not a valid email. 使用者不存在。 檢查 sub 憑證附加資訊 (欄位) 中的電子郵件地址是否正確。
invalid_grant

Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your 'iat' and 'exp' values and use a clock with skew to account for clock differences between systems.

通常表示本機系統時間有誤。如果 exp 值在未來 65 分鐘與 iat 值相隔超過 65 分鐘,或 exp 值低於 iat 值,也可能會發生此情況。

請確定產生 JWT 的系統時鐘正確無誤。如有需要,請將您的時間與 Google NTP 同步。

invalid_grant Invalid JWT Signature.

用於簽署 JWT 斷言的私密金鑰,不能與用戶端電子郵件指定的服務帳戶相關聯,或是金鑰已遭刪除、停用或過期。

或者,JWT 斷言的編碼可能有誤,其必須採用 Base64 編碼,且不能使用換行符號或相等符號。

將 JWT 憑證附加資訊集解碼,並驗證簽署聲明的金鑰是否已連結至服務帳戶。

嘗試使用 Google 提供的 OAuth 程式庫,確保系統正確產生 JWT。

invalid_scope Invalid OAuth scope or ID token audience provided. 未要求任何範圍 (範圍空白清單),或是要求的任一範圍不存在 (即無效)。

確認已填入 JWT 的 scope 憑證附加資訊 (欄位),並比對這個權杖包含的範圍與您要使用的 API 所記錄範圍,確保沒有錯誤或錯字。

請注意,scope 宣告中的範圍清單必須以空格分隔,而非以半形逗號分隔。

disabled_client The OAuth client was disabled. 用於簽署 JWT 斷言的金鑰已停用。

前往 Google API Console,然後在「IAM 與管理」>「服務帳戶」下方,啟用含有用來簽署宣告的「金鑰 ID」的服務帳戶。

org_internal This client is restricted to users within its organization. 要求中的 OAuth 用戶端 ID 屬於一項專案,用於限制特定 Google Cloud 機構中的 Google 帳戶存取權。

請使用機構的服務帳戶進行驗證。確認 OAuth 應用程式的使用者類型設定

附加條款:沒有 OAuth 的服務帳戶授權

透過部分 Google API,您可以直接將已簽署的 JWT 當做不記名憑證 (而非 OAuth 2.0 存取權杖) 進行授權的 API 呼叫。如果可以的話,您在發出 API 呼叫之前,就不必先向 Google 的授權伺服器發出網路要求。

如果要呼叫的 API 已在 Google API GitHub 存放區中發布服務定義,您可以使用 JWT 而非存取權杖進行授權 API 呼叫。方法如下:

  1. 按照上述步驟建立服務帳戶。請務必保留您建立帳戶時取得的 JSON 檔案。
  2. 使用任何標準 JWT 程式庫 (例如 jwt.io 中的程式庫),建立含有標頭和酬載的 JWT,如下所示:
    {
      "alg": "RS256",
      "typ": "JWT",
      "kid": "abcdef1234567890"
    }
    .
    {
      "iss": "123456-compute@developer.gserviceaccount.com",
      "sub": "123456-compute@developer.gserviceaccount.com",
      "aud": "https://firestore.googleapis.com/",
      "iat": 1511900000,
      "exp": 1511903600
    }
    • 針對標頭中的 kid 欄位,指定服務帳戶的私密金鑰 ID。您可以在服務帳戶 JSON 檔案的 private_key_id 欄位中找到這個值。
    • isssub 欄位中,指定服務帳戶的電子郵件地址。您可以在服務帳戶 JSON 檔案的 client_email 欄位中找到這個值。
    • aud 欄位中,指定 API 端點。例如:https://SERVICE.googleapis.com/
    • iat 欄位中,指定目前的 Unix 時間,並在 exp 欄位中指定 3600 秒後 (即 JWT 到期的時間)。

使用服務帳戶 JSON 檔案中找到的私密金鑰,透過 RSA-256 簽署 JWT。

例如:

Java

使用 google-api-java-clientjava-jwt

GoogleCredential credential =
        GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json"));
PrivateKey privateKey = credential.getServiceAccountPrivateKey();
String privateKeyId = credential.getServiceAccountPrivateKeyId();

long now = System.currentTimeMillis();

try {
    Algorithm algorithm = Algorithm.RSA256(null, privateKey);
    String signedJwt = JWT.create()
        .withKeyId(privateKeyId)
        .withIssuer("123456-compute@developer.gserviceaccount.com")
        .withSubject("123456-compute@developer.gserviceaccount.com")
        .withAudience("https://firestore.googleapis.com/")
        .withIssuedAt(new Date(now))
        .withExpiresAt(new Date(now + 3600 * 1000L))
        .sign(algorithm);
} catch ...

Python

使用 PyJWT

iat = time.time()
exp = iat + 3600
payload = {'iss': '123456-compute@developer.gserviceaccount.com',
           'sub': '123456-compute@developer.gserviceaccount.com',
           'aud': 'https://firestore.googleapis.com/',
           'iat': iat,
           'exp': exp}
additional_headers = {'kid': PRIVATE_KEY_ID_FROM_JSON}
signed_jwt = jwt.encode(payload, PRIVATE_KEY_FROM_JSON, headers=additional_headers,
                       algorithm='RS256')
  1. 使用已簽署的 JWT 做為不記名權杖,來呼叫 API:
    GET /v1/projects/abc/databases/123/indexes HTTP/1.1
    Authorization: Bearer SIGNED_JWT
    Host: firestore.googleapis.com