Anfragen von Google Chat prüfen

Für Google Chat-Apps, die auf HTTP-Endpunkten basieren, wird in diesem Abschnitt erläutert, wie Sie prüfen, ob die Anfragen an Ihren Endpunkt von Google Chat stammen.

Google sendet Anfragen an Ihren Dienst, um Interaktionsereignisse an den Endpunkt Ihrer Chat-App weiterzuleiten. Google Chat fügt im Authorization-Header jeder HTTPS-Anfrage an Ihren Endpunkt ein Inhabertoken ein, um zu prüfen, ob die Anfrage von Google kommt. Beispiel:

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

Der String AbCdEf123456 im vorherigen Beispiel ist das Inhaberautorisierungstoken. Dies ist ein von Google erstelltes kryptografisches Token. Der Typ des Inhabertokens und der Wert des Felds audience hängen von der Art der Authentifizierungszielgruppe ab, die Sie beim Konfigurieren der Chat-App ausgewählt haben.

Wenn Sie Ihre Chat-Anwendung mit Cloud Functions oder Cloud Run implementiert haben, führt Cloud IAM die Tokenüberprüfung automatisch durch. Sie müssen nur das Google Chat-Dienstkonto als autorisierten Aufrufer hinzufügen. Wenn Ihre Anwendung einen eigenen HTTP-Server implementiert, können Sie das Inhabertoken mithilfe einer Open-Source-Google API-Clientbibliothek verifizieren:

Wenn das Token für die Chat-App nicht bestätigt wird, sollte Ihr Dienst auf die Anfrage mit dem HTTPS-Antwortcode 401 (Unauthorized) antworten.

Anfragen mit Cloud Functions oder Cloud Run authentifizieren

Wenn Ihre Funktionslogik mit Cloud Functions oder Cloud Run implementiert wird, müssen Sie App URL im Feld Authentifizierungsziel der Verbindungseinstellung der Chat-App auswählen und darauf achten, dass die App-URL in der Konfiguration der URL der Cloud Functions-Funktion oder des Cloud Run-Endpunkts entspricht.

Anschließend müssen Sie das Google Chat-Dienstkonto chat@system.gserviceaccount.com als Aufrufer autorisieren.

Die folgenden Schritte zeigen, wie Cloud Functions (1. Generation) verwendet wird:

Console

Nach der Bereitstellung der Funktion in Google Cloud:

  1. Wechseln Sie in der Google Cloud Console zur Seite Cloud Functions:

    Zu Cloud Functions

  2. Klicken Sie in der Cloud Functions-Liste auf das Kästchen neben der empfangenden Funktion. (Klicken Sie nicht auf die Funktion selbst.)

  3. Klicken Sie oben auf dem Bildschirm auf Berechtigungen. Der Bereich Berechtigungen wird geöffnet.

  4. Klicken Sie auf Hauptkonto hinzufügen.

  5. Geben Sie im Feld Neue Hauptkonten chat@system.gserviceaccount.com ein.

  6. Wählen Sie im Drop-down-Menü Rolle auswählen die Rolle Cloud Functions > Cloud Functions-Aufrufer aus.

  7. Klicke auf Speichern.

gcloud

Führen Sie den Befehl gcloud functions add-iam-policy-binding aus:

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

Ersetzen Sie RECEIVING_FUNCTION durch den Namen der Funktion Ihrer Chat-App.

Die folgenden Schritte zeigen, wie Sie Cloud Functions (2. Generation) oder Cloud Run-Dienste verwenden:

Console

Nach der Bereitstellung der Funktion oder des Dienstes in Google Cloud:

  1. Rufen Sie in der Google Cloud Console die Seite „Cloud Run“ auf:

    Zu Cloud Run

  2. Klicken Sie in der Liste der Cloud Run-Dienste auf das Kästchen neben der empfangenden Funktion. (Klicken Sie nicht auf die Funktion selbst.)

  3. Klicken Sie oben auf dem Bildschirm auf Berechtigungen. Der Bereich Berechtigungen wird geöffnet.

  4. Klicken Sie auf Hauptkonto hinzufügen.

  5. Geben Sie im Feld Neue Hauptkonten chat@system.gserviceaccount.com ein.

  6. Wählen Sie im Drop-down-Menü Rolle auswählen die Rolle Cloud Run > Cloud Run Invoker aus.

  7. Klicke auf Speichern.

gcloud

