Weryfikowanie próśb z Google Chat

W przypadku aplikacji Google Chat utworzonych na punktach końcowych HTTP ta sekcja wyjaśnia, jak sprawdzić, czy żądania wysyłane do punktu końcowego pochodzą z Google Chat.

Aby wysyłać zdarzenia interakcji do punktu końcowego aplikacji Google Chat, Google wysyła żądania do Twojej usługi. Aby sprawdzić, czy żądanie pochodzi od Google, Google Chat umieszcza token okaziciela w nagłówku Authorization każdego żądania HTTPS kierowanego do punktu końcowego. Przykład:

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

Ciąg AbCdEf123456 w poprzednim przykładzie jest tokenem autoryzacji okaziciela. To token kryptograficzny wygenerowany przez Google. Typ tokena okaziciela i wartość w polu audience zależą od typu odbiorców uwierzytelniania wybranych podczas konfigurowania aplikacji Google Chat.

Jeśli Twoja aplikacja do obsługi czatu została zaimplementowana za pomocą funkcji w Cloud Functions lub Cloud Run, Cloud IAM automatycznie obsługuje weryfikację tokenów. Wystarczy, że dodasz konto usługi Google Chat jako autoryzowany wywołujący. Jeśli Twoja aplikacja ma własny serwer HTTP, możesz zweryfikować token okaziciela, korzystając z biblioteki klienta interfejsu API Google typu open source:

Jeśli token nie przejdzie weryfikacji dla aplikacji Google Chat, usługa powinna odpowiedzieć na żądanie kodem odpowiedzi HTTPS 401 (Unauthorized).

Uwierzytelnianie żądań za pomocą Cloud Functions lub Cloud Run

Jeśli logika funkcji jest zaimplementowana za pomocą Cloud Functions lub Cloud Run, musisz wybrać App URL w polu Odbiorcy uwierzytelniania w ustawieniach połączenia aplikacji Google Chat i sprawdzić, czy adres URL aplikacji w konfiguracji odpowiada adresowi URL punktu końcowego Cloud Functions lub Cloud Run.

Następnie musisz autoryzować konto usługi Google Chat chat@system.gserviceaccount.com jako wywołujący.

Poniżej znajdziesz instrukcje korzystania z Cloud Functions (1 generacji):

Konsola

Po wdrożeniu funkcji w Google Cloud:

  1. W konsoli Google Cloud otwórz stronę Cloud Functions:

    Otwórz Cloud Functions

  2. Na liście funkcji w Cloud Functions kliknij pole wyboru obok funkcji odbierania. Nie klikaj samej funkcji.

  3. Kliknij Uprawnienia u góry ekranu. Otworzy się panel Uprawnienia.

  4. Kliknij Dodaj podmiot zabezpieczeń.

  5. W polu Nowe podmioty zabezpieczeń wpisz chat@system.gserviceaccount.com.

  6. W menu Wybierz rolę wybierz rolę Cloud Functions > Wywołujący funkcje Cloud Functions.

  7. Kliknij Zapisz.

gcloud

Użyj polecenia gcloud functions add-iam-policy-binding:

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

Zastąp RECEIVING_FUNCTION nazwą funkcji aplikacji Google Chat.

Poniższe kroki pokazują, jak korzystać z usług Cloud Functions (2 generacji) lub Cloud Run:

Konsola

Po wdrożeniu funkcji lub usługi w Google Cloud:

  1. W konsoli Google Cloud otwórz stronę Cloud Run:

    Otwórz Cloud Run

  2. Na liście usług Cloud Run kliknij pole wyboru obok funkcji odbierania. Nie klikaj samej funkcji.

  3. Kliknij Uprawnienia u góry ekranu. Otworzy się panel Uprawnienia.

  4. Kliknij Dodaj podmiot zabezpieczeń.

  5. W polu Nowe podmioty zabezpieczeń wpisz chat@system.gserviceaccount.com.

  6. W menu Wybierz rolę wybierz rolę Cloud Run > Wywołujący Cloud Run.

  7. Kliknij Zapisz.

gcloud

Użyj polecenia gcloud functions add-invoker-policy-binding:

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

Zastąp RECEIVING_FUNCTION nazwą funkcji aplikacji Google Chat.

Uwierzytelnianie żądań za pomocą tokena identyfikatora adresu URL aplikacji

Jeśli pole Authentication Audience w ustawieniach połączenia aplikacji Google Chat ma wartość App URL, token autoryzacji okaziciela w żądaniu to podpisany przez Google token identyfikatora OpenID Connect (OIDC). Pole email jest ustawione na chat@system.gserviceaccount.com. Pole audience jest ustawione na adres URL skonfigurowany w Google Chat pod kątem wysyłania żądań do aplikacji Google Chat. Jeśli na przykład skonfigurowany punkt końcowy aplikacji Google Chat to https://example.com/app/, pole audience w tokenie identyfikatora to https://example.com/app/.

Z poniższych przykładów dowiesz się, jak sprawdzić, czy token okaziciela został wydany przez Google Chat i kierowany na Twoją aplikację za pomocą biblioteki klienta Google OAuth.

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();

Uwierzytelnianie żądań za pomocą tokena JWT z numerem projektu

Jeśli pole Authentication Audience w ustawieniach połączenia aplikacji Google Chat jest ustawione na Project Number (lub nie jest ustawione), token autoryzacji okaziciela w żądaniu jest samodzielnie podpisanym tokenem internetowym JSON (JWT) wydanym i podpisanym przez chat@system.gserviceaccount.com. W polu audience jest ustawiony numer projektu Google Cloud użytego do utworzenia aplikacji Google Chat. Jeśli na przykład numer projektu Cloud Twojej aplikacji Google Chat to 1234567890, to pole audience w tokenie JWT to 1234567890.

Z poniższych przykładów dowiesz się, jak sprawdzić, czy token okaziciela został wystawiony przez Google Chat i kierowany na Twój projekt za pomocą biblioteki klienta Google OAuth.

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();