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

如何

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

總覽

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

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

最後,您的應用程式可以使用存取權杖呼叫 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 呼叫才能進行授權 API 呼叫。

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

使用 Google Workspace 帳戶時,機構的 Workspace 管理員可以授權應用程式代表 Google Workspace 網域中的使用者存取 Workspace 使用者資料。舉例來說,使用 Google Calendar API 在 Google Workspace 網域中所有使用者的日曆中加入事件時,應用程式會透過服務帳戶代表使用者存取 Google Calendar API。授權服務帳戶代表網域使用者存取資料的行為,有時也稱為「委派全網域授權」至服務帳戶。

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

  1. 從 Google Workspace 網域的 管理控制台前往主選單,>「安全性」>「資料控管」>「API 控制項」
  2. 在 [全網域委派] 窗格中,選取 [管理全網域委派設定]
  3. 按一下 [Add new] (新增)
  4. 在「Client ID」(用戶端 ID) 欄位中,輸入服務帳戶的「Client 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 呼叫,在您的 Workspace 網域中「模擬」使用者。準備進行這些委派 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 的 [服務帳戶金鑰金鑰](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 斷言的到期時間,以世界標準時間 107 年 1 月 1 日 00:00:00 起計算。這個值的核發時間上限為核發後的 1 小時。
iat 宣告發出的時間,以世界標準時間 107 年 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 憑證附加資訊集的 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 雜湊函式) 取得的私密金鑰 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-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 服務傳送要求。舉例來說,如要列出您可能需要名為「example-example-123」專案中的 Cloud SQL 資料庫執行個體:
    response = sqladmin.instances().list(project='exciting-example-123').execute()

HTTP/REST

應用程式取得存取權杖後,您可以使用該權杖代表指定服務帳戶或 Google API 的呼叫權限(如果已授予 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 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 (數字)。 在管理控制台的 全網域委派頁面中移除用戶端,然後使用數字 ID 重新新增用戶端。
access_denied (任何值) 如果您使用全網域委派,代表管理控制台並未授權一或多個要求的範圍。

請前往管理控制台的 sub 版權聲明 (欄位) 中,在管理控制台的 全網域委派頁面中,確認服務帳戶已取得授權,並在 JWT 的 scope 憑證附加要求中納入您要求的所有範圍。

這項作業通常會在幾分鐘內完成,但系統最多可能需要 24 小時才能為 Google 帳戶的所有使用者生效。

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

請參閱 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 值未來比 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 做為不記名權杖,來發出授權的 API 呼叫,而不是使用 OAuth 2.0 存取權杖。如果可以的話,您就不需要在發出 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