LINE Message API Webhooks Signature Validation
LINE Message API Webhooks Signature Validation
一開始需求源自於紀錄LINE bot訊息,因為只是記錄訊息而已,並沒有其餘應用,所以沒有採用社群維護的LINE SDK以求精簡,結果在簽名驗證的時候卡住了…最後還是參考了社群的Source Code才解決,所以這邊簡單紀錄一下過程。
驗證簽名最主要是為了確保紀錄的訊息,是透過LINE Platform發送的,LINE Platform在發送到Webhooks指定網址的時候,會在header中包含一組簽名X-Line-Signature,我們要將Channel Secret 和 Request Body用HMAC-SHA256 進行雜湊後,與X-Line-Signature比對是否相符,Channel Secret 在啟動Webhooks就可以看到。
環境
- .NET Core 3.1
- 啟用LINE Message API服務
實作
-
啟動Webhooks功能
從 LINE Developers Console 或是 LINE Official Account Manager 都可以啟動
-
使用授權篩選條件,驗證X-Line-Signature是否為LINE Platform發送
1 2 3 4 5 6 7
public class LineVerifySignatureFilter : IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { throw new NotImplementedException(); } }
-
以下程式碼參考pierre3/LineMessagingApi WebhookRequestMessageHelper,要注意.NET Core 3.1 需使用
EnableBuffering
來多次讀取body1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
public class LineVerifySignatureFilter : IAuthorizationFilter { private readonly string _channelSecret; public LineVerifySignatureFilter(IConfiguration configuration) { _channelSecret = configuration["ChannelSecret"]; } public void OnAuthorization(AuthorizationFilterContext context) { var request = context.HttpContext.Request; // .NET Core 3.1 需使用EnableBuffering來多次讀取body request.EnableBuffering(); string requestBody = new StreamReader(request.Body).ReadToEndAsync().GetAwaiter().GetResult(); request.Body.Position = 0; var xLineSignature = request.Headers["X-Line-Signature"].FirstOrDefault(); if (string.IsNullOrEmpty(xLineSignature) || !VerifySignature(xLineSignature, requestBody)) { throw new Exception("Signature validation faild."); } } private bool VerifySignature(string xLineSignature, string requestBody) { try { var key = Encoding.UTF8.GetBytes(_channelSecret); var body = Encoding.UTF8.GetBytes(requestBody); using HMACSHA256 hmac = new HMACSHA256(key); var hash = hmac.ComputeHash(body, 0, body.Length); var xLineBytes = Convert.FromBase64String(xLineSignature); return SlowEquals(xLineBytes, hash); } catch { return false; } } 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; } }
-
使用
TypeFilterAttribute
簡化Filter1 2 3 4 5 6
public class LineVerifySignatureAttribute : TypeFilterAttribute { public LineVerifySignatureAttribute() : base(typeof(LineVerifySignatureFilter)) { } }
-
簡單寫個logs檔看結果,透過Line發送訊息,如果通過驗證,就會有值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
[ApiController] [Route("api/{controller}/{action}/{id?}")] public class LineLogsController : ControllerBase { [HttpPost] [LineVerifySignature] public IActionResult Index(Rootobject request) { var requestToJson = JsonConvert.SerializeObject(request); using (StreamWriter w = System.IO.File.AppendText(@"C:\test.txt")) { w.Write(requestToJson); } return Ok(); } }
參考
Line Signature validation 其他範例
https://developers.line.biz/zh-hant/reference/messaging-api/#signature-validation
Line Message API 社群
https://github.com/pierre3/LineMessagingApi
ASP.NET Core 中的篩選條件
https://docs.microsoft.com/zh-tw/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.1