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

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

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

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

สตริง AbCdEf123456 ในตัวอย่างก่อนหน้าคือโทเค็นการให้สิทธิ์ Bearer โทเค็นการเข้ารหัสนี้ลงนามโดยบัญชีบริการที่ไม่ซ้ำกันต่อโปรเจ็กต์ของแอป Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com) และฟิลด์ audience จะตั้งค่าเป็น URL ปลายทาง HTTP ที่กำหนดค่าไว้สำหรับแอป Chat เมื่อกำหนดค่าแอป Chat

คุณสามารถคัดลอกอีเมลบัญชีบริการของแอป Chat ได้จากส่วนการตั้งค่าการเชื่อมต่อของแท็บการกำหนดค่า Chat API ในคอนโซล Google Cloud โดยทำดังนี้

  1. ในคอนโซล Google Cloud ให้ไปที่เมนู > API และบริการ > API และบริการที่เปิดใช้ > Google Chat API > การกำหนดค่า

    ไปที่การกำหนดค่า Google Chat API

  2. ในส่วนฟีเจอร์แบบอินเทอร์แอกทีฟ > การตั้งค่าการเชื่อมต่อ ให้คัดลอก อีเมลบัญชีบริการ (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com)

หากคุณได้ติดตั้งใช้งานแอป Chat โดยใช้ฟังก์ชัน Cloud Run หรือ Cloud Run แล้ว Cloud IAM จะจัดการการยืนยันโทเค็นโดยอัตโนมัติเมื่อคุณให้สิทธิ์บัญชีบริการของแอป Chat ในบทบาท Cloud Run Invoker (roles/run.invoker) หากแอปใช้เซิร์ฟเวอร์ HTTP ของตัวเอง คุณสามารถยืนยันโทเค็นผู้ถือ โดยใช้ไลบรารีของไคลเอ็นต์ Google API แบบโอเพนซอร์สได้ดังนี้

หากโทเค็นไม่ได้รับการยืนยันสำหรับแอป Chat บริการของคุณควรตอบกลับคำขอด้วยรหัสการตอบกลับ HTTPS 401 (Unauthorized)

ตรวจสอบสิทธิ์คำขอโดยใช้ฟังก์ชัน Cloud Run

หากใช้ตรรกะฟังก์ชันโดยใช้ฟังก์ชัน Cloud Run หรือ Cloud Run โปรดตรวจสอบว่า URL ของปลายทาง HTTP ที่กำหนดค่าไว้ในส่วนทริกเกอร์ใน แอป Chat การตั้งค่าการเชื่อมต่อสอดคล้องกับ URL ของปลายทางฟังก์ชัน Cloud Run

จากนั้นให้สิทธิ์บัญชีบริการของแอป Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com, คัดลอกจากส่วนการตั้งค่าการเชื่อมต่อของ Chat API แท็บการกำหนดค่า) เป็นผู้เรียกใช้โดยทำตามขั้นตอนต่อไปนี้

คอนโซล

หลังจากติดตั้งใช้งานฟังก์ชันหรือบริการใน Google Cloud แล้ว ให้ทำดังนี้

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

    ไปที่ Cloud Run

  2. ในรายการบริการ Cloud Run ให้คลิกช่องทําเครื่องหมายข้างฟังก์ชันการรับ (อย่าคลิกฟังก์ชันนั้นโดยตรง)

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

  4. คลิกเพิ่มหลักการ

  5. ในช่องผู้ใช้หลักรายใหม่ ให้ป้อนอีเมลบัญชีบริการของแอป Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com)

  6. จากเมนูเลือกบทบาท ให้เลือกบทบาท Cloud Run

    Cloud Run Invoker

  7. คลิกบันทึก

gcloud

ใช้คำสั่ง gcloud functions add-invoker-policy-binding

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:service-PROJECT_NUMBER@gcp-sa-gsuiteaddons.iam.gserviceaccount.com'

แทนที่ค่าต่อไปนี้

  • RECEIVING_FUNCTION: ชื่อฟังก์ชันของแอป Chat
  • PROJECT_NUMBER: หมายเลขโปรเจ็กต์จากอีเมลบัญชีบริการของแอป Chat

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

สำหรับปลายทาง HTTP โทเค็นการให้สิทธิ์แบบ Bearer ในคำขอคือ โทเค็น OpenID Connect (OIDC) ที่ Google ลงนาม โทเค็นรหัส ฟิลด์ email จะตั้งค่าเป็นอีเมลบัญชีบริการของแอป Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com) และฟิลด์ audience จะตั้งค่าเป็น URL ปลายทาง HTTP ที่กำหนดค่าไว้เพื่อรับคำขอ เช่น หากปลายทางที่กำหนดค่าของแอป Chat คือ https://example.com/app/ ฟิลด์ audience ในโทเค็นรหัสจะเป็น https://example.com/app/

