Xác minh yêu cầu từ Google Chat

Đối với các ứng dụng Google Chat được xây dựng trên các điểm cuối HTTP, phần này giải thích cách xác minh rằng các yêu cầu gửi đến điểm cuối của bạn đến từ Chat.

Để gửi các sự kiện tương tác đến điểm cuối của ứng dụng Chat, Google sẽ gửi yêu cầu tới dịch vụ của bạn. Để xác minh rằng yêu cầu đến từ Google, Chat sẽ bao gồm một mã thông báo mang trong tiêu đề Authorization của mọi yêu cầu HTTPS đến điểm cuối của bạn. Ví dụ:

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

Chuỗi AbCdEf123456 trong ví dụ trước là mã thông báo uỷ quyền mang. Đây là mã thông báo mật mã do Google tạo ra. Loại mã thông báo mang tải và giá trị của trường audience phụ thuộc vào loại đối tượng xác thực mà bạn đã chọn khi định cấu hình ứng dụng Chat.

Nếu bạn đã triển khai ứng dụng Chat bằng các hàm Cloud hoặc Cloud Run, thì Cloud IAM sẽ tự động xử lý việc xác minh mã thông báo. Bạn chỉ cần thêm tài khoản dịch vụ Google Chat làm người gọi được uỷ quyền. Nếu ứng dụng của bạn triển khai máy chủ HTTP của riêng ứng dụng đó, thì bạn có thể xác minh mã thông báo truyền tải của mình bằng cách sử dụng thư viện ứng dụng Google API nguồn mở:

Nếu mã thông báo không xác minh cho ứng dụng Chat, thì dịch vụ của bạn sẽ phản hồi yêu cầu bằng một mã phản hồi HTTPS 401 (Unauthorized).

Xác thực yêu cầu bằng Cloud Functions hoặc Cloud Run

Nếu logic hàm của bạn được triển khai bằng Cloud Functions hoặc Cloud Run, thì bạn phải chọn App URL trong trường Authentication Audience (Đối tượng xác thực) của chế độ cài đặt kết nối ứng dụng Chat và đảm bảo rằng URL ứng dụng trong cấu hình tương ứng với URL của hàm Cloud Function hoặc điểm cuối Cloud Run.

Sau đó, bạn cần uỷ quyền cho tài khoản dịch vụ Google Chat chat@system.gserviceaccount.com làm người gọi.

Các bước sau đây cho biết cách sử dụng Cloud Functions (thế hệ thứ 1):

Giao diện dòng lệnh

Sau khi triển khai chức năng lên Google Cloud:

  1. Trong bảng điều khiển Google Cloud, hãy truy cập vào trang Cloud Functions:

    Chuyển đến Cloud Functions

  2. Trong danh sách Cloud Functions, hãy nhấp vào hộp đánh dấu bên cạnh hàm nhận. (Đừng nhấp vào chính hàm đó.)

  3. Nhấp vào Quyền ở đầu màn hình. Bảng điều khiển Permissions (Quyền) sẽ mở ra.

  4. Nhấp vào Thêm người chính.

  5. Trong trường New uỷs (Người dùng mới), hãy nhập chat@system.gserviceaccount.com.

  6. Chọn vai trò Cloud Functions > Cloud Functions Invoker (Trình gọi hàm đám mây) từ trình đơn thả xuống Select a role (Chọn vai trò).

  7. Nhấp vào Lưu.

gcloud

Sử dụng lệnh gcloud functions add-iam-policy-binding:

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

Thay thế RECEIVING_FUNCTION bằng tên chức năng của ứng dụng Chat.

Các bước sau đây cho biết cách sử dụng Cloud Functions (thế hệ thứ 2) hoặc dịch vụ Cloud Run:

Giao diện dòng lệnh

Sau khi triển khai chức năng hoặc dịch vụ của bạn trên Google Cloud:

  1. Trong bảng điều khiển Google Cloud, hãy truy cập vào trang Cloud Run:

    Chuyển đến Cloud Run

  2. Trong danh sách các dịch vụ của Cloud Run, hãy nhấp vào hộp đánh dấu bên cạnh hàm nhận. (Đừng nhấp vào chính hàm đó.)

  3. Nhấp vào Quyền ở đầu màn hình. Bảng điều khiển Permissions (Quyền) sẽ mở ra.

  4. Nhấp vào Thêm người chính.

  5. Trong trường New uỷs (Người dùng mới), hãy nhập chat@system.gserviceaccount.com.

  6. Chọn vai trò Cloud Run > Cloud Run Invoker (Trình điều khiển Cloud Run) trong trình đơn thả xuống Select a role (Chọn vai trò).

  7. Nhấp vào Lưu.

gcloud

Sử dụng lệnh gcloud functions add-invoker-policy-binding:

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

Thay thế RECEIVING_FUNCTION bằng tên chức năng của ứng dụng Chat.

Xác thực yêu cầu bằng Mã thông báo mã nhận dạng URL ứng dụng

Nếu trường Đối tượng xác thực của chế độ cài đặt kết nối trong ứng dụng Chat được đặt thành App URL, thì mã thông báo uỷ quyền người dùng trong yêu cầu sẽ là mã thông báo mã nhận dạng (OIDC) do Google ký. Trường email được đặt thành chat@system.gserviceaccount.com. Trường audience được đặt thành URL mà bạn đã định cấu hình Google Chat để gửi yêu cầu đến ứng dụng Chat. Ví dụ: nếu điểm cuối đã định cấu hình của ứng dụng Chat là https://example.com/app/, thì trường audience trong mã thông báo mã nhận dạng sẽ là https://example.com/app/.

Các mẫu sau đây cho biết cách xác minh rằng mã thông báo truyền tải là do Google Chat phát hành và nhắm đến ứng dụng của bạn bằng thư viện ứng dụng OAuth của Google.

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

Xác thực yêu cầu bằng Mã số dự án JWT

Nếu trường Đối tượng xác thực của chế độ cài đặt kết nối trong ứng dụng Chat được đặt thành Project Number (hoặc chưa đặt), thì mã thông báo uỷ quyền của hàm mang trong yêu cầu là một Mã thông báo web JSON (JWT) tự ký, do chat@system.gserviceaccount.com cấp và ký. Trường audience được đặt thành số dự án trên Google Cloud mà bạn đã dùng để tạo ứng dụng Chat. Ví dụ: nếu số dự án trên đám mây của ứng dụng Chat là 1234567890, thì trường audience trong JWT là 1234567890.

Các mẫu sau đây cho biết cách xác minh rằng mã thông báo truyền tải là do Google Chat phát hành và nhắm đến dự án của bạn bằng thư viện ứng dụng OAuth của Google.

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