驗證來自 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. 按一下畫面頂端的「權限」。「權限」面板隨即開啟。

  4. 按一下「新增主體」

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

  6. 從「Select a role」(請選擇角色) 下拉式選單中,選取「Cloud Functions」 >「Cloud Functions Invoker」(Cloud Functions 叫用者) 角色。

  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. 按一下畫面頂端的「權限」。「權限」面板隨即開啟。

  4. 按一下「新增主體」

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

  6. 從「Select a role」(請選擇角色) 下拉式選單中,依序選取「Cloud Run」和「Cloud Run Invoker」(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 應用程式連線設定的「驗證對象」欄位設為HTTP 端點網址,要求中的不記名授權權杖就是 Google 簽署的 OpenID Connect (OIDC)ID 權杖email 欄位已設為 chat@system.gserviceaccount.com。「驗證目標對象」欄位會設為您設定的網址,Google Chat 會將要求傳送至該網址的 Chat 擴充應用程式。舉例來說,如果 Chat 擴充應用程式的設定端點是 https://example.com/app/,則 ID 權杖中的「驗證目標對象」欄位會是 https://example.com/app/

如果 HTTP 端點並非託管在支援 IAM 驗證的服務 (例如 Cloud Functions 或 Cloud Run),建議使用這種驗證方法。使用這種方法時,HTTP 服務需要執行端點的網址相關資訊,但不需要 Cloud 專案編號相關資訊。

下列範例說明如何使用 Google OAuth 用戶端程式庫,驗證持有人權杖是否由 Google Chat 核發,且是否以您的應用程式為目標。

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 應用程式連線設定的「驗證對象」欄位設為 Project Number,要求中的不記名授權權杖就是由 chat@system.gserviceaccount.com 核發及簽署的自行簽署 JSON Web Token (JWT)audience 欄位會設為您用來建構 Chat 應用程式的 Google Cloud 專案編號。舉例來說,如果 Chat 應用程式的 Cloud 專案編號是 1234567890,則 JWT 中的 audience 欄位就是 1234567890

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

下列範例說明如何使用 Google OAuth 用戶端程式庫,驗證持有人權杖是否由 Google Chat 核發,且以您的專案為目標。

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;
}