使用代理的自有身份向外部服务进行身份验证

托管在 Google Cloud 上的代理可以使用自己的身份向托管在 Google Cloud 运行时(例如 Cloud Run 或 Google Kubernetes Engine (GKE))上的工具和服务进行身份验证,方法是从代理身份请求 OpenID Connect (OIDC) ID 令牌。代理还可以使用这些 ID 令牌向第三方云平台(例如 Amazon Web Services (AWS) 和 Microsoft Azure)、自定义 API、API 网关和本地后端进行身份验证。

当代理自行授权访问外部服务时,代理身份会签发 OpenID Connect (OIDC) ID 令牌。此 JSON Web 令牌 (JWT) 断言了代理的 SPIFFE 身份,并由代理的信任网域(受管理的工作负载身份池)的签发者密钥签名。外部系统可以通过 Google Cloud Security Token Service 托管的公共端点,在没有 Google Cloud 凭据或 SDK 的情况下验证这些令牌:

  • 一个 OpenID Connect Discovery 1.0 端点 (/.well-known/openid-configuration),用于发布 OpenID 提供方元数据和公钥端点 (jwks_uri)。
  • 一个 JSON Web 密钥集 (JWKS) 端点 (/openid/jwks),用于提供用于验证代理 ID 令牌上签名的有效公钥。

