推送订阅身份验证

如果推送订阅使用身份验证,则 Pub/Sub 服务会签署 JSON Web 令牌(JWT)并在推送请求的授权标头中发送 JWT。JWT 包含声明和签名。

订阅者可以验证 JWT 并验证以下内容:

  • 声明准确无误。
  • Pub/Sub 服务签署了声明。

如果订阅者使用防火墙,则无法接收推送请求。要接收推送请求,您必须关闭防火墙并验证 JWT。 如果订阅者有防火墙,您可能会收到 403 permission denied 错误。

准备工作

JWT 格式

JWT 是一个 OpenIDConnect JWT,包含标头、声明集和签名三个部分。Pub/Sub 服务将 JWT 编码为具有英文句点分隔符的 base64 字符串。

例如,以下授权标头包含已编码的 JWT:

"Authorization" : "Bearer
eyJhbGciOiJSUzI1NiIsImtpZCI6IjdkNjgwZDhjNzBkNDRlOTQ3MTMzY2JkNDk5ZWJjMWE2MWMzZDVh
YmMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tIiwiYXpwIjoiMTEzNzc0M
jY0NDYzMDM4MzIxOTY0IiwiZW1haWwiOiJnYWUtZ2NwQGFwcHNwb3QuZ3NlcnZpY2VhY2NvdW50LmNvb
SIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NTAxODU5MzUsImlhdCI6MTU1MDE4MjMzNSwia
XNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEzNzc0MjY0NDYzMDM4MzIxO
TY0In0.QVjyqpmadTyDZmlX2u3jWd1kJ68YkdwsRZDo-QxSPbxjug4ucLBwAs2QePrcgZ6hhkvdc4UHY
4YF3fz9g7XHULNVIzX5xh02qXEH8dK6PgGndIWcZQzjSYfgO-q-R2oo2hNM5HBBsQN4ARtGK_acG-NGG
WM3CQfahbEjZPAJe_B8M7HfIu_G5jOLZCw2EUcGo8BvEwGcLWB2WqEgRM0-xt5-UPzoa3-FpSPG7DHk7
z9zRUeq6eB__ldb-2o4RciJmjVwHgnYqn3VvlX9oVKEgXpNFhKuYA-mWh5o7BCwhujSMmFoBOh6mbIXF
cyf5UiVqKjpqEbqPGo_AvKvIQ9VTQ" 

标头和声明集是 JSON 字符串。解码后,它们将采用以下格式:

{"alg":"RS256","kid":"7d680d8c70d44e947133cbd499ebc1a61c3d5abc","typ":"JWT"}

{
   "aud":"https://example.com",
   "azp":"113774264463038321964",
   "email":"gae-gcp@appspot.gserviceaccount.com",
   "sub":"113774264463038321964",
   "email_verified":true,
   "exp":1550185935,
   "iat":1550182335,
   "iss":"https://accounts.google.com"
  }

附加到发送到推送端点的请求的令牌可具有最长达一个小时的生命周期。

为推送身份验证配置 Pub/Sub

以下示例展示了如何将推送身份验证服务账号设置为您选择的服务账号,以及如何向 service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com 服务代理授予 iam.serviceAccountTokenCreator 角色。

控制台

  1. 前往 Pub/Sub 订阅页面。

    转到“订阅”页面

  2. 点击创建订阅

  3. 订阅 ID 字段中,输入一个名称。

  4. 选择主题。

  5. 选择推送作为传送类型

  6. 输入端点网址。

  7. 勾选启用身份验证

  8. 选择服务账号。

  9. 确保服务代理 service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com 在项目的 IAM 信息中心内具有 iam.serviceAccountTokenCreator 角色。 如果服务账号尚未获得该角色,请在 IAM 控制台中点击授予以授予该角色。

  10. 可选:输入受众群体。

  11. 点击创建

gcloud

# Configure the push subscription
gcloud pubsub subscriptions (create|update|modify-push-config) ${SUBSCRIPTION} \
 --topic=${TOPIC} \
 --push-endpoint=${PUSH_ENDPOINT_URI} \
 --push-auth-service-account=${SERVICE_ACCOUNT_EMAIL} \
 --push-auth-token-audience=${OPTIONAL_AUDIENCE_OVERRIDE}

# Your service agent
# `service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com` needs to have the
# `iam.serviceAccountTokenCreator` role.
PUBSUB_SERVICE_ACCOUNT="service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
 --member="serviceAccount:${PUBSUB_SERVICE_ACCOUNT}"\
 --role='roles/iam.serviceAccountTokenCreator'

为推送订阅启用身份验证时,您可能会遇到 permission deniednot authorized 错误。 如需解决此问题,请向发起订阅创建或更新的主账号授予服务账号的 iam.serviceAccounts.actAs 权限。如需了解详情,请参阅“创建推送订阅”中的身份验证

