針對伺服器對伺服器應用程式使用 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 呼叫。

將網域層級的權限委派給服務帳戶

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

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

  1. 在 Google Workspace 網域的 管理控制台中,依序前往「主選單」 >「安全性」>「存取權與資料控管」>「API 控制項」
  2. 在「全網域委派」窗格中,選取「管理全網域委派設定」
  3. 點選「新增」
  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 Auth 程式庫,根據服務帳戶的憑證和應用程式需要存取的範圍,建立 GoogleCredentials 物件。例如:

import com.google.auth.oauth2.GoogleCredentials;
import com.google.api.services.sqladmin.SQLAdminScopes;

// ...

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

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

委派全網域授權

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

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

上述程式碼使用 GoogleCredentials 物件呼叫其 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')

在應用程式中使用 Credentials 物件呼叫 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,並要求另一個存取權杖。

伺服器應用程式會使用 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 欄位的存取權杖要求會傳回 錯誤

以下是包含 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 簽名演算法。這項資訊會以 RS256 形式表示在 JWT 標頭的 alg 欄位中。

使用 SHA256withRSA (也稱為 RSASSA-PKCS1-V1_5-SIGN,搭配 SHA-256 雜湊函式) 和從 Google API Console取得的私密金鑰,簽署輸入內容的 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

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

  1. 使用 GoogleCredentials 物件,為要呼叫的 API 建立服務物件。例如:
    SQLAdmin sqladmin =
        new SQLAdmin.Builder(httpTransport, JSON_FACTORY, credentials).build();
  2. 使用服務物件提供的介面,向 API 服務提出要求。舉例來說,如要列出 exciting-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 服務提出要求。舉例來說,如要列出 exciting-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 時)。

您可以在 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 (數字) 授權服務帳戶。 在管理控制台的「全網域委派」頁面中,移除用戶端,然後使用數字 ID 重新新增。
access_denied (任何值) 如果您使用全網域委派,管理控制台未授權一或多個要求的範圍。

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

授權通常會在幾分鐘內生效,但最多可能需要 24 小時,才會對 Google 帳戶中的所有使用者生效。

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

如要進一步瞭解管理員如何限制對所有範圍或機密/受限範圍的存取權,直到明確授予 OAuth 用戶端 ID 存取權為止,請參閱 Google Workspace 管理員說明文章「控管哪些第三方應用程式和內部應用程式可存取 Google Workspace 資料」。

invalid_client (任何值)

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

詳情請參閱錯誤說明。

確認 JWT 權杖有效,且包含正確的聲明。

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

確認 JWT 權杖正確無誤,且是為要求中的用戶端 ID 核發。

deleted_client (任何值)

用來提出要求的 OAuth 用戶端已遭刪除。如果用戶端未使用 ,系統可能會手動或自動刪除。刪除後的 30 天內均可還原用戶端。 瞭解詳情

請使用仍有效的用戶端 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」的服務帳戶,該 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 APIs 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-auth-library-javajava-jwt

import com.google.auth.oauth2.ServiceAccountCredentials;
...
GoogleCredentials credentials =
        GoogleCredentials.fromStream(new FileInputStream("MyProject-1234.json"));
PrivateKey privateKey = ((ServiceAccountCredentials) credentials).getPrivateKey();
String privateKeyId = ((ServiceAccountCredentials) credentials).getPrivateKeyId();

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

導入跨帳戶防護功能

為保護使用者帳戶,您應採取額外步驟,利用 Google 的跨帳戶保護服務實作跨帳戶保護機制。這項服務可讓您訂閱安全性事件通知,向應用程式提供使用者帳戶重大變更的相關資訊。接著,您就能根據決定如何回應活動,採取適當行動。

Google 跨帳戶保護服務傳送給應用程式的事件類型包括:

  • https://schemas.openid.net/secevent/risc/event-type/sessions-revoked
  • https://schemas.openid.net/secevent/oauth/event-type/token-revoked
  • https://schemas.openid.net/secevent/risc/event-type/account-disabled

如要進一步瞭解如何實作跨帳戶保護機制,以及查看可用事件的完整清單,請參閱「 透過跨帳戶保護機制保護使用者帳戶 」頁面。