ยืนยันคำขอจาก Google Chat

ส่วนสำหรับแอป Google Chat ที่สร้างขึ้นบนปลายทาง HTTP หัวข้อนี้จะอธิบายวิธียืนยันว่าคำขอที่ส่งไปยังปลายทางของคุณมาจาก Chat

Google จะส่งคำขอไปยังบริการของคุณเพื่อส่งเหตุการณ์การโต้ตอบไปยังปลายทางของแอป Chat Chat จะใส่โทเค็นสำหรับผู้ถือไว้ในส่วนหัวของ Authorization ของคำขอ HTTPS ทุกรายการที่ส่งไปยังปลายทางเพื่อยืนยันว่าคำขอนั้นมาจาก Google ตัวอย่างเช่น

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 ในฐานะผู้เรียกใช้

ขั้นตอนต่อไปนี้จะแสดงวิธีใช้ Cloud Functions (รุ่นที่ 1)

คอนโซล

หลังจากทำให้ฟังก์ชันใช้งานได้ใน Google Cloud แล้ว ให้ทำดังนี้

  1. ในคอนโซล Google Cloud ให้ไปที่หน้า Cloud Functions แล้วทำดังนี้

    ไปที่ Cloud Functions

  2. ในรายการ Cloud Functions ให้คลิกช่องทำเครื่องหมายข้างฟังก์ชันการรับ (อย่าคลิกที่ฟังก์ชัน)

  3. คลิกสิทธิ์ที่ด้านบนของหน้าจอ แผงสิทธิ์ จะเปิดขึ้น

  4. คลิกเพิ่มผู้ใช้หลัก

  5. ในช่องผู้ใช้หลักใหม่ ให้ป้อน chat@system.gserviceaccount.com

  6. เลือกบทบาท Cloud Functions > ผู้เรียกใช้ Cloud Functions จากเมนูแบบเลื่อนลงเลือกบทบาท

  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 ด้วยชื่อฟังก์ชันของแอป Chat

ขั้นตอนต่อไปนี้แสดงวิธีใช้บริการ 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 จากเมนูแบบเลื่อนลงเลือกบทบาท

  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

ตรวจสอบสิทธิ์คำขอด้วยโทเค็นรหัส URL ของแอป

หากช่องผู้ชมการตรวจสอบสิทธิ์ของแอป Chat การตั้งค่าการเชื่อมต่อมีการตั้งค่าเป็น App URL โทเค็นการให้สิทธิ์สำหรับผู้ถือในคำขอจะเป็นโทเค็นรหัส ของ OpenID Connect (OIDC) ที่ Google รับรอง ตั้งค่าช่อง email เป็น chat@system.gserviceaccount.com ช่อง audience ได้รับการตั้งค่าเป็น URL ที่คุณกำหนดค่าให้ Google Chat ส่งคำขอไปยังแอป Chat เช่น หากปลายทางที่กำหนดค่าของแอป Chat คือ https://example.com/app/ ช่อง audience ในโทเค็นรหัสจะเป็น https://example.com/app/

ตัวอย่างต่อไปนี้แสดงวิธียืนยันว่า Google Chat ออกโทเค็นสำหรับผู้ถือและกำหนดเป้าหมายไปที่แอปของคุณโดยใช้ไลบรารีไคลเอ็นต์ OAuth ของ 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();

ตรวจสอบสิทธิ์คำขอด้วยหมายเลขโปรเจ็กต์ JWT

หากช่องผู้ชมการตรวจสอบสิทธิ์ของแอป Chat การตั้งค่าการเชื่อมต่อมีการตั้งค่าเป็น Project Number (หรือไม่ได้ตั้งค่า) โทเค็นการให้สิทธิ์สำหรับผู้ถือในคำขอจะเป็นเว็บโทเค็น JSON (JWT) ที่ลงชื่อด้วยตนเอง ซึ่งออกและลงนามโดย chat@system.gserviceaccount.com ช่อง audience ได้รับการตั้งค่าเป็นหมายเลขโปรเจ็กต์ Google Cloud ที่คุณใช้สร้างแอป Chat เช่น หากหมายเลขโปรเจ็กต์ระบบคลาวด์ของแอป Chat คือ 1234567890 ช่อง audience ใน JWT จะเป็น 1234567890

ตัวอย่างต่อไปนี้แสดงวิธียืนยันว่า Google Chat เป็นผู้ออกโทเค็นสำหรับผู้ถือและกำหนดเป้าหมายไปที่โปรเจ็กต์ของคุณโดยใช้ไลบรารีไคลเอ็นต์ OAuth ของ 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();