التحقّق من الطلبات من 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، يجب أن تستجيب خدمتك للطلب باستخدام رمز الاستجابة 401 (Unauthorized).

مصادقة الطلبات باستخدام دوال Cloud أو Cloud Run

في حال تنفيذ منطق الوظيفة باستخدام Cloud Functions أو Cloud Run، عليك اختيار App URL في الحقل Authentication Audience (جمهور المصادقة) ضمن إعداد الاتصال في تطبيق Chat والتأكّد من أنّ عنوان URL للتطبيق في الإعدادات يتوافق مع عنوان URL لـ Cloud Function أو نقطة نهاية Cloud Run.

بعد ذلك، عليك تفويض حساب خدمة Google Chat chat@system.gserviceaccount.com كمُستدعي.

توضّح الخطوات التالية كيفية استخدام دوال Cloud (الجيل الأول):

وحدة التحكّم

بعد نشر الدالة على Google Cloud:

  1. في وحدة التحكّم في Google Cloud، انتقِل إلى صفحة "وظائف السحابة الإلكترونية":

    الانتقال إلى وظائف السحابة الإلكترونية

  2. في قائمة "دوال السحابة الإلكترونية"، انقر على مربّع الاختيار بجانب وظيفة الاستلام. (لا تنقر على الدالة نفسها.)

  3. النقر على الأذونات في أعلى الشاشة ستفتح لوحة الأذونات.

  4. انقر على إضافة مدير.

  5. في الحقل القواعد الرئيسية الجديدة، أدخِل chat@system.gserviceaccount.com.

  6. اختَر الدور Cloud Functions > مسوّد دوال Cloud من القائمة المنسدلة اختيار دور.

  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 (الجيل الثاني) أو Cloud Run:

وحدة التحكّم

بعد نشر الدالة أو الخدمة على Google Cloud:

  1. في Google Cloud Console، انتقِل إلى صفحة "تشغيل السحابة الإلكترونية":

    الانتقال إلى Cloud Run

  2. في قائمة خدمات تشغيل السحابة، انقر على مربع الاختيار بجوار وظيفة الاستلام. (لا تنقر على الدالة نفسها.)

  3. النقر على الأذونات في أعلى الشاشة ستفتح لوحة الأذونات.

  4. انقر على إضافة مدير.

  5. في الحقل القواعد الرئيسية الجديدة، أدخِل chat@system.gserviceaccount.com.

  6. اختَر الدور تشغيل السحابة الإلكترونية > مُستدعي 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 للتطبيق

إذا تم ضبط حقل Authentication Audience (جمهور المصادقة) في إعداد الاتصال في تطبيق Chat على App URL، يكون الرمز المميّز لحامل البطاقة في الطلب هو رمز تعريف (OIDC) موقَّع من Google لـ OpenID Connect. تم ضبط الحقل email على chat@system.gserviceaccount.com. يتم ضبط الحقل audience على عنوان URL الذي ضبطته في Google Chat لإرسال الطلبات إلى تطبيقك في Chat. على سبيل المثال، إذا كانت نقطة النهاية التي تم ضبطها لتطبيق Chat هي https://example.com/app/، يكون الحقل audience في الرمز المميّز للمعرّف هو https://example.com/app/.

توضّح النماذج التالية كيفية التحقّق من أنّ الرمز المميّز للحامل قد تم إصداره من خلال Google Chat ويستهدف تطبيقك باستخدام مكتبة برامج 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();

مصادقة الطلبات باستخدام رقم المشروع JWT

في حال ضبط حقل Authentication Audience (جمهور المصادقة) ضمن إعداد الاتصال في تطبيق Chat على Project Number (أو لم يتم ضبطه)، يكون الرمز المميّز لحامل البطاقة في الطلب هو رمز JSON المميّز للويب (JWT) الموقَّع ذاتيًا، ويكون صادرًا وموقَّعًا من قِبل chat@system.gserviceaccount.com. تم ضبط الحقل audience على رقم مشروع Google Cloud الذي استخدمته لإنشاء تطبيق Chat. على سبيل المثال، إذا كان رقم مشروع Google Cloud الخاص بتطبيق Chat هو 1234567890، يكون حقل audience في JWT هو 1234567890.

توضّح النماذج التالية كيفية التحقّق من أنّ الرمز المميّز للحامل قد تم إصداره من خلال Google Chat ويستهدف مشروعك باستخدام مكتبة عملاء 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();