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

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

使用 Google Workspace 帳戶,機構的 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 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("[email protected]");

上述程式碼使用 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('[email protected]')

使用 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 向 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": "[email protected]",
  "scope": "https://www.googleapis.com/auth/prediction",
  "aud": "https://oauth2.googleapis.com/token",
  "exp": 1328554385,
  "iat": 1328550785
}
為 JWT 憑證集編碼

與 JWT 標頭一樣,JWT 憑證附加資訊集應序列化為 UTF-8 和 Base64url-safe 編碼。以下是 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 後,應用程式即可使用該 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 服務提出要求。舉例來說,如要列出 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,然後要求另一個存取權杖。

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 核發。

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 與管理」>「服務帳戶」下方,啟用包含用來簽署斷言的「Key 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": "[email protected]",
      "sub": "[email protected]",
      "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 欄位,請指定 JWT 到期時間的 3600 秒後的時間。

使用服務帳戶 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("[email protected]")
        .withSubject("[email protected]")
        .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': '[email protected]',
           'sub': '[email protected]',
           '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

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