Führen Sie den Befehl gcloud functions add-invoker-policy-binding aus:

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

Ersetzen Sie RECEIVING_FUNCTION durch den Namen der Funktion Ihrer Chat-App.

Anfragen mit einem App-URL-ID-Token authentifizieren

Wenn das Feld „Authentication Audience“ der Verbindungseinstellung der Chat-App auf App URL gesetzt ist, ist das Inhaberautorisierungstoken in der Anfrage ein von Google signiertes OIDC-ID-Token (OpenID Connect). Das Feld email ist auf chat@system.gserviceaccount.com gesetzt. Das Feld audience ist auf die URL gesetzt, die Sie Google Chat so konfiguriert haben, dass Anfragen an Ihre Chat-App gesendet werden. Wenn der konfigurierte Endpunkt Ihrer Chat-App beispielsweise https://example.com/app/ ist, ist das Feld audience im ID-Token https://example.com/app/.

In den folgenden Beispielen wird gezeigt, wie Sie mithilfe der Google OAuth-Clientbibliothek prüfen, ob das Inhabertoken von Google Chat ausgegeben und auf Ihre Anwendung ausgerichtet wurde.

Java

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.json.JsonFactory;

/** Tool for verifying JWT Tokens for Apps in Google Chat. */
public class JWTVerify {
  // Bearer Tokens received by apps will always specify this issuer.
  static String CHAT_ISSUER = "chat@system.gserviceaccount.com";

  // Intended audience of the token, which is the URL of the app.
  static String AUDIENCE = "https://example.com/app/";

  // Get this value from the request's Authorization HTTPS header.
  // For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456".
  static String BEARER_TOKEN = "AbCdEf123456";

  public static void main(String[] args) throws GeneralSecurityException, IOException {
    JsonFactory factory = new GsonFactory();

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

    GoogleIdToken idToken = GoogleIdToken.parse(factory, BEARER_TOKEN);
    if (idToken == null) {
      System.out.println("Token cannot be parsed");
      System.exit(-1);
    }

    // Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    if (!verifier.verify(idToken)
        || !idToken.getPayload().getEmailVerified()
        || !idToken.getPayload().getEmail().equals(CHAT_ISSUER)) {
      System.out.println("Invalid token");
      System.exit(-1);
    }

    // Token originates from Google and is targeted to a specific client.
    System.out.println("The token is valid");
  }
}

Python

import sys
from google.oauth2 import id_token
from google.auth.transport import requests

# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

# Intended audience of the token, which is the URL of the app.
AUDIENCE = 'https://example.com/app/'

# Get this value from the request's Authorization HTTPS header.
# For example, for 'Authorization: Bearer AbCdEf123456' use 'AbCdEf123456'.
BEARER_TOKEN = 'AbCdEf123456'

try:
  # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  request = requests.Request()
  token = id_token.verify_oauth2_token(BEARER_TOKEN, request, AUDIENCE)

  if token['email'] != CHAT_ISSUER:
    sys.exit('Invalid token')
except:
  sys.exit('Invalid token')

# Token originates from Google and is targeted to a specific client.
print('The token is valid')

Node.js

import {OAuth2Client} from 'google-auth-library';

// Bearer Tokens received by apps will always specify this issuer.
const CHAT_ISSUER = 'chat@system.gserviceaccount.com';

// Intended audience of the token, which is the URL of the app.
const AUDIENCE = 'https://example.com/app/';

// Get this value from the request's Authorization HTTPS header.
// For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456"
const BEARER_TOKEN = 'AbCdEf123456';

const client = new OAuth2Client();

async function verify() {
  // Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  try {
    const ticket = await client.verifyIdToken({
      idToken: BEARER_TOKEN,
      audience: AUDIENCE
    });
    if (!ticket.getPayload().email_verified
        || ticket.getPayload().email !== CHAT_ISSUER) {
      throw new Error('Invalid issuer');
    }
  } catch (unused) {
    console.error('Invalid token');
    process.exit(1);
  }

  // Token originates from Google and is targeted to a specific client.
  console.log('The token is valid');
}

verify();

Anfragen mit einem JWT der Projektnummer authentifizieren

