Проверка запросов от Google Chat

Для приложений Google Chat, созданных на основе конечных точек HTTP, в этом разделе объясняется, как проверить, что запросы к вашей конечной точке поступают из Chat.

Чтобы отправить события взаимодействия на конечную точку вашего приложения Chat, Google отправляет запросы к вашей службе. Чтобы убедиться, что запрос исходит от Google, Chat включает токен носителя в заголовок Authorization каждого HTTPS-запроса к вашей конечной точке. Например:

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

Строка AbCdEf123456 в предыдущем примере является токеном авторизации носителя. Это криптографический токен, созданный Google. Тип токена носителя и значение поля audience зависят от типа аудитории аутентификации, выбранного вами при настройке приложения Chat .

Если вы внедрили приложение Chat с помощью Cloud Functions или Cloud Run, Cloud IAM автоматически выполняет проверку токена. Вам просто нужно добавить учетную запись службы Google Chat в качестве авторизованного пользователя. Если ваше приложение реализует собственный HTTP-сервер, вы можете проверить свой токен носителя с помощью клиентской библиотеки Google API с открытым исходным кодом:

Если токен не проверяется для приложения Chat, ваша служба должна ответить на запрос кодом ответа HTTPS 401 (Unauthorized) .

Аутентификация запросов с помощью Cloud Functions или Cloud Run.

Если логика вашей функции реализована с помощью Cloud Functions или Cloud Run, необходимо выбрать App URL в поле Аудитория аутентификации настройки подключения к приложению Chat и убедиться, что URL-адрес приложения в конфигурации соответствует URL-адресу Cloud Function или Cloud Run. конечная точка.

Затем вам необходимо авторизовать учетную запись службы Google Chat chat@system.gserviceaccount.com в качестве инициатора.

Следующие шаги показывают, как использовать облачные функции (1-го поколения):

Консоль

После развертывания вашей функции в Google Cloud:

  1. В консоли Google Cloud перейдите на страницу «Функции облака»:

    Перейти к облачным функциям

  2. В списке «Облачные функции» установите флажок рядом с функцией приема. (Не нажимайте на саму функцию.)

  3. Нажмите «Разрешения» в верхней части экрана. Откроется панель «Разрешения» .

  4. Нажмите Добавить принципала .

  5. В поле «Новые участники» введите chat@system.gserviceaccount.com .

  6. В раскрывающемся меню «Выберите роль» выберите роль «Облачные функции» > «Вызов облачных функций» .

  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 на имя функции вашего приложения чата.

Следующие шаги показывают, как использовать Cloud Functions (2-го поколения) или службы Cloud Run:

Консоль

После развертывания вашей функции или сервиса в Google Cloud:

  1. В консоли Google Cloud перейдите на страницу Cloud Run:

    Перейти в Cloud Run

  2. В списке сервисов Cloud Run установите флажок рядом с функцией приема. (Не нажимайте на саму функцию.)

  3. Нажмите «Разрешения» в верхней части экрана. Откроется панель «Разрешения» .

  4. Нажмите Добавить принципала .

  5. В поле «Новые участники» введите chat@system.gserviceaccount.com .

  6. В раскрывающемся меню «Выберите роль» выберите роль Cloud Run > Cloud Run Invoker .

  7. Нажмите Сохранить .

gcloud

Используйте команду gcloud functions add-invoker-policy-binding :

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

Замените RECEIVING_FUNCTION на имя функции вашего приложения чата.

Аутентификация запросов с помощью токена идентификатора URL-адреса приложения.

Если в поле «Аудитория аутентификации» настройки подключения к приложению чата установлено значение App URL , токен авторизации носителя в запросе представляет собой подписанный Google токен идентификатора OpenID Connect (OIDC). В поле email установлено значение chat@system.gserviceaccount.com . В поле audience указан URL-адрес, который вы настроили в Google Chat для отправки запросов в ваше приложение Chat. Например, если настроенная конечная точка вашего приложения Chat — https://example.com/app/ , то поле audience в токене идентификатора — https://example.com/app/ .

В следующих примерах показано, как с помощью клиентской библиотеки Google OAuth проверить, что токен носителя был выпущен Google Chat и предназначен для вашего приложения.

Джава

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

Питон

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

Аутентификация запросов по номеру проекта JWT.

Если в поле «Аудитория проверки подлинности» параметра подключения к приложению чата установлено Project Number (или не задано), токен авторизации носителя в запросе представляет собой самозаверяющий веб-токен JSON (JWT) , выданный и подписанный chat@system.gserviceaccount.com . В поле audience указан номер проекта Google Cloud, который вы использовали для создания приложения Chat. Например, если номер облачного проекта вашего приложения Chat — 1234567890 , то поле audience в JWT — 1234567890 .

В следующих примерах показано, как проверить, что токен на предъявителя был выпущен Google Chat и предназначен для вашего проекта с помощью клиентской библиотеки Google OAuth.

Джава

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

Питон

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