驗證來自 Google Chat 的要求

針對以 HTTP 端點建構的 Google Chat 應用程式,本節說明如何驗證端點的請求來自 Chat。

為了將互動事件轉送至 Chat 應用程式的端點,Google 會向您的服務提出要求。為了確認要求來自 Google,Chat 會在每個端點的 HTTPS 要求 Authorization 標頭中加入載體權杖。例如:

POST
Host: yourappurl.com
Authorization: Bearer AbCdEf123456
Content-Type: application/json
User-Agent: Google-Dynamite

上述範例中的字串 AbCdEf123456 是載具授權權杖。這是由 Google 產生的加密權杖。權杖權杖的類型和 audience 欄位的值取決於您在設定 Chat 應用程式時選取的驗證對象類型。

如果您已使用 Cloud Functions 或 Cloud Run 實作 Chat 應用程式,Cloud IAM 會自動處理權杖驗證。您只需將 Google Chat 服務帳戶新增為授權叫用者即可。如果應用程式實作自己的 HTTP 伺服器,您可以使用開放原始碼 Google API 用戶端程式庫驗證權杖:

如果權杖未驗證 Chat 應用程式,您的服務應以 HTTPS 回應碼 401 (Unauthorized) 回應要求。

使用 Cloud Functions 或 Cloud Run 驗證要求

如果您使用 Cloud Functions 或 Cloud Run 實作函式邏輯,請務必在 Chat 應用程式的連線設定中,於驗證對象欄位選取「HTTP 端點網址」,並確認設定中的 HTTP 端點網址與 Cloud Functions 或 Cloud Run 端點的網址相符。

接著,您需要將 Google Chat 服務帳戶 chat@system.gserviceaccount.com 授權為叫用者。

以下步驟說明如何使用 Cloud Functions (第 1 代):

控制台

將函式部署至 Google Cloud 後:

  1. 前往 Google Cloud 控制台的「Cloud Functions」頁面:

    前往 Cloud Functions 頁面

  2. 在「Cloud Functions」清單中,按一下接收函式旁的核取方塊。(請勿按一下函式本身)。

  3. 按一下畫面頂端的「權限」,「Permissions」面板會隨即開啟。

  4. 按一下「新增主體」

  5. 在「New principals」(新增主體) 欄位中輸入 chat@system.gserviceaccount.com

  6. 從「請選擇角色」下拉式選單中,依序選取「Cloud Functions」 >「Cloud Functions Invoker」 角色。

  7. 按一下 [儲存]

gcloud

使用 gcloud functions add-iam-policy-binding 指令:

gcloud functions add-iam-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:chat@system.gserviceaccount.com' \
  --role='roles/cloudfunctions.invoker'

RECEIVING_FUNCTION 替換為 Chat 應用程式函式的名稱。

以下步驟說明如何使用 Cloud Functions (第 2 代) 或 Cloud Run 服務:

控制台

將函式或服務部署至 Google Cloud 後:

  1. 前往 Google Cloud 控制台中的 Cloud Run 頁面:

    前往 Cloud Run

  2. 在 Cloud Run 服務清單中,按一下接收函式旁的核取方塊。(請勿按一下函式本身)。

  3. 按一下畫面頂端的「權限」,「Permissions」面板會隨即開啟。

  4. 按一下「新增主體」

  5. 在「New principals」(新增主體) 欄位中輸入 chat@system.gserviceaccount.com

  6. 從「請選擇角色」下拉式選單中,依序選取「Cloud Run」 >「Cloud Run 叫用者」角色。

  7. 按一下 [儲存]

gcloud

使用 gcloud functions add-invoker-policy-binding 指令:

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:chat@system.gserviceaccount.com'

RECEIVING_FUNCTION 替換為 Chat 應用程式函式的名稱。

使用 ID 權杖驗證 HTTP 要求

如果 Chat 應用程式的連線設定中「Authentication Audience」欄位設為 HTTP 端點網址,要求中的憑證授權權杖就是 Google 簽署的 OpenID Connect (OIDC) ID 權杖email 欄位已設為 chat@system.gserviceaccount.com。「Authentication Audience」欄位會設為您設定 Google Chat 傳送要求至 Chat 應用程式的網址。舉例來說,如果 Chat 應用程式的已設定端點為 https://example.com/app/,則 ID 權杖中的「Authentication Audience」欄位會是 https://example.com/app/