准备工作

  1. 确认您已选择正确的身份验证方法。 查看代理身份概览,了解 SPIFFE 身份、信任网域和代理凭据如何运作。
  2. 创建并部署启用了代理身份的代理。
  3. 确保您的外部服务或身份提供方满足以下要求:
  4. 确定您的代理和目标外部服务的以下配置值:
    • 颁发者网址(iss 声明):您组织 (https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/TRUST_DOMAIN) 或项目 (https://sts.googleapis.com/v1/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/TRUST_DOMAIN) 的工作负载身份池颁发者网址。
    • 允许的受众群体(aud 声明):外部服务或身份提供方在验证 ID 令牌时预期的受众群体 URI。
  5. 验证您是否拥有完成此任务所需的角色。

所需的角色

如需获得使用代理身份部署代理所需的权限,请让管理员向您授予项目的以下 IAM 角色:

  • 将代理部署到 Gemini Enterprise Agent Platform 上的代理运行时: Vertex AI User (roles/aiplatform.user)
  • 将代理服务部署到 Cloud Run: Cloud Run Admin (roles/run.admin)

如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限。

这些预定义角色包含使用 Agent Identity 部署代理所需的权限。如需查看所需的确切权限,请展开所需权限部分:

所需权限

如需部署具有 Agent Identity 的代理,您需要具备以下权限:

  • 将智能体部署到 Gemini Enterprise Agent Platform 上的 Agent Runtime:
    • aiplatform.reasoningEngines.create
    • aiplatform.reasoningEngines.update
  • 将代理服务部署到 Cloud Run:
    • run.services.create
    • run.services.update

您也可以使用自定义角色或其他预定义角色来获取这些权限。

获取代理的 OIDC ID 令牌

如需配置代理以获取 OIDC ID 令牌并将其发送到外部服务,请完成以下任务:

  1. 使用 Agent Identity 配置智能体
  2. 在应用代码中请求 OIDC ID 令牌

使用代理身份配置智能体

在部署代理时启用 Agent Identity:

  • 如果您将代理部署到 Gemini Enterprise Agent Platform 上的 Agent Runtime,请将 identity_type 设置为 AGENT_IDENTITY:

    remote_app = client.agent_engines.create(
        agent=app,
        config={
            "identity_type": types.IdentityType.AGENT_IDENTITY,
            "requirements": ["google-cloud-aiplatform[agent_engines,adk]"],
        },
    )
    
  • 如果您将容器化代理服务部署到 Cloud Run,请传递 --identity-type=agent-identity 标志:

    gcloud run deploy SERVICE_NAME \
        --image=IMAGE_URL \
        --identity-type=agent-identity \
        --no-allow-unauthenticated

    替换以下内容:

    • SERVICE_NAME:Cloud Run 服务的名称。
    • IMAGE_URL:代理的容器映像网址。

在应用代码中请求 OIDC ID 令牌

在代理的应用代码中,使用 Google Auth 客户端库为目标外部受众群体请求 OIDC ID 令牌。客户端库负责处理令牌生成、本地缓存和从元数据服务器自动续订。

默认情况下,为外部受众群体签发的 OIDC ID 令牌不会绑定到运行时证书。

以下示例使用 google-auth 库请求 OIDC ID 令牌,并将其作为 Bearer 令牌附加到出站请求中:

Python

from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import id_token

# 1. Specify the audience expected by the external receiver
# (for example, AWS Bedrock AgentCore or your external service URL).
target_audience = "https://EXTERNAL_SERVICE_AUDIENCE"

# 2. Create ID token credentials and an AuthorizedSession, which handles
# local token caching, automatic renewal before expiry, and the Bearer header.
credentials = id_token.fetch_id_token_credentials(audience=target_audience)
authed_session = AuthorizedSession(credentials)

# 3. Send the authenticated request to the external service.
response = authed_session.post(
    "https://EXTERNAL_SERVICE_ENDPOINT",
    json={"prompt": "Hello from Agent"},
)

替换以下内容:

  • EXTERNAL_SERVICE_AUDIENCE:接收服务预期接收的目标对象 URI(例如 bedrock.us-east-1.amazonaws.com 或 api.example.com)。
  • EXTERNAL_SERVICE_ENDPOINT:代理调用的外部 API 或后端端点的网址。

如需查看其他编程语言(包括 Go、Node.js 和 Java)的客户端库说明和示例,请参阅获取 ID 令牌。这些语言的当前客户端库版本支持 --identity-type=agent-identity,但默认情况下不使用绑定令牌。

验证 Agent Identity ID 令牌

当外部服务从代理收到 OIDC ID 令牌时,请根据目标服务使用以下任一方法验证令牌:

使用内置的工作负载身份联合

如果您的接收服务在支持内置 IAM 身份验证或 OIDC 工作负载身份联合的云平台上运行,则无需编写自定义令牌验证代码:

  • Cloud Run:如果接收服务在 Cloud Run 上运行,且入站流量经过身份验证 (--no-allow-unauthenticated),Cloud Run 会在入站流量层验证传入的 Agent Identity 令牌。向调用代理授予接收服务的 Cloud Run Invoker (roles/run.invoker) 角色。 如需了解详情,请参阅向 Cloud Run 上的 MCP 服务器进行身份验证。

    如果您的服务允许未经身份验证的入站流量,并在应用代码中验证令牌,请参阅以编程方式验证令牌。

  • Amazon Bedrock:通过指定Google Cloud 安全令牌服务发现网址或发布者网址以及预期受众群体,配置入站 JWT 身份验证。如需查看相关说明,请参阅 AWS 文档中的配置入站 JWT 授权方。

  • Microsoft Entra ID:配置其他颁发者场景下的联合身份凭据。指定 Google Cloud Security Token Service 发布者网址、预期受众群体和主体标识符(sub 声明)。 如需了解相关说明,请参阅 Microsoft Learn 文档中的在应用与外部身份提供方之间创建信任关系。

以编程方式验证令牌

如果您的代理向自定义 API、微服务、API 网关或本地工作负载发送请求,则接收服务必须先验证传入的 OIDC ID 令牌,然后才能授予访问权限。您的代理通常会在 Authorization: Bearer TOKEN HTTP 标头中传递此令牌。

如需以编程方式验证传入的 ID 令牌,请完成以下任务:

  1. 提取并验证令牌颁发者网址
  2. 发现并缓存公开签名密钥
  3. 验证令牌签名和声明
  4. 授权代理的 SPIFFE 身份

提取并验证令牌签发者网址

当传入请求到达时,读取未验证的 JWT 载荷以提取 iss(签发者)声明。此声明包含代理的信任域的工作负载身份池的网址。此网址用作发现文档和公开签名密钥的基准网址。

在发出任何出站网络请求之前,请验证 iss 声明是否与组织或项目的预期 Google Cloud Security Token Service 工作负载身份池网址相符:

  • 组织级信任网域:

    https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/TRUST_DOMAIN

    例如,对于 ID 为 123456789012 的组织,TRUST_DOMAIN 为 agents.global.org-123456789012.system.id.goog。

  • 项目级信任网域(适用于没有组织的网域):

    https://sts.googleapis.com/v1/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/TRUST_DOMAIN

    例如,对于编号为 9876543210 的项目,TRUST_DOMAIN 为 agents.global.proj-9876543210.system.id.goog。

发现并缓存公开签名密钥

验证发布者网址后,从 Google Cloud 安全令牌服务中检索并缓存公共签名密钥:

  1. 查询 OpenID Connect 发现端点:将 /.well-known/openid-configuration 附加到基础颁发者网址,然后发送未经身份验证的 HTTP GET 请求:

    在使用任何请求数据之前,请先进行以下替换:

    • ORGANIZATION_ID:您的 Google Cloud组织 ID。对于没有组织的项目,请将 organizations/ORGANIZATION_ID 替换为 projects/PROJECT_NUMBER。
    • TRUST_DOMAIN:代理的信任域的工作负载身份池 ID(例如 agents.global.org-123456789012.system.id.goog)。

    HTTP 方法和网址:

    GET https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/TRUST_DOMAIN/.well-known/openid-configuration

    如需发送您的请求,请展开以下选项之一:

    如果请求成功,则会返回 HTTP 200 OK 状态和一个包含 OpenID 提供方元数据(包括 jwks_uri 字段)的 JSON 对象:

    {
      "issuer": "https://sts.googleapis.com/v1/organizations/123456789012/locations/global/workloadIdentityPools/agents.global.org-123456789012.system.id.goog",
      "jwks_uri": "https://sts.googleapis.com/v1/organizations/123456789012/locations/global/workloadIdentityPools/agents.global.org-123456789012.system.id.goog/openid/jwks",
      "authorization_endpoint": "https://sts.googleapis.com/v1/organizations/123456789012/locations/global/workloadIdentityPools/agents.global.org-123456789012.system.id.goog/authorize",
      "token_endpoint": "https://sts.googleapis.com/v1/organizations/123456789012/locations/global/workloadIdentityPools/agents.global.org-123456789012.system.id.goog/token",
      "response_types_supported": [
        "id_token"
      ],
      "subject_types_supported": [
        "public"
      ],
      "id_token_signing_alg_values_supported": [
        "RS256"
      ]
    }
    
  2. 查询 JSON Web 密钥集 (JWKS) 端点:向 OpenID 提供方元数据中返回的 jwks_uri 网址发送未经身份验证的 HTTP GET 请求:

    在使用任何请求数据之前,请先进行以下替换:

    • ORGANIZATION_ID:您的 Google Cloud组织 ID。对于没有组织的项目,请将 organizations/ORGANIZATION_ID 替换为 projects/PROJECT_NUMBER。
    • TRUST_DOMAIN:代理的信任域的工作负载身份池 ID(例如 agents.global.org-123456789012.system.id.goog)。

    HTTP 方法和网址:

    GET https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/TRUST_DOMAIN/openid/jwks

    如需发送您的请求,请展开以下选项之一:

    如果请求成功,系统会返回 HTTP 200 OK 状态和一个 JSON 对象,其中包含一个根据 RFC 7517 格式设置的公钥数组:

    {
      "keys": [
        {
          "kty": "RSA",
          "use": "sig",
          "alg": "RS256",
          "kid": "4d1933f8e6c4e0b512c140989f6655c68997...",
          "n": "uQn4zN_1mQ0VpGv82-Wp3w...",
          "e": "AQAB"
        }
      ]
    }
    
  3. 缓存发现文档和密钥:来自 OpenID Connect 发现端点和 JWKS 端点的响应均包含以下 HTTP 缓存标头:

    Cache-Control: public, max-age=86400, must-revalidate
    

    将发现文档和 JWKS 缓存长达 24 小时(86400 秒),以提高验证性能并避免速率限制。

    Google Cloud 会定期轮替工作负载身份池的私有和公共签名密钥。如果验证方收到的传入令牌包含不在其本地密钥缓存中的 kid(密钥 ID),请先从 /openid/jwks 端点提取新的 JWKS,然后再拒绝该令牌。

    如果您在查询发现或 JWKS 端点时遇到 HTTP 错误,请参阅排查 Agent Identity 身份验证问题。

验证令牌签名和声明

如需以加密方式验证令牌签名并验证 JWT 声明,请使用标准 OIDC 或 JWT 验证库(例如 Google Tink),并执行以下操作:

  1. 签名:在缓存的 JWKS 中查找与 JWT 标头中的 kid(密钥 ID)匹配的公钥。使用 alg 字段 (RS256) 中指定的算法验证签名。为了实现向前兼容,请动态检查 JWKS 中的 alg 和 kty 字段,而不是对算法类型进行硬编码。
  2. 颁发者 (iss):确认 iss 声明与信任网域的受信任Google Cloud 工作负载身份池颁发者网址一致。
  3. 目标对象 (aud):确认 aud 声明与您服务配置的目标对象标识符一致。
  4. 签发时间 (iat) 和到期时间 (exp):验证 iat 声明是否在过去,以及当前时间是否早于 exp 声明(允许存在较小的时钟偏差容差,例如 1 到 2 分钟)。

以下示例使用 Google Tink (tink.jwt) 针对 JWKS JSON 载荷验证 Agent Identity ID 令牌:

Python

import tink
from tink import jwt

# Initialize Tink JWT signature primitives (call once at application startup).
jwt.register_jwt_signature()


def verify_agent_identity_token(
    token: str,
    jwks_json: str,
    expected_issuer: str,
    expected_audience: str,
) -> jwt.VerifiedJwt:
    """Verifies an Agent Identity JWT against a JWKS JSON string using Tink.

    Args:
        token: The compact serialized JWT string.
        jwks_json: The JWKS JSON string fetched from the STS pool endpoint.
        expected_issuer: The expected token issuer ('iss' claim).
        expected_audience: The expected token audience ('aud' claim).

    Returns:
        jwt.VerifiedJwt: The verified JWT claims object.

    Raises:
        tink.TinkError: If the JWKS cannot be parsed, the key is not found,
            or token validation (signature, issuer, audience, expiration) fails.
    """
    # 1. Convert the JWKS JSON into a Tink public KeysetHandle.
    keyset_handle = jwt.jwk_set_to_public_keyset_handle(jwks_json)

    # 2. Instantiate the Tink JwtPublicKeyVerify primitive.
    jwt_verifier = keyset_handle.primitive(jwt.JwtPublicKeyVerify)

    # 3. Configure expected validation rules (issuer, audience, expiration).
    # Google Cloud STS sets 'typ': 'JWT' in the header, so
    # expected_type_header="JWT" is required.
    validator = jwt.new_validator(
        expected_issuer=expected_issuer,
        expected_audience=expected_audience,
        expected_type_header="JWT",
        allow_missing_expiration=False,
    )

    # 4. Cryptographically verify the signature and standard OIDC claims.
    return jwt_verifier.verify_and_decode(token, validator)

为智能体的 SPIFFE 身份授权

验证令牌的签名和标准声明后,检查已验证的 sub(主题)声明,以授权请求并在审核日志中记录调用代理。

sub 声明包含代理的唯一 SPIFFE ID,例如:

  spiffe://agents.global.org-123456789012.system.id.goog/resources/aiplatform/projects/9876543210/locations/us-central1/reasoningEngines/my-test-agent

在服务的授权逻辑中,将经过验证的 sub 声明与可信代理 SPIFFE ID(或信任域前缀)的许可名单进行比较,然后再授予对受保护资源的访问权限。

后续步骤