基本設定

關於如何註冊一個串接自己Web Api 的 Line Chatbot 網路上已經很多的教學文了,大概分為以下幾個步驟

https://developers.line.biz/en/

  • 申請一個Provider
  • 申請一個Channel (一個Provider可以有多個Channel)
  • 於Channel取得 Channel Secret 及 Access Token
  • Use webhook 啟用
  • Auto-reply messages 關閉
  • 設定 Webhook URL (需為https)

起始 Web API 專案與 Line Server 做連結

建立一個 .NET Core 3.1 的 Web Api 專案

建立一個 Line Controller,先用 dynamic 接收,看看 Request 過來的是什麼

執行看看,weatherforecast 為初始專案的 Sample,此Local Api 的 Port 為 44300

透過 ngrok 讓 Line Server 跟 Local Web API 做連結

  1. 安裝 node.js
  2. npm install ngrok -g
  3. ngrok -v

本文章所使用的版本為 2.3.35

執行 ngrok http 44300 -host-header=”localhost:44300" -region ap

44300要替換成你本地執行Run起來的Port

-region ap 為切換 ngrok 的 server,預設為us,在今年 Line 開始阻擋來至於 ngrok us 的 server 了,所以我們要將 Region 切到 Asia/Pacific (ap)

Run 起來大概會長這樣

我們將 https://44a914411bf2.ap.ngrok.io 這串加上Route api/line 後貼到 Line Developer Console,點選 Verify

進入 http://127.0.0.1:4040/ ,可以看到Line Server POST 過來的 Request

把這個POST 的 Body 移到 Postman 方便測試

也可以直接用我建好的 Collection 匯入 https://www.getpostman.com/collections/8a93e0b6ce3ff66f8c8b

透過 Postman 測試,在VS下中斷點,驗證可接收到

驗證是否為有效的Line來源

API 有了,但我們要如何驗證這個 Request 是來自 Line Server 來阻擋惡意攻擊呢? 在 Line Server 所傳過來的 Header 會包含這個項目 **X-Line-Signature ,**將 POST Body 跟 Chanel Secret 用做 HMAC-SHA256 演算法Hash過後,比對過後如果相同,表示該 Request 是來自 Line Server

*Verifying Signatures 官方參考文件*

在 Web Api 實作,我們先新建一個 Authorization Filter 來驗證 Line Signature,.NET Core Web Api 的 Reuqest pipeline 如下:

https://docs.microsoft.com/zh-tw/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.1

新增一個 Filter 叫 LineVerifySignatureFilter.cs 繼承 IAuthorizationFilter 實作,Channel Secret 要替換

    public class LineVerifySignatureFilter : IAuthorizationFilter
    {
     public void OnAuthorization(AuthorizationFilterContext context)
     {
      /*
       * Ensure the requestBody can be read multiple times. 
       * [https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httprequestrewindextensions.enablebuffering?view=aspnetcore-3.1](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httprequestrewindextensions.enablebuffering?view=aspnetcore-3.1)
       */
      context.HttpContext.Request.EnableBuffering();

    string requestBody = new StreamReader(context.HttpContext.Request.Body).ReadToEndAsync().Result;
      context.HttpContext.Request.Body.Position = 0;

    var xLineSignature = context.HttpContext.Request.Headers["X-Line-Signature"].ToString();
      var channelSecret = Encoding.UTF8.GetBytes("{Channel Secret}");
      var body = Encoding.UTF8.GetBytes(requestBody);

    using (HMACSHA256 hmac = new HMACSHA256(channelSecret))
      {
       var hash = hmac.ComputeHash(body, 0, body.Length);
       var xLineBytes = Convert.FromBase64String(xLineSignature);
       if (SlowEquals(xLineBytes, hash) == false)
       {
        context.Result = new ForbidResult();
       }
      }
     }

    private static bool SlowEquals(byte[] a, byte[] b)
     {
      uint diff = (uint)a.Length ^ (uint)b.Length;
      for (int i = 0; i < a.Length && i < b.Length; i++)
       diff |= (uint)(a[i] ^ b[i]);
      return diff == 0;
     }
    }

新增一個 Line LineVerifySignatureAttribute.cs 透過 TypeFilterAttribute 來例化

    public class LineVerifySignatureAttribute : TypeFilterAttribute
    {
     public LineVerifySignatureAttribute() : base(typeof(LineVerifySignatureFilter))
     {
     }
    }

在 Line Api Route 加上 [LineVerifySignature] 進行驗證,這樣程式就乾淨多了

把 Channel Secret/Access Token 移到 appsettings.json

通常我們會把這兩個資訊寫在設定檔,以便使用跟替換,在 .NET Core 可以使用 IOption 的方式來從設定檔注入

在 appsettings.json 增加區塊,放Channel Secret/Access Token

增加一個 LineSetting.cs,我們要用強型別來開發

startup.cs 增加 services.AddOptions()

回到前面建立的 LineVerifySignatureFilter.cs,注入 LineSetting及使用 ChannelSecret 即可完成

懶人包,本次學到了什麼?