GCP Pub/Sub 訂閱者以 Push 方式接收訊息並驗證JWT

  1. 前言
  2. 前置作業
    1. API 建立
    2. 透過 ngrok 提供外部呼叫 (本機測試方便使用)
  3. 建立 傳送類型 = push 的 subscription
  4. 參考文件

前言

測試 Pub/Sub Subscription 傳送類型 = Push

由 Client (我方) 提供 API Endpoint 供 Pub/Sub 呼叫

  • Endpoint 需為公開的 https address,且為有效 SSL 憑證
  • 可啟用身分驗證,啟用後 Pub/Sub 打我方 API 時,會在 header 帶 Authorization 的 JWT Token 供我方驗證,避免被亂呼叫
  • 收到消息後,若要確認消息則需回傳 http status code 102、200、201、202、204

前置作業

API 建立

預先準備好一個 .Net Web API 專案,建立 API 用以提供 GCP Pub/Sub 呼叫,並驗證 JWT Token

using Google.Apis.Auth;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

namespace GCP_PubSubConsumerPush.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class TestController : ControllerBase
    {
        private readonly ILogger<TestController> _logger;

        public TestController(ILogger<TestController> logger)
        {
            _logger = logger;
        }

        /// <summary>
        /// Handle authenticated push request coming from pubsub.
        /// </summary>
        [HttpPost]
        [Route("/AuthPush")]
        public async Task<IActionResult> AuthPushAsync([FromBody] PushBody body, [FromQuery] string token)
        {
            try
            {
                string bodyJson = JsonConvert.SerializeObject(body);

                string authorizaionHeader = HttpContext.Request.Headers["Authorization"];

                if (!string.IsNullOrEmpty(authorizaionHeader))
                {
                    string authToken = authorizaionHeader.StartsWith("Bearer ") ? authorizaionHeader.Substring(7) : string.Empty;
                    // Verify and decode the JWT.
                    var payload = await JsonWebSignature.VerifySignedTokenAsync<PubSubPayload>(authToken);

                    string payloadJson = JsonConvert.SerializeObject(payload);
                }

                string verificationToken = token ?? body.message.attributes["token"];
                if (verificationToken != "123")
                    return new BadRequestResult();

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

                return new OkResult();
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message);
            }
        }
    }

    public class PubSubPayload : JsonWebSignature.Payload
    {
        [JsonProperty("email")]
        public string Email { get; set; }

        [JsonProperty("email_verified")]
        public string EmailVerified { get; set; }
    }

    public class PushBody
    {
        public PushMessage message { get; set; }
        public string subscription { get; set; }
    }

    public class PushMessage
    {
        public Dictionary<string, string> attributes { get; set; }
        public string data { get; set; }
        public string message_id { get; set; }
        public string publish_time { get; set; }
    }
}

透過 ngrok 提供外部呼叫 (本機測試方便使用)

可透過免費的 ngrok 服務,可將外部 request 轉發到本機,且 ngrok 有自帶 https 憑證

5208 為 本機測試的 API Port號

ngrok http 5208

備註: 若一直收不到訊息,且 ngrok 顯示 307 Temporary Redirect,則把 app.UseHttpsRedirection() 註解掉即可

建立 傳送類型 = push 的 subscription

可參考GCP 官方文件 - Push subscriptions

# 因要建立 Token 需先將服務帳戶賦予權限
gcloud projects add-iam-policy-binding $projectId --member="serviceAccount:leo-pubsub@$projectId.iam.gserviceaccount.com" --role="roles/iam.serviceAccountTokenCreator"

# 建立 subscription
gcloud pubsub subscriptions create Test-Topic-SubPush \
    --topic=Test-Topic \
    --push-endpoint=https://d993-116-241-108-66.ngrok-free.app/AuthPush?token=123 \
    --push-auth-service-account=leo-pubsub@$projectId.iam.gserviceaccount.com \
    --ack-deadline=10

push-endpoint = 我方的 API endpoint,後面加的 token 可隨機產生,可在 API 內再次驗證
API 內除了驗證 Google Authorization 的 JWT Token之外,也可再次驗證自定義的 token(123)

body 內容

{
  "message": {
    "attributes": {
      "test": "888"
    },
    "data": "MTIz",
    "message_id": "7743243870023401",
    "publish_time": "2023-05-20T08:55:02.649Z"
  },
  "subscription": "projects/$projectId/subscriptions/Test-Topic-SubPush"
}

data 是 base64字串,需要轉為 byte 再轉回 string

JWT Token 解析後的 payload 內容

{
  "email": "leo-pubsub@$projectId.iam.gserviceaccount.com",
  "email_verified": "true",
  "iss": "https://accounts.google.com",
  "sub": "104310064111477055168",
  "aud": "https://d993-116-241-108-66.ngrok-free.app/AuthPush?token=123",
  "target_audience": null,
  "exp": 1684578888,
  "nbf": null,
  "iat": 1684575288,
  "jti": null,
  "nonce": null,
  "typ": null
}

參考文件

GCP 官方文件 - Push subscriptions
GCP 官方文件 - Authentication for push subscription


轉載請註明來源,若有任何錯誤或表達不清楚的地方,歡迎在下方評論區留言,也可以來信至 leozheng0621@gmail.com
如果文章對您有幫助,歡迎斗內(donate),請我喝杯咖啡

斗內💰

×

歡迎斗內

github