驗證來自 Google Chat 的要求

對於在 HTTP 端點上建構的 Google Chat 應用程式,本節說明如何 驗證傳送至端點的要求是否來自 Chat。

如何將互動事件分派到 Chat 應用程式的 端點時,Google 會向您的服務發出要求。如何驗證要求 來自 Google,Chat 也提供了 不記名權杖 加到 Authorization 標頭中。例如:

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

上述範例的字串 AbCdEf123456 是不記名授權 產生下一個符記這是 Google 產生的密碼編譯權杖。不記名者的類型 以及 audience 欄位取決於您在下列情況中選取的驗證目標對象類型: 設定 Chat 應用程式

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

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

使用 Cloud Functions 或 Cloud Run 驗證要求

如果函式邏輯是透過 Cloud Functions 或 Cloud Run 實作, 必須在App URL Chat 擴充應用程式 連線設定,確保 設定中的應用程式網址會對應至 Cloud 函式的網址; 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 叫用者,來自 「請選取角色」下拉式選單。

  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 權杖驗證要求

如果 Chat 應用程式的「Authentication Audience」欄位 連線設定設為 App URL。 要求中的不記名授權權杖是 Google 簽署的 OpenID Connect (OIDC) ID 權杖email 欄位已設為 chat@system.gserviceaccount.com。 「audience」欄位會設為你設定 Google Chat 傳送的網址 向 Chat 應用程式提出要求舉例來說, Chat 應用程式設定的端點 https://example.com/app/,則 ID 權杖中的 audience 欄位為 https://example.com/app/

下列範例說明如何驗證不記名權杖是由 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 欄位會設為您使用的 Google Cloud 專案編號 瞭解如何建構 Chat 應用程式舉例來說 Chat 應用程式的 Cloud 專案編號為 1234567890,然後 JWT 中的 audience 欄位為 1234567890

下列範例說明如何驗證不記名權杖是由 使用 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;
}