นี่คือวิธีการตรวจสอบสิทธิ์ที่แนะนำหากไม่ได้โฮสต์ปลายทาง HTTP ในบริการที่รองรับการตรวจสอบสิทธิ์ตาม IAM (เช่น Cloud Run)

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

Java

java/chat/secured-app/src/main/java/com/google/chat/app/secured/App.java
/**
 * Determine whether a Google Workspace add-on request is legitimate.
 * 
 * @param event Event sent from Google Workspace add-on
 * @param authorization Authorization header from the request
 * @return {boolean} Whether the request is legitimate
 */
private boolean verifyAddOnRequest(JsonNode event, String authorization) throws Exception {
  JsonFactory factory = JacksonFactory.getDefaultInstance();

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

  String bearer = authorization.substring("Bearer ".length(), authorization.length());
  GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
  return idToken != null
    && verifier.verify(idToken)
    && idToken.getPayload().getEmailVerified()
    && idToken.getPayload().getEmail().equals(SERVICE_ACCOUNT_EMAIL);
}

Python

python/chat/secured-app/main.py
def verifyAddOnRequest() -> bool:
  """Determine whether a Google Workspace add-on request is legitimate.

  Args:
    request: Request sent from Google Workspace add-on

  Returns:
    Whether the request is legitimate
  """
  try:
    bearer = request.headers.get('Authorization')[len("Bearer "):]
    token = id_token.verify_oauth2_token(bearer, requests.Request(), HTTP_ENDPOINT)
    return token['email'] == SERVICE_ACCOUNT_EMAIL

  except:
    return False

Node.js

node/chat/secured-app/index.js
/**
 * Determine whether a Google Workspace add-on request is legitimate.
 * 
 * @param {Object} req Request sent from Google Workspace add-on
 * @return {boolean} Whether the request is legitimate
 */
async function verifyAddOnRequest(req) {
  try {
    const authorization = req.headers.authorization;
    const idToken = authorization.substring('Bearer '.length, authorization.length);
    const ticket = await new OAuth2Client().verifyIdToken({idToken, audience: HTTP_ENDPOINT});
    return ticket.getPayload().email_verified
        && ticket.getPayload().email === SERVICE_ACCOUNT_EMAIL;
  } catch (unused) {
    return false;
  }
}

แอป Chat ที่ไม่ใช่ส่วนเสริม: ยืนยันคำขอจาก Google Chat

เอกสารต่อไปนี้ใช้กับแอป Chat ที่ไม่ใช่ส่วนเสริมของ Google Workspace หากต้องการย้ายข้อมูลแอป Chat ที่ไม่ใช่ส่วนเสริม โปรดดูหัวข้อแปลงแอป Google Chat เป็นส่วนเสริม Google Workspace

สำหรับแอปแชทที่ไม่ใช่ส่วนเสริมที่กำหนดค่าด้วยURL ของปลายทาง HTTP ในส่วนการตั้งค่าการเชื่อมต่อ ประเภทของโทเค็นสำหรับผู้ถือและค่าของฟิลด์ audience จะขึ้นอยู่กับประเภทของกลุ่มเป้าหมายการตรวจสอบสิทธิ์ที่คุณเลือกเมื่อกำหนดค่าแอปแชท และคำขอจะได้รับการลงนามโดยบัญชีบริการที่ใช้ร่วมกัน chat@system.gserviceaccount.com

ตรวจสอบสิทธิ์คำขอโดยใช้ฟังก์ชัน Cloud Run (แอป Chat ที่ไม่ใช่ส่วนเสริม)

หากใช้ตรรกะฟังก์ชันโดยใช้ฟังก์ชัน Cloud Run คุณต้องเลือก URL ของปลายทาง HTTP ในช่องกลุ่มเป้าหมายการตรวจสอบสิทธิ์ของ การตั้งค่าการเชื่อมต่อแอป Chat และตรวจสอบว่า URL ของปลายทาง HTTP ในการกำหนดค่าตรงกับ URL ของปลายทางฟังก์ชัน Cloud Run

จากนั้นคุณต้องให้สิทธิ์บัญชีบริการของ Google Chat chat@system.gserviceaccount.com เป็นผู้เรียกใช้โดยทำตามขั้นตอนต่อไปนี้

คอนโซล

หลังจากติดตั้งใช้งานฟังก์ชันหรือบริการใน Google Cloud แล้ว ให้ทำดังนี้

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

    ไปที่ Cloud Run

  2. ในรายการบริการ Cloud Run ให้คลิกช่องทําเครื่องหมายข้างฟังก์ชันการรับ (อย่าคลิกฟังก์ชันนั้นโดยตรง)

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

  4. คลิกเพิ่มหลักการ

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

  6. จากเมนูเลือกบทบาท ให้เลือกบทบาท 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 ที่ไม่ใช่ส่วนเสริม)

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

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

ตัวอย่างต่อไปนี้แสดงวิธียืนยันว่าโทเค็นผู้ถือสิทธิ์ออกโดย 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;
}