驗證來自 Google Chat 的要求

如果您是使用 HTTP 端點建構 Google Chat 應用程式,本節將說明如何驗證傳送至端點的要求是否來自 Chat。

如要將互動事件傳送至 Chat 擴充應用程式的端點,Google 會向您的服務發出 HTTPS 要求。為驗證要求是否由 Google 發出,Chat 會在每個 HTTPS 要求的 Authorization 標頭中,加入 Google 簽署的 OpenID Connect (OIDC) ID 權杖做為不記名權杖 (以及要求內文的 authorizationEventObject.systemIdToken 欄位)。例如:

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

上述範例中的字串 AbCdEf123456 是持有者授權權杖。這個加密權杖是由 Chat 應用程式的專案專屬服務帳戶 (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com) 簽署,且 audience 欄位會設為設定 Chat 應用程式時,為 Chat 應用程式設定的 HTTP 端點網址。

您可以前往 Google Cloud 控制台,並在 Chat API「設定」分頁的「連線設定」部分,複製 Chat 應用程式的服務帳戶電子郵件地址:

  1. 前往 Google Cloud 控制台,依序點選「選單」 >「API 和服務」 >「已啟用的 API 和服務」 >「Google Chat API」 >「設定」:

    前往 Google Chat API 設定

  2. 依序前往「互動功能」>「連線設定」,複製「服務帳戶電子郵件」 (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com)。

如果您使用 Cloud Run 函式或 Cloud Run 實作 Chat 應用程式,當您將 Cloud Run Invoker (roles/run.invoker) 角色授予 Chat 應用程式的服務帳戶時,Cloud IAM 會自動處理權杖驗證。如果應用程式實作了自己的 HTTP 伺服器,可以使用開放原始碼的 Google API 用戶端程式庫驗證不記名權杖:

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

使用 Cloud Run 函式驗證要求

如果函式邏輯是使用 Cloud Run functions 或 Cloud Run 實作,請確認 Chat 應用程式連線設定中「觸發條件」下方設定的 HTTP 端點網址,與 Cloud Run 函式端點的網址相符。

接著,請按照下列步驟,授權 Chat 應用程式的服務帳戶 (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com,從 Chat API「設定」分頁的「連線設定」部分複製) 做為呼叫者:

控制台

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

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

    前往 Cloud Run。

  2. 在 Cloud Run 服務清單中,勾選目標函式旁邊的核取方塊。(請勿點選函式本身)。

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

  4. 按一下「Add principal」(新增主體)。

  5. 在「新增主體」欄位中,輸入 Chat 應用程式的服務帳戶電子郵件地址 (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.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:service-PROJECT_NUMBER@gcp-sa-gsuiteaddons.iam.gserviceaccount.com'

更改下列內容:

  • RECEIVING_FUNCTION:Chat 應用程式函式的名稱。
  • PROJECT_NUMBER:Chat 應用程式服務帳戶電子郵件地址中的專案編號。

使用 ID 權杖驗證 HTTP 要求

如果是 HTTP 端點,要求中的不記名授權權杖是 Google 簽署的 OpenID Connect (OIDC) ID 權杖。email 欄位會設為 Chat 應用程式的服務帳戶電子郵件地址 (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com),audience 欄位則會設為已設定接收要求的 HTTP 端點網址。舉例來說,如果您的 Chat 應用程式設定的端點是 https://example.com/app/,則 ID 權杖中的 audience 欄位就是 https://example.com/app/。

如果 HTTP 端點並非託管在支援 IAM 驗證方式的服務 (例如 Cloud Run),建議使用這種驗證方式。

下列範例說明如何使用 Google OAuth 用戶端程式庫,驗證不記名權杖是否由 Google 發給您的 Chat 應用程式,且是否以您應用程式的端點為目標:

Java

java/chat/secured-app/src/main/java/com/google/chat/app/secured/App.java
/**
 * Determine whether a Google Workspace add-on request is legitimate.
 * 
 * @param event Event sent from Google Workspace add-on
 * @param authorization Authorization header from the request
 * @return {boolean} Whether the request is legitimate
 */
private boolean verifyAddOnRequest(JsonNode event, String authorization) throws Exception {
  JsonFactory factory = JacksonFactory.getDefaultInstance();

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

  String bearer = authorization.substring("Bearer ".length(), authorization.length());
  GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
  return idToken != null
    && verifier.verify(idToken)
    && idToken.getPayload().getEmailVerified()
    && idToken.getPayload().getEmail().equals(SERVICE_ACCOUNT_EMAIL);
}

Python

python/chat/secured-app/main.py
def verifyAddOnRequest() -> bool:
  """Determine whether a Google Workspace add-on request is legitimate.

  Args:
    request: Request sent from Google Workspace add-on

  Returns:
    Whether the request is legitimate
  """
  try:
    bearer = request.headers.get('Authorization')[len("Bearer "):]
    token = id_token.verify_oauth2_token(bearer, requests.Request(), HTTP_ENDPOINT)
    return token['email'] == SERVICE_ACCOUNT_EMAIL

  except:
    return False

Node.js

node/chat/secured-app/index.js
/**
 * Determine whether a Google Workspace add-on request is legitimate.
 * 
 * @param {Object} req Request sent from Google Workspace add-on
 * @return {boolean} Whether the request is legitimate
 */
async function verifyAddOnRequest(req) {
  try {
    const authorization = req.headers.authorization;
    const idToken = authorization.substring('Bearer '.length, authorization.length);
    const ticket = await new OAuth2Client().verifyIdToken({idToken, audience: HTTP_ENDPOINT});
    return ticket.getPayload().email_verified
        && ticket.getPayload().email === SERVICE_ACCOUNT_EMAIL;
  } catch (unused) {
    return false;
  }
}

非外掛程式的 Chat 擴充應用程式:驗證 Google Chat 的要求

下列說明文件適用於非 Google Workspace 外掛程式的 Chat 擴充應用程式。如要遷移非外掛程式的 Chat 應用程式,請參閱「將 Google Chat 應用程式轉換為 Google Workspace 外掛程式」。

如果 Chat 應用程式不是外掛程式,且已在「連線設定」下方設定 HTTP 端點網址,則不記名權杖的類型和 audience 欄位的值,取決於設定 Chat 應用程式時選取的「驗證目標對象」類型,且要求會由共用服務帳戶 chat@system.gserviceaccount.com 簽署。

使用 Cloud Run functions 驗證要求 (非外掛程式的 Chat 應用程式)

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

接著,請按照下列步驟,授權 Google Chat 服務帳戶 chat@system.gserviceaccount.com 做為呼叫者:

控制台

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

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

    前往 Cloud Run。

  2. 在 Cloud Run 服務清單中,勾選目標函式旁邊的核取方塊。(請勿點選函式本身)。

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

  4. 按一下「Add principal」(新增主體)。

  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 應用程式)

如果非外掛程式的 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/。

下列範例說明如何使用 Google OAuth 用戶端程式庫,驗證不記名權杖是由 Google Chat 核發,且目標是您的 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 應用程式)

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