对于基于 HTTP 端点构建的 Google Chat 应用,本部分介绍了如何验证向端点发出的请求是否来自 Chat。
为了将互动事件发送到您的 Chat 应用的端点,Google 会向您的服务发出请求。为了验证请求是否来自 Google,Chat 会在向端点发送的每个 HTTPS 请求的 Authorization
标头中添加一个不记名令牌。例如:
POST
Host: yourappurl.com
Authorization: Bearer AbCdEf123456
Content-Type: application/json
User-Agent: Google-Dynamite
上述示例中的字符串 AbCdEf123456
是不记名授权令牌。这是 Google 生成的加密令牌。不记名令牌的类型和 audience
字段的值取决于您在配置 Chat 应用时选择的身份验证目标设备类型。
如果您是使用 Cloud Functions 或 Cloud Run 实现的 Chat 应用,则 Cloud IAM 会自动处理令牌验证。您只需将 Google Chat 服务帐号添加为已获授权的调用方即可。如果您的应用实现了自己的 HTTP 服务器,您可以使用开源 Google API 客户端库验证您的不记名令牌:
- Java:https://github.com/google/google-api-java-client
- Python:https://github.com/google/google-api-python-client
- Node.js:https://github.com/google/google-api-java-client
- .NET:https://github.com/google/google-api-dotnet-client
如果令牌没有针对 Chat 应用进行验证,您的服务应使用 HTTPS 响应代码 401 (Unauthorized)
响应请求。
使用 Cloud Functions 或 Cloud Run 对请求进行身份验证
如果您的函数逻辑是使用 Cloud Functions 或 Cloud Run 实现的,您必须在 Chat 应用连接设置的 Authentication Audience 字段中选择 App URL
,并确保配置中的应用网址与 Cloud Functions 函数或 Cloud Run 端点的网址对应。
然后,您需要授权 Google Chat 服务账号 chat@system.gserviceaccount.com
作为调用方。
以下步骤展示了如何使用 Cloud Functions (第 1 代):
控制台
将函数部署到 Google Cloud 后:
在 Google Cloud 控制台中,转到 Cloud Functions 页面:
在 Cloud Functions 列表中,点击接收函数旁边的复选框。(请勿点击函数本身。)
点击屏幕顶部的权限。此时权限面板会打开。
点击添加主账号。
在新的主账号字段中,输入
chat@system.gserviceaccount.com
。从选择角色下拉菜单中选择 Cloud Functions > Cloud Functions Invoker 角色。
点击保存。
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(第 2 代)或 Cloud Run 服务:
控制台
将您的函数或服务部署到 Google Cloud 后:
在 Google Cloud 控制台中,前往 Cloud Run 页面:
在 Cloud Run 服务列表中,点击接收函数旁边的复选框。(请勿点击函数本身。)
点击屏幕顶部的权限。此时权限面板会打开。
点击添加主账号。
在新的主账号字段中,输入
chat@system.gserviceaccount.com
。从选择角色下拉菜单中选择 Cloud Run > Cloud Run Invoker 角色。
点击保存。
gcloud
使用 gcloud functions add-invoker-policy-binding
命令:
gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
--member='serviceAccount:chat@system.gserviceaccount.com'
将 RECEIVING_FUNCTION
替换为 Chat 应用的函数名称。
使用应用网址 ID 令牌对请求进行身份验证
如果 Chat 应用连接设置的 Authentication Audience 字段设置为 App URL
,则请求中的不记名授权令牌是由 Google 签名的 OpenID Connect (OIDC) ID 令牌。email
字段设置为 chat@system.gserviceaccount.com
。audience
字段设置为您配置 Google Chat 以向您的 Chat 应用发送请求的网址。例如,如果您的 Chat 应用的已配置端点为 https://example.com/app/
,则 ID 令牌中的 audience
字段为 https://example.com/app/
。
以下示例展示了如何使用 Google OAuth 客户端库验证不记名令牌是否由 Google Chat 颁发,以及是否以您的应用为目标。
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 对请求进行身份验证
如果 Chat 应用连接设置的 Authentication Audience 字段设置为 Project
Number
,则请求中的不记名授权令牌是由 chat@system.gserviceaccount.com
颁发和签名的自签名 JSON Web 令牌 (JWT)。audience
字段设置为您用于构建 Chat 应用的 Google Cloud 项目编号。例如,如果您的 Chat 应用的 Cloud 项目编号为 1234567890
,则 JWT 中的 audience
字段为 1234567890
。
以下示例展示了如何使用 Google OAuth 客户端库验证不记名令牌是否由 Google Chat 发放,以及是否针对您的项目。
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();
相关主题
- 如需简要了解 Google Workspace 中的身份验证和授权,请参阅了解身份验证和授权。
- 如需大致了解 Chat 中的身份验证和授权,请参阅身份验证概览。
- 使用用户凭据或服务帐号设置身份验证和授权。