Google Chat'ten gelen istekleri doğrulama

HTTP uç noktaları üzerine oluşturulan Google Chat uygulamaları için bu bölümde, uç noktanıza gelen isteklerin Chat'ten geldiğini nasıl doğrulayacağınız açıklanmaktadır.

Google, etkileşim etkinliklerini Chat uygulamanızın uç noktasına göndermek için hizmetinize istekler gönderir. İsteğin Google'dan geldiğini doğrulamak için Chat, uç noktanıza gönderilen her HTTPS isteğinin Authorization başlığına bir hamiline ait jeton ekler. Örneğin:

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

Yukarıdaki örnekte yer alan AbCdEf123456 dizesi, hamiline ait yetkilendirme jetonudur. Bu, Google tarafından oluşturulan bir şifreleme jetonudur. Hamiline ait jetonun türü ve audience alanının değeri, Chat uygulamasını yapılandırırken seçtiğiniz kimlik doğrulama kitlesi türüne bağlıdır.

Chat uygulamanızı Cloud Functions veya Cloud Run'ı kullanarak uyguladıysanız Cloud IAM, jeton doğrulamasını otomatik olarak gerçekleştirir. Google Chat hizmet hesabını yetkili bir çağrıcı olarak eklemeniz yeterlidir. Uygulamanız kendi HTTP sunucusunu uyguluyorsa açık kaynak bir Google API istemci kitaplığı kullanarak hamiline ait jetonunuzu doğrulayabilirsiniz:

Jeton, Chat uygulamasını doğrulamazsa hizmetiniz isteğe HTTPS yanıt koduyla (401 (Unauthorized)) yanıt vermelidir.

Cloud Functions veya Cloud Run kullanarak isteklerin kimliğini doğrulama

İşlev mantığınız Cloud Functions veya Cloud Run kullanılarak uygulandıysa Chat uygulaması bağlantı ayarının Kimlik Doğrulama Kitlesi alanında App URL öğesini seçmeniz ve yapılandırmadaki uygulama URL'sinin Cloud Functions veya Cloud Run uç noktasının URL'sine karşılık geldiğinden emin olmanız gerekir.

Ardından, Google Chat hizmet hesabını (chat@system.gserviceaccount.com) çağırıcı olarak yetkilendirmeniz gerekir.

Aşağıdaki adımlarda Cloud Functions'ın (1. nesil) nasıl kullanılacağı gösterilmektedir:

Konsol

İşlevinizi Google Cloud'a dağıttıktan sonra:

  1. Google Cloud Console'da Cloud Functions sayfasına gidin:

    Cloud Functions'a git

  2. Cloud Functions listesinde, alma işlevinin yanındaki onay kutusunu tıklayın. (İşlevi tıklamayın.)

  3. Ekranın üst kısmındaki İzinler'i tıklayın. İzinler paneli açılır.

  4. Ana hesap ekle'yi tıklayın.

  5. Yeni ana hesaplar alanına chat@system.gserviceaccount.com yazın.

  6. Rol seçin açılır menüsünden Cloud Functions > Cloud Functions Çağırıcısı rolünü seçin.

  7. Kaydet'i tıklayın.

gcloud

gcloud functions add-iam-policy-binding komutunu kullanın:

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

RECEIVING_FUNCTION değerini Chat uygulamanızın işlevinin adıyla değiştirin.

Aşağıdaki adımlarda, Cloud Functions (2. nesil) veya Cloud Run hizmetlerinin nasıl kullanılacağı gösterilmektedir:

Konsol

İşlevinizi veya hizmetinizi Google Cloud'a dağıttıktan sonra:

  1. Google Cloud Console'da Cloud Run sayfasına gidin:

    Cloud Run'a git

  2. Cloud Run hizmetleri listesinde, alma işlevinin yanındaki onay kutusunu tıklayın. (İşlevi tıklamayın.)

  3. Ekranın üst kısmındaki İzinler'i tıklayın. İzinler paneli açılır.

  4. Ana hesap ekle'yi tıklayın.

  5. Yeni ana hesaplar alanına chat@system.gserviceaccount.com yazın.

  6. Rol seçin açılır menüsünden Cloud Run > Cloud Run Çağırıcısı rolünü seçin.

  7. Kaydet'i tıklayın.

gcloud

gcloud functions add-invoker-policy-binding komutunu kullanın:

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

RECEIVING_FUNCTION değerini Chat uygulamanızın işlevinin adıyla değiştirin.

Uygulama URL'si Kimliği Jetonu ile isteklerin kimliğini doğrulama

Chat uygulaması bağlantı ayarının Authentication Audience (Kimlik Doğrulama Kitlesi) alanı App URL olarak ayarlanırsa istekteki hamiline ait yetkilendirme jetonu, Google tarafından imzalanmış bir RFC Connect (OIDC) kimlik jetonudur. email alanı chat@system.gserviceaccount.com olarak ayarlanmış. audience alanı, Google Chat'i Chat uygulamanıza istek göndermesi için yapılandırdığınız URL olarak ayarlanır. Örneğin, Chat uygulamanızın yapılandırılmış uç noktası https://example.com/app/ ise kimlik jetonunda audience alanı https://example.com/app/ olur.

Aşağıdaki örneklerde, hamiline ait jetonun Google Chat tarafından yayınlandığını ve Google OAuth istemci kitaplığı kullanılarak uygulamanızı hedeflediğinin nasıl doğrulanacağı gösterilmektedir.

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

Proje Numarası JWT ile isteklerin kimliğini doğrulama

Chat uygulaması bağlantı ayarının Authentication Audience alanı Project Number olarak ayarlanırsa istekteki hamiline ait yetkilendirme jetonu, chat@system.gserviceaccount.com tarafından yayınlanan ve imzalanan kendinden imzalı bir JSON Web Jetonu (JWT) olur. audience alanı, Chat uygulamanızı oluşturmak için kullandığınız Google Cloud proje numarası olarak ayarlanır. Örneğin, Chat uygulamanızın Cloud proje numarası 1234567890 ise JWT'deki audience alanı 1234567890 olur.

Aşağıdaki örneklerde, hamiline ait jetonun Google Chat tarafından yayınlandığını ve Google OAuth istemci kitaplığı kullanılarak projenizi hedeflediğinin nasıl doğrulanacağı gösterilmektedir.

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