Wenn das Feld „Authentication Audience“ der Verbindungseinstellung der Chat-App auf Project Number gesetzt ist, ist das Inhaberautorisierungstoken in der Anfrage ein selbstsigniertes JSON Web Token (JWT), das von chat@system.gserviceaccount.com ausgestellt und signiert wurde. Das Feld audience ist auf die Google Cloud-Projektnummer gesetzt, die Sie zum Erstellen Ihrer Chat-App verwendet haben. Wenn die Cloud-Projektnummer Ihrer Chat-App beispielsweise 1234567890 lautet, ist das Feld audience im JWT 1234567890.

In den folgenden Beispielen wird gezeigt, wie Sie mithilfe der Google OAuth-Clientbibliothek prüfen, ob das Inhabertoken von Google Chat ausgegeben und auf Ihr Projekt ausgerichtet wurde.

Java

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.json.JsonFactory;

/** Tool for verifying JWT Tokens for Apps in Google Chat. */
public class JWTVerify {
  // Bearer Tokens received by apps will always specify this issuer.
  static String CHAT_ISSUER = "chat@system.gserviceaccount.com";

  // Url to obtain the public certificate for the issuer.
  static String PUBLIC_CERT_URL_PREFIX =
      "https://www.googleapis.com/service_accounts/v1/metadata/x509/";

  // Intended audience of the token, which is the project number of the app.
  static String AUDIENCE = "1234567890";

  // Get this value from the request's Authorization HTTPS header.
  // For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456".
  static String BEARER_TOKEN = "AbCdEf123456";

  public static void main(String[] args) throws GeneralSecurityException, IOException {
    JsonFactory factory = new GsonFactory();

    GooglePublicKeysManager.Builder keyManagerBuilder =
        new GooglePublicKeysManager.Builder(new ApacheHttpTransport(), factory);

    String certUrl = PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER;
    keyManagerBuilder.setPublicCertsEncodedUrl(certUrl);

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

    GoogleIdToken idToken = GoogleIdToken.parse(factory, BEARER_TOKEN);
    if (idToken == null) {
      System.out.println("Token cannot be parsed");
      System.exit(-1);
    }

    // Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    if (!verifier.verify(idToken)
        || !idToken.verifyAudience(Collections.singletonList(AUDIENCE))
        || !idToken.verifyIssuer(CHAT_ISSUER)) {
      System.out.println("Invalid token");
      System.exit(-1);
    }

    // Token originates from Google and is targeted to a specific client.
    System.out.println("The token is valid");
  }
}

Python

import sys

from google.oauth2 import id_token
from google.auth.transport import requests

# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

# Url to obtain the public certificate for the issuer.
PUBLIC_CERT_URL_PREFIX = 'https://www.googleapis.com/service_accounts/v1/metadata/x509/'

# Intended audience of the token, which will be the project number of the app.
AUDIENCE = '1234567890'

# Get this value from the request's Authorization HTTPS header.
# For example, for 'Authorization: Bearer AbCdEf123456' use 'AbCdEf123456'.
BEARER_TOKEN = 'AbCdEf123456'

try:
  # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  request = requests.Request()
  certs_url = PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER
  token = id_token.verify_token(BEARER_TOKEN, request, AUDIENCE, certs_url)

  if token['iss'] != CHAT_ISSUER:
    sys.exit('Invalid issuer')
except:
  sys.exit('Invalid token')

# Token originates from Google and is targeted to a specific client.
print('The token is valid')

Node.js

import fetch from 'node-fetch';
import {OAuth2Client} from 'google-auth-library';

// Bearer Tokens received by apps will always specify this issuer.
const CHAT_ISSUER = 'chat@system.gserviceaccount.com';

// Url to obtain the public certificate for the issuer.
const PUBLIC_CERT_URL_PREFIX =
    'https://www.googleapis.com/service_accounts/v1/metadata/x509/';

// Intended audience of the token, which is the project number of the app.
const AUDIENCE = '1234567890';

// Get this value from the request's Authorization HTTPS header.
// For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456"
const BEARER_TOKEN = 'AbCdEf123456';

const client = new OAuth2Client();

/** Verifies JWT Tokens for Apps in Google Chat. */
async function verify() {
  // Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  try {
    const response = await fetch(PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER);
    const certs = await response.json();
    const ticket = await client.verifySignedJwtWithCertsAsync(
        BEARER_TOKEN, certs, AUDIENCE, [CHAT_ISSUER]);
  } catch (unused) {
    console.error('Invalid token');
    process.exit(1);
  }

  // Token originates from Google and is targeted to a specific client.
  console.log('The token is valid');
}

verify();