אימות בקשות מ-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, צריך לבחור באפשרות כתובת URL של נקודת קצה מסוג HTTP בשדה Authentication Audience בהגדרת החיבור של אפליקציית Chat, ולוודא שכתובת ה-URL של נקודת הקצה מסוג HTTP בהגדרה תואמת לכתובת ה-URL של נקודת הקצה ב-Cloud Functions או ב-Cloud Run.

לאחר מכן, צריך לאשר את חשבון השירות של Google Chat‏ chat@system.gserviceaccount.com כגורם מפעיל.

השלבים הבאים מראים איך להשתמש ב-Cloud Functions (דור ראשון):

המסוף

אחרי שפרסתם את הפונקציה ב-Google Cloud:

  1. נכנסים לדף Cloud Functions במסוף Google Cloud:

    כניסה אל Cloud Functions

  2. ברשימה של Cloud Functions, לוחצים על תיבת הסימון שלצד הפונקציה המקבלת. (לא לוחצים על הפונקציה עצמה).

  3. לוחצים על Permissions (הרשאות) בחלק העליון של המסך. נפתחת החלונית Permissions.

  4. לוחצים על Add principal.

  5. בשדה New principals, מזינים chat@system.gserviceaccount.com.

  6. בתפריט הנפתח Select a role, בוחרים את התפקיד Cloud Functions > Cloud Functions Invoker.

  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. נכנסים לדף Cloud Run במסוף Google Cloud:

    כניסה אל Cloud Run

  2. ברשימת השירותים של Cloud Run, לוחצים על תיבת הסימון שליד הפונקציה המקבלת. (אין ללחוץ על הפונקציה עצמה).

  3. לוחצים על Permissions (הרשאות) בחלק העליון של המסך. נפתחת החלונית Permissions.

  4. לוחצים על Add principal.

  5. בשדה New principals, מזינים chat@system.gserviceaccount.com.

  6. בתפריט הנפתח Select a role, בוחרים את התפקיד 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 בשם הפונקציה של אפליקציית Chat.

אימות בקשות HTTP באמצעות אסימון מזהה

אם השדה Authentication Audience בהגדרת החיבור של אפליקציית Chat מוגדר לכתובת ה-URL של נקודת הקצה מסוג HTTP, אסימון ההרשאה למוכ"ז בבקשה הוא אסימון מזהה של OpenID Connect בחתימה של Google (OIDC). השדה email מוגדר ל-chat@system.gserviceaccount.com. השדה Authentication Audience מוגדר לכתובת ה-URL שהגדרתם ב-Google Chat לשליחת בקשות לאפליקציית Chat. לדוגמה, אם נקודת הקצה שמוגדרת באפליקציית Chat היא https://example.com/app/, השדה Authentication Audience באסימון המזהה יהיה https://example.com/app/.

בדוגמאות הבאות מוסבר איך לאמת באמצעות ספריית הלקוח של Google OAuth שאסימון למוכ"ז הונפק על ידי Google Chat וכי הוא מיועד לאפליקציה שלכם.

Java

java/basic-app/src/main/java/com/google/chat/app/basic/App.java
String CHAT_ISSUER = "chat@system.gserviceaccount.com";
JsonFactory factory = JacksonFactory.getDefaultInstance();

GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), factory)
        .setAudience(Collections.singletonList(AUDIENCE))
        .build();

GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
return idToken != null
    && verifier.verify(idToken)
    && idToken.getPayload().getEmailVerified()
    && idToken.getPayload().getEmail().equals(CHAT_ISSUER);

Python

python/basic-app/main.py
# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

try:
    # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    request = requests.Request()
    token = id_token.verify_oauth2_token(bearer, request, AUDIENCE)
    return token['email'] == CHAT_ISSUER

except:
    return False

Node.js

node/basic-app/index.js
// Bearer Tokens received by apps will always specify this issuer.
const chatIssuer = 'chat@system.gserviceaccount.com';

// Verify valid token, signed by chatIssuer, intended for a third party.
try {
  const ticket = await client.verifyIdToken({
    idToken: bearer,
    audience: audience
  });
  return ticket.getPayload().email_verified
      && ticket.getPayload().email === chatIssuer;
} catch (unused) {
  return false;
}

אימות בקשות באמצעות JWT של מספר פרויקט

אם השדה Authentication Audience (קהל היעד לאימות) בהגדרת החיבור של אפליקציית Chat מוגדר כ-Project Number, אסימון ההרשאה למוכ"ז בבקשה הוא אסימון אינטרנט מסוג JSON‏ (JWT) בחתימה עצמית, שהונפק ונחתם על ידי chat@system.gserviceaccount.com. השדה audience מוגדר למספר הפרויקט ב-Google Cloud שבו השתמשתם כדי ליצור את אפליקציית Chat. לדוגמה, אם מספר הפרויקט ב-Cloud של אפליקציית Chat הוא 1234567890, השדה audience ב-JWT יהיה 1234567890.

הדוגמאות הבאות ממחישות איך לוודא שהאסימון למוכ"ז הונפק על ידי Google Chat ושהוא מטורגט לפרויקט שלכם באמצעות ספריית הלקוח של OAuth של Google.

Java

java/basic-app/src/main/java/com/google/chat/app/basic/App.java
String CHAT_ISSUER = "chat@system.gserviceaccount.com";
JsonFactory factory = JacksonFactory.getDefaultInstance();

GooglePublicKeysManager keyManagerBuilder =
    new GooglePublicKeysManager.Builder(new ApacheHttpTransport(), factory)
        .setPublicCertsEncodedUrl(
            "https://www.googleapis.com/service_accounts/v1/metadata/x509/" + CHAT_ISSUER)
        .build();

GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(keyManagerBuilder).setIssuer(CHAT_ISSUER).build();

GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
return idToken != null
    && verifier.verify(idToken)
    && idToken.verifyAudience(Collections.singletonList(AUDIENCE))
    && idToken.verifyIssuer(CHAT_ISSUER);

Python

python/basic-app/main.py
# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

try:
    # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    request = requests.Request()
    certs_url = 'https://www.googleapis.com/service_accounts/v1/metadata/x509/' + CHAT_ISSUER
    token = id_token.verify_token(bearer, request, AUDIENCE, certs_url)
    return token['iss'] == CHAT_ISSUER

except:
    return False

Node.js

node/basic-app/index.js
// Bearer Tokens received by apps will always specify this issuer.
const chatIssuer = 'chat@system.gserviceaccount.com';

// Verify valid token, signed by CHAT_ISSUER, intended for a third party.
try {
  const response = await fetch('https://www.googleapis.com/service_accounts/v1/metadata/x509/' + chatIssuer);
  const certs = await response.json();
  await client.verifySignedJwtWithCertsAsync(
    bearer, certs, audience, [chatIssuer]);
  return true;
} catch (unused) {
  return false;
}