為伺服器對伺服器應用程式採用 OAuth 2.0

詳情請參閱 Google Cloud Platform 說明文件中的驗證總覽

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

通常,應用程式會使用 Google API 來使用自己的資料,而不是處理使用者的資料。舉例來說,使用 Google Cloud Datastore 保存資料的應用程式會使用服務帳戶驗證對 Google Cloud Datastore API 的呼叫。

此外,Google Workspace 網域管理員也可以授予服務帳戶全網域權限,代表網域使用者存取使用者資料。

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

總覽

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

接著,您的應用程式會使用服務帳戶的憑證向 OAuth 2.0 驗證伺服器要求存取憑證,以準備授權的 API 呼叫。

最後,您的應用程式可以使用存取憑證來呼叫 Google API。

正在建立服務帳戶

服務帳戶的憑證包含產生的專屬電子郵件地址,且包含至少一個公開/私密金鑰組。如果啟用了全網域委派功能,用戶端 ID 也是服務帳戶憑證的一部分。

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

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

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

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

  1. 打開 Service accounts page
  2. If prompted, select a project, or create a new one.
  3. 點擊創建服務帳戶
  4. 服務帳戶的詳細信息,鍵入一個名稱,ID和服務帳戶的描述,然後單擊創建並繼續
  5. 可選:在授予此服務帳戶訪問到項目中,選擇IAM角色授予服務帳戶。
  6. 點擊繼續
  7. 可選:在授予用戶訪問該服務帳戶,添加允許使用和管理服務帳戶的用戶或組。
  8. 點擊完成
  9. 點擊創建鍵,然後單擊創建

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

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

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

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

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

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

如果您有 Google Workspace 帳戶,機構管理員可以授權應用程式代表 Google 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 網域 API 和 Google Calendar API 的完整網域存取權,請輸入:https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/calendar
  6. 點選 [Authorize]。

您的應用程式現在有權以網域使用者的身分對 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("user@example.com");

使用 GoogleCredential 物件呼叫應用程式中的 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 標頭

標頭由兩個欄位組成,代表簽署演算法和宣告宣告的格式。這兩個欄位均為必填欄位,且每個欄位都只有一個值。隨著新的演算法和格式上線,這個標頭也會隨之改變。

服務帳戶必須採用 RSA SHA-256 演算法和 JWT 憑證格式。因此,標頭的 JSON 表示法如下所示:

{"alg":"RS256","typ":"JWT"}

以 Base64url 表示:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
建立 JWT 憑證集

JWT 憑證集包含 JWT 的相關資訊,包括要求的權限 (範圍)、憑證的目標、核發單位、核發時間以及憑證的生命週期。大部分欄位都是必填欄位。JWT 憑證集與 JWT 標頭一樣,是 JSON 物件,用於計算簽章。

必要聲明

以下是 JWT 憑證集內要求的宣告。並按版權聲明集中的任意順序顯示。

名稱 說明
iss 服務帳戶的電子郵件地址。
scope 以空格分隔的清單,列出應用程式要求的權限。
aud 宣告斷言目標的描述元。提出存取憑證要求時,這個值一律為 https://oauth2.googleapis.com/token
exp 斷言的到期時間,從世界標準時間 00:00:00 起算,為 1970 年 1 月 1 日。這個值在發出時間的後 1 小時內有效。
iat 發出宣告的時間,從世界標準時間 1070 年 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 欄位的存取憑證要求的回應就會是「錯誤」

以下為包含 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 標頭一樣。以下是 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 表示。

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

接著,該簽名必須採用 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 後,應用程式即可使用該憑證來要求存取憑證。此存取憑證要求為 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 服務傳送要求。例如,若要在激勵-example-123 專案中列出 Cloud SQL 資料庫的執行個體:
    SQLAdmin.Instances.List instances =
        sqladmin.instances().list("exciting-example-123").execute();

Python

使用具有授權的 Credentials 物件以呼叫 Google API,完成下列步驟:

  1. 為您要呼叫的 API 建構服務物件。若要建構服務物件,請呼叫 build 函式,並附上 API 的名稱與版本以及已獲授權的 Credentials 物件。舉例來說,如要呼叫 Cloud SQL Administration API 的 1beta3 版:
    import googleapiclient.discovery
    
    sqladmin = googleapiclient.discovery.build('sqladmin', 'v1beta3', credentials=credentials)
  2. 使用服務物件提供的介面向 API 服務傳送要求。例如,若要在激勵-example-123 專案中列出 Cloud SQL 資料庫的執行個體:
    response = sqladmin.instances().list(project='exciting-example-123').execute()

HTTP/REST

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

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

HTTP GET 範例

使用 Authorization: Bearer HTTP 標頭呼叫 drive.files 端點 (Drive API 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) 進行授權。 在管理控制台的「全網域委派」頁面中,移除用戶端,並以數字 ID 重新加入用戶端。
access_denied (任何值) 如果您使用的是全網域委派,則管理控制台不會授權一或多個要求的範圍。

確認服務帳戶已在管理控制台的「全網域委派」頁面中獲得授權,可在 sub 聲明中 (使用者欄位) 取得授權。此外,服務帳戶也包含您在 JWT 的 scope 憑證中要求的所有範圍。

這項作業通常需要幾分鐘的時間,但授權最多可能需要 24 小時,才會反映在您的 Google 帳戶中。

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 分鐘,或是 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 與管理員」的「服務帳戶」下方,啟用包含「In IDt」的服務帳戶 (用於簽署宣告)。

附錄:服務帳戶沒有 OAuth 的授權

在某些 Google API 中,您可以直接使用已簽署的 JWT 做為不記名憑證 (而非 OAuth 2.0 存取憑證) 進行授權的 API 呼叫。如果可能的話,您可以避免向 Google 授權伺服器發出網路要求,然後再進行 API 呼叫。

如果您要呼叫的 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