如果您将经过身份验证的推送订阅与通过 Identity-Aware Proxy 保护的 App Engine 应用搭配使用,则必须将 IAP 客户端 ID 作为推送身份验证令牌的目标对象提供。如需在 App Engine 应用上启用 IAP,请参阅启用 IAP。 如需查找 IAP 客户端 ID,请在凭据页面上查找 IAP-App-Engine-app 客户端 ID。

声明

JWT 可用于验证由 Google 签名的声明(包括 emailaud 声明)。如需详细了解 Google 的 OAuth 2.0 API 如何用于身份验证和授权,请参阅 OpenID Connect

可通过两种机制使声明变得有意义。首先,Pub/Sub 要求,发出 CreateSubscription、UpdateSubscription 或 ModifyPushConfig 调用的用户或服务账号对推送授权服务账号拥有具有 iam.serviceAccounts.actAs 权限的角色。roles/iam.serviceAccountUser 角色就是此类角色。

其次,严格控制对用于为令牌签名的证书的访问。 要创建令牌,Pub/Sub 必须使用单独的签名服务账号身份(即服务代理 service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com)调用内部 Google 服务。此签名服务账号必须对推送身份验证服务账号(或推送身份验证服务账号的任何祖先资源,例如项目)拥有 iam.serviceAccounts.getOpenIdToken 权限或 Service Account Token Creator 角色 (roles/iam.serviceAccountTokenCreator)。

验证令牌

验证 Pub/Sub 发送到推送端点的令牌涉及:

  • 通过使用签名验证检查令牌完整性。
  • 确保令牌中的 emailaudience 声明与推送订阅配置中设置的值匹配。

以下示例展示了如何对发送到未受 Identity-Aware Proxy 保护的 App Engine 应用的推送请求进行身份验证。如果您的 App Engine 应用受到 IAP 的保护,则包含 IAP JWT 的 HTTP 请求标头为 x-goog-iap-jwt-assertion,并且必须进行相应的验证

协议

请求:

GET https://oauth2.googleapis.com/tokeninfo?id_token={BEARER_TOKEN}

响应:

200 OK
{
    "alg": "RS256",
    "aud": "example.com",
    "azp": "104176025330667568672",
    "email": "{SERVICE_ACCOUNT_NAME}@{YOUR_PROJECT_NAME}.iam.gserviceaccount.com",
    "email_verified": "true",
    "exp": "1555463097",
    "iat": "1555459497",
    "iss": "https://accounts.google.com",
    "kid": "3782d3f0bc89008d9d2c01730f765cfb19d3b70e",
    "sub": "104176025330667568672",
    "typ": "JWT"
}

C#

在尝试此示例之前,请按照《快速入门:使用客户端库》中的 C# 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub C# API 参考文档

/// <summary>
/// Extended JWT payload to match the pubsub payload format.
/// </summary>
public class PubSubPayload : JsonWebSignature.Payload
{
    [JsonProperty("email")]
    public string Email { get; set; }
    [JsonProperty("email_verified")]
    public string EmailVerified { get; set; }
}
/// <summary>
/// Handle authenticated push request coming from pubsub.
/// See the full sample in https://github.com/GoogleCloudPlatform/dotnet-docs-samples/blob/main/appengine/flexible/Pubsub/Pubsub.Sample/Controllers/HomeController.cs
/// </summary>
[HttpPost]
[Route("/AuthPush")]
public async Task<IActionResult> AuthPushAsync([FromBody] PushBody body, [FromQuery] string token)
{
    // Get the Cloud Pub/Sub-generated "Authorization" header.
    string authorizaionHeader = HttpContext.Request.Headers["Authorization"];
    string verificationToken = token ?? body.message.attributes["token"];
    // JWT token comes in `Bearer <JWT>` format substring 7 specifies the position of first JWT char.
    string authToken = authorizaionHeader.StartsWith("Bearer ") ? authorizaionHeader.Substring(7) : null;
    if (verificationToken != _options.VerificationToken || authToken is null)
    {
        return new BadRequestResult();
    }
    // Verify and decode the JWT.
    // Note: For high volume push requests, it would save some network
    // overhead if you verify the tokens offline by decoding them using
    // Google's Public Cert; caching already seen tokens works best when
    // a large volume of messages have prompted a single push server to
    // handle them, in which case they would all share the same token for
    // a limited time window.
    var payload = await JsonWebSignature.VerifySignedTokenAsync<PubSubPayload>(authToken);

    // IMPORTANT: you should validate payload details not covered
    // by signature and audience verification above, including:
    //   - Ensure that `payload.Email` is equal to the expected service
    //     account set up in the push subscription settings.
    //   - Ensure that `payload.Email_verified` is set to true.

    var messageBytes = Convert.FromBase64String(body.message.data);
    string message = System.Text.Encoding.UTF8.GetString(messageBytes);
    s_authenticatedMessages.Add(message);