אימות בקשות מ-Google Chat

בקטע הזה מוסבר איך לוודא שבקשות לנקודת הקצה (endpoint) מגיעות מ-Chat, אם מדובר באפליקציות ל-Google Chat שמבוססות על נקודות קצה (endpoints) של HTTP.

כדי לשלוח אירועי אינטראקציה לנקודת הקצה של אפליקציית Chat, ‏ Google שולחת בקשות לשירות שלכם. כדי לוודא שהבקשה מגיעה מ-Google, ‏ Chat כולל אסימון bearer בכותרת Authorization של כל בקשת HTTPS לנקודת הקצה. לדוגמה:

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

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

אם הטמעתם את אפליקציית Chat באמצעות Cloud Functions או Cloud Run, מערכת Cloud IAM מטפלת באימות האסימון באופן אוטומטי. פשוט צריך להוסיף את חשבון השירות של Google Chat כחשבון משתמש מורשה להפעלת פונקציות. אם האפליקציה שלכם מטמיעה שרת HTTP משלה, אתם יכולים לאמת את אסימון ה-bearer באמצעות ספריית לקוח של Google API בקוד פתוח:

אם לא ניתן לאמת את הטוקן באפליקציית Chat, השירות שלכם צריך להגיב לבקשה עם קוד תגובה מסוג HTTPS‏ 401 (Unauthorized).

אימות בקשות באמצעות Cloud Functions או Cloud Run

אם הלוגיקה של הפונקציה מיושמת באמצעות Cloud Functions או Cloud Run, צריך לבחור באפשרות כתובת URL של נקודת קצה מסוג HTTP בשדה קהל היעד לאימות של הגדרת החיבור של אפליקציית Chat. בנוסף, צריך לוודא שכתובת ה-URL של נקודת הקצה מסוג HTTP בתצורה תואמת לכתובת ה-URL של נקודת הקצה של Cloud Function או Cloud Run.

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

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

המסוף

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

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

    כניסה לדף Cloud Functions

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

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

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

    כניסה ל-Cloud Run

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

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

  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 באמצעות אסימון מזהה

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

זוהי שיטת האימות המומלצת אם נקודת הקצה של ה-HTTP לא מתארחת בשירות שתומך באימות מבוסס-IAM (כמו Cloud Functions או Cloud Run). בשיטה הזו, שירות ה-HTTP צריך מידע על כתובת ה-URL של נקודת הקצה שבה הוא פועל, אבל הוא לא צריך מידע על מספר פרויקט Cloud.

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

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 של מספר פרויקט

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

מומלץ להשתמש בשיטת האימות הזו רק אם אתם מעדיפים להשתמש במספר פרויקט Cloud כדי לאמת בקשות במקום בכתובת ה-URL של נקודת הקצה של HTTP. לדוגמה, אם רוצים לשנות את כתובת ה-URL של נקודת הקצה לאורך זמן תוך שמירה על אותו מספר פרויקט ב-Cloud, או אם רוצים להשתמש באותה נקודת קצה למספר פרויקטים ב-Cloud ולהשוות את השדה audience לרשימה של מספרי פרויקטים ב-Cloud.

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

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;
}