本页面介绍了如何使用签名的 IAP 标头保护应用的安全。配置 Identity-Aware Proxy (IAP) 后,该服务会使用 JSON Web 令牌 (JWT) 来确保向应用发出的请求已获得授权。这样可以保护您的应用远离以下种类的风险:
- IAP 被意外停用;
- 防火墙配置有误;
- 来自项目内部的访问。
要妥善保护应用的安全,您必须对所有应用类型使用签名标头。
或者,如果您有 App Engine 标准环境应用,则可以使用 Users API。
请注意,Compute Engine 和 GKE 健康检查不包含 JWT 标头,而 IAP 不会处理健康检查。如果您的健康检查返回访问错误,请确保已在 Google Cloud 控制台中对其进行了正确配置,并且 JWT 标头验证将健康检查路径列入了许可名单。如需了解详情,请参阅创建健康检查例外。
准备工作
要使用签名标头保护应用的安全,您需要做好以下准备:
- 希望用户连接的应用。
- 支持
ES256
算法的与您所用语言对应的第三方 JWT 库。
使用 IAP 标头保护应用的安全
为了使用 IAP JWT 保护您的应用,请验证 JWT 的标头、载荷和签名。JWT 在 HTTP 请求标头 x-goog-iap-jwt-assertion
中。如果攻击者绕过 IAP,他们便能够伪造没有签名的 IAP 身份标头 x-goog-authenticated-user-{email,id}
。JWT 可提供一种更加安全的替代解决方案。
当有人绕过 IAP 时,签名标头可提供额外的安全保护。请注意,IAP 启用后,它会在请求通过 IAP 服务基础架构时删除客户端提供的 x-goog-*
标头。
验证 JWT 标头
验证 JWT 的标头是否符合以下规定:
JWT 标头声明 | ||
---|---|---|
alg |
算法 | ES256 |
kid |
密钥 ID | 必须与 IAP 密钥文件中列出的公钥之一相对应,并以两种不同的格式提供:https://www.gstatic.com/iap/verify/public_key 和 https://www.gstatic.com/iap/verify/public_key-jwk |
请确保 JWT 已由与令牌的 kid
声明相对应的私钥签名。为此,请首先从以下任一位置中获取公钥:
https://www.gstatic.com/iap/verify/public_key
。该网址包含一个 JSON 字典,该字典将kid
声明映射到公钥值。https://www.gstatic.com/iap/verify/public_key-jwk
。该网址包含 JWK 格式的 IAP 公钥。
获取公钥后,请使用 JWT 库来验证签名。
验证 JWT 载荷
验证 JWT 的载荷是否符合以下规定:
JWT 负载声明 | ||
---|---|---|
exp |
到期时间 | 必须是将来的时间。时间从 UNIX 计时原点开始计算,以秒为单位。允许 30 秒偏差。 令牌的最长生命周期为 10 分钟 + 2 * 偏差。 |
iat |
颁发时间 | 必须是过去的时间。时间从 UNIX 计时原点开始计算,以秒为单位。允许 30 秒偏差。 |
aud |
受众 |
必须是一个含有以下值的字符串:
|
iss |
颁发者 | 必须为 https://cloud.google.com/iap 。
|
hd |
账号网域 | 如果账号属于一个托管网域,则会提供 hd 声明来区分与该账号关联的网域。 |
google |
Google 声明 |
如果一个或多个访问权限级别适用于请求,则这些级别的名称将以字符串数组形式存储在 google 声明 JSON 对象的 access_levels 键下。
指定设备政策且组织有权访问设备数据时, |
要获取上述 aud
字符串的值,您可以访问 Google Cloud 控制台,也可以使用 gcloud 命令行工具。
如需通过 Google Cloud 控制台获取 aud
字符串值,请前往项目的 Identity-Aware Proxy 设置,点击负载平衡器资源旁边的更多,然后选择签名标头 JWT 目标设备。出现的签名标头 JWT对话框显示所选资源的 aud
声明。
如果您想使用 gcloud CLI gcloud 命令行工具来获取 aud
字符串值,则需要知道项目 ID。您可以在 Google Cloud 控制台的项目信息卡片上找到项目 ID,然后为每个值运行以下指定的命令。
项目编号
要使用 gcloud 命令行工具获取项目编号,请运行以下命令:
gcloud projects describe PROJECT_ID
该命令返回如下输出:
createTime: '2016-10-13T16:44:28.170Z' lifecycleState: ACTIVE name: project_name parent: id: '433637338589' type: organization projectId: PROJECT_ID projectNumber: 'PROJECT_NUMBER'
服务 ID
要使用 gcloud 命令行工具获取服务 ID,请运行以下命令:
gcloud compute backend-services describe SERVICE_NAME --project=PROJECT_ID --global
该命令会返回如下输出:
affinityCookieTtlSec: 0 backends: - balancingMode: UTILIZATION capacityScaler: 1.0 group: https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/instanceGroups/my-group connectionDraining: drainingTimeoutSec: 0 creationTimestamp: '2017-04-03T14:01:35.687-07:00' description: '' enableCDN: false fingerprint: zaOnO4k56Cw= healthChecks: - https://www.googleapis.com/compute/v1/projects/project_name/global/httpsHealthChecks/my-hc id: 'SERVICE_ID' kind: compute#backendService loadBalancingScheme: EXTERNAL name: my-service port: 8443 portName: https protocol: HTTPS selfLink: https://www.googleapis.com/compute/v1/projects/project_name/global/backendServices/my-service sessionAffinity: NONE timeoutSec: 3610
检索用户身份
如果以上所有验证均成功通过,请检索用户身份。 ID 令牌的载荷包含以下用户信息:
ID 令牌载荷用户身份 | ||
---|---|---|
sub |
主题 |
用户的唯一稳定标识符。请使用此值来代替 x-goog-authenticated-user-id 标头。 |
email |
用户电子邮件地址 | 用户电子邮件地址。
|
以下是使用签名的 IAP 标头保护应用的部分示例代码:
C#
Go
Java
Node.js
PHP
Python
Ruby
测试您的验证代码
如果您使用 secure_token_test
查询参数访问应用,则 IAP 将包含无效的 JWT。使用此方法可以确保 JWT 验证逻辑处理各种失败情况,并了解您的应用在收到无效 JWT 时的行为方式。
创建健康检查例外
如前所述,Compute Engine 和 GKE 健康检查不使用 JWT 标头,而 IAP 不会处理健康检查。要允许使用此类健康检查,您需要对健康检查和应用进行配置。
配置健康检查
如果您尚未设置健康检查路径,请使用 Google Cloud 控制台为健康检查设置一条非敏感路径。确保此路径未分享给其他任何资源。
- 前往 Google Cloud 控制台中的健康检查页面。
转到“健康检查”页面 - 点击您要在应用中使用的健康检查,然后点击修改。
- 在请求路径下,添加非敏感路径的名称。这用于指定在发送健康检查请求时 Google Cloud 使用的网址路径。如果省略,则健康检查请求将发送到
/
。 - 点击保存。
配置 JWT 验证
在调用 JWT 验证例程的代码中,添加一个条件,以便让系统返回 200 HTTP 状态来响应您的健康检查请求路径。例如:
if HttpRequest.path_info = '/HEALTH_CHECK_REQUEST_PATH' return HttpResponse(status=200) else VALIDATION_FUNCTION
外部身份的 JWT
如果您将 IAP 与外部身份一起使用,则 IAP 仍然会针对每个经过身份验证的请求发出已签名的 JWT,就像处理 Google 身份一样。不过,也有一些区别。
提供商信息
当使用外部身份时,JWT 有效载荷将包含一个名为 gcip
的声明。此声明包含有关用户的信息,例如他们的电子邮件地址和照片网址,以及任何其他提供商特定的属性。
以下是使用 Facebook 登录的用户的 JWT 示例:
"gcip": '{
"auth_time": 1553219869,
"email": "facebook_user@gmail.com",
"email_verified": false,
"firebase": {
"identities": {
"email": [
"facebook_user@gmail.com"
],
"facebook.com": [
"1234567890"
]
},
"sign_in_provider": "facebook.com",
},
"name": "Facebook User",
"picture: "https://graph.facebook.com/1234567890/picture",
"sub": "gZG0yELPypZElTmAT9I55prjHg63"
}',
email
和 sub
字段
如果用户已通过 Identity Platform 认证,则 JWT 的 email
和 sub
字段将以 Identity Platform 令牌颁发者和所使用的租户 ID(如果有)为前缀。例如:
"email": "securetoken.google.com/PROJECT-ID/TENANT-ID:demo_user@gmail.com", "sub": "securetoken.google.com/PROJECT-ID/TENANT-ID:gZG0yELPypZElTmAT9I55prjHg63"
用 sign_in_attributes
控制访问权限
外部身份不支持 IAM ,但您可以改为使用嵌入在 sign_in_attributes
字段中的声明来控制访问权限。例如,考虑使用 SAML 提供商登录的用户:
{
"aud": "/projects/project_number/apps/my_project_id",
"gcip": '{
"auth_time": 1553219869,
"email": "demo_user@gmail.com",
"email_verified": true,
"firebase": {
"identities": {
"email": [
"demo_user@gmail.com"
],
"saml.myProvider": [
"demo_user@gmail.com"
]
},
"sign_in_attributes": {
"firstname": "John",
"group": "test group",
"role": "admin",
"lastname": "Doe"
},
"sign_in_provider": "saml.myProvider",
"tenant": "my_tenant_id"
},
"sub": "gZG0yELPypZElTmAT9I55prjHg63"
}',
"email": "securetoken.google.com/my_project_id/my_tenant_id:demo_user@gmail.com",
"exp": 1553220470,
"iat": 1553219870,
"iss": "https://cloud.google.com/iap",
"sub": "securetoken.google.com/my_project_id/my_tenant_id:gZG0yELPypZElTmAT9I55prjHg63"
}
您可以向您的应用添加类似于以下代码的逻辑,以限制具有有效角色的用户的访问:
const gcipClaims = JSON.parse(decodedIapJwtClaims.gcip);
if (gcipClaims &&
gcipClaims.firebase &&
gcipClaims.firebase.sign_in_attributes &&
gcipClaims.firebase.sign_in_attribute.role === 'admin') {
// Allow access to admin restricted resource.
} else {
// Block access.
}
可以使用 gcipClaims.gcip.firebase.sign_in_attributes
嵌套声明访问 Identity Platform SAML 和 OIDC 提供商的其他用户属性。
IdP 声明的大小限制
用户使用 Identity Platform 登录后,额外的用户属性将传播到无状态的 Identity Platform ID 令牌载荷,这些载荷将安全地传递给 IAP。IAP 随后会发出自己的无状态不透明 Cookie,其中也包含相同的声明。IAP 将根据 Cookie 内容生成已签名的 JWT 标头。
因此,如果发起的会话包含大量声明,则它可能会超过允许的 Cookie 大小上限(在大多数浏览器中,此大小通常约为 4KB)。这将导致登录操作失败。
您应该确保仅将必要的声明传播到 IdP SAML 或 OIDC 属性中。另一种方法是使用屏蔽函数过滤掉授权检查不需要的声明。
const gcipCloudFunctions = require('gcip-cloud-functions');
const authFunctions = new gcipCloudFunctions.Auth().functions();
// This function runs before any sign-in operation.
exports.beforeSignIn = authFunctions.beforeSignInHandler((user, context) => {
if (context.credential &&
context.credential.providerId === 'saml.my-provider') {
// Get the original claims.
const claims = context.credential.claims;
// Define this function to filter out the unnecessary claims.
claims.groups = keepNeededClaims(claims.groups);
// Return only the needed claims. The claims will be propagated to the token
// payload.
return {
sessionClaims: claims,
};
}
});