如果 HTTP 端點未託管於支援以 IAM 為基礎的驗證服務 (例如 Cloud Functions 或 Cloud Run),建議您採用這種驗證方法。使用這種方法時,HTTP 服務需要執行端點的網址資訊,但不需要 Cloud 專案編號資訊。

以下範例說明如何驗證憑證代管者憑證是由 Google Chat 核發,並且以 Google OAuth 用戶端程式庫為目標應用程式。

Java

java/basic-app/src/main/java/com/google/chat/app/basic/App.java
String CHAT_ISSUER = "chat@system.gserviceaccount.com";
JsonFactory factory = JacksonFactory.getDefaultInstance();

GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), factory)
        .setAudience(Collections.singletonList(AUDIENCE))
        .build();

GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
return idToken != null
    && verifier.verify(idToken)
    && idToken.getPayload().getEmailVerified()
    && idToken.getPayload().getEmail().equals(CHAT_ISSUER);

Python

python/basic-app/main.py
# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

try:
    # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    request = requests.Request()
    token = id_token.verify_oauth2_token(bearer, request, AUDIENCE)
    return token['email'] == CHAT_ISSUER

except:
    return False

Node.js

node/basic-app/index.js
// Bearer Tokens received by apps will always specify this issuer.
const chatIssuer = 'chat@system.gserviceaccount.com';

// Verify valid token, signed by chatIssuer, intended for a third party.
try {
  const ticket = await client.verifyIdToken({
    idToken: bearer,
    audience: audience
  });
  return ticket.getPayload().email_verified
      && ticket.getPayload().email === chatIssuer;
} catch (unused) {
  return false;
}

使用專案編號 JWT 驗證要求

如果 Chat 應用程式的連線設定中「Authentication Audience」欄位設為 Project Number,則要求中的承載授權權杖為自行簽署的 JSON Web Token (JWT),由 chat@system.gserviceaccount.com 簽發及簽署。audience 欄位會設為您用來建構 Chat 應用程式的 Google Cloud 專案編號。舉例來說,如果 Chat 應用程式的 Cloud 專案編號為 1234567890,則 JWT 中的 audience 欄位就是 1234567890

只有在您偏好使用 Cloud 專案編號驗證要求,而非使用 HTTP 端點網址時,才建議採用這種驗證方法。舉例來說,如果您想在保留相同 Cloud 專案編號的情況下變更端點網址,或是想為多個 Cloud 專案編號使用相同的端點,並將 audience 欄位與 Cloud 專案編號清單進行比較。

以下範例說明如何驗證憑證是 Google Chat 核發,並且使用 Google OAuth 用戶端程式庫指定您的專案。

Java

java/basic-app/src/main/java/com/google/chat/app/basic/App.java
String CHAT_ISSUER = "chat@system.gserviceaccount.com";
JsonFactory factory = JacksonFactory.getDefaultInstance();

GooglePublicKeysManager keyManagerBuilder =
    new GooglePublicKeysManager.Builder(new ApacheHttpTransport(), factory)
        .setPublicCertsEncodedUrl(
            "https://www.googleapis.com/service_accounts/v1/metadata/x509/" + CHAT_ISSUER)
        .build();

GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(keyManagerBuilder).setIssuer(CHAT_ISSUER).build();

GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
return idToken != null
    && verifier.verify(idToken)
    && idToken.verifyAudience(Collections.singletonList(AUDIENCE))
    && idToken.verifyIssuer(CHAT_ISSUER);

Python

python/basic-app/main.py
# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

try:
    # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    request = requests.Request()
    certs_url = 'https://www.googleapis.com/service_accounts/v1/metadata/x509/' + CHAT_ISSUER
    token = id_token.verify_token(bearer, request, AUDIENCE, certs_url)
    return token['iss'] == CHAT_ISSUER

except:
    return False

Node.js

node/basic-app/index.js
// Bearer Tokens received by apps will always specify this issuer.
const chatIssuer = 'chat@system.gserviceaccount.com';

// Verify valid token, signed by CHAT_ISSUER, intended for a third party.
try {
  const response = await fetch('https://www.googleapis.com/service_accounts/v1/metadata/x509/' + chatIssuer);
  const certs = await response.json();
  await client.verifySignedJwtWithCertsAsync(
    bearer, certs, audience, [chatIssuer]);
  return true;
} catch (unused) {
  return false;
}