はじめに
前回、Azure Functionsを使ってLINE Messaging APIを使ったbotを作成しました。
Azure Functionsを使って非同期処理のLINE BOTを作成する
前回の残作業として、以下の内容が残っていました。
紹介したコードは最小限のロジックのみ実装していてセキュリティやエラー処理などは考慮していません。
例えば、LINEサーバーからのメッセージであることの検証などが不足しています。
今回はメッセージの署名検証を行ってみます。
署名検証について
LINE developersのAPI Referenceに次の記述があります。
Webhook Authentication
X-Line-Signature Request Headerに入っているSignatureを検証することによって、
リクエストがLINE Platformから送信されたものであることを確認する必要があります。
検証は以下の手順で行います。
1. Channel Secretを秘密鍵として、HMAC-SHA256アルゴリズムによりRequest Bodyのダイジェスト値を得る。
2. ダイジェスト値をBASE64エンコードした文字列が、Request Headerに付与されたSignatureと一致することを確認する。
つまり、用意したWebhookへのアクセスが本当にLINEのサーバーからのそれなのかチェックする必要があります。
検証手順も記載されているのでその通りに実装して見ます。
実装
Webを検索すると類似の実装をやっている例がありました。
【乗るしかない】AWS LambdaとLINE BOT APIでRSSを通知する【このビッグウェーry】 | Developers.IO
昔のLINE APIを使う際の認証のためのコードのようですが、やっていることはほとんど同じです。上記のサイトを参考にして実装したのがこちらです。
var crypto = require("crypto");
function validate_signature(signature, body)
{
const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
return signature == crypto.createHmac('sha256', LINE_CHANNEL_SECRET).update(Buffer.from(JSON.stringify(body))).digest('base64');
}
module.exports = function(context, req) {
context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);
if (validate_signature(req.headers['x-line-signature'], req.body)) {
context.bindings.outputQueueItem = req.body;
}
else {
context.log('fail to validate signature');
}
context.res = { body : "" };
context.done();
};
yorifuji/azure-functions-line-signature-bot
個別に見ていきます。
module.exports = function(context, req) {
context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);
if (validate_signature(req.headers['x-line-signature'], req.body)) {
context.bindings.outputQueueItem = req.body;
}
else {
context.log('fail to validate signature');
}
context.res = { body : "" };
context.done();
};
まずLINEサーバーからのリクエストが届くと、こちらの関数が実行されます。
署名検証のために新たに追加したのはこちらのコードです。
if (validate_signature(req.headers['x-line-signature'], req.body)) {
context.bindings.outputQueueItem = req.body;
}
else {
context.log('fail to validate signature');
}
validate_signatureにreq.headers['x-line-signature']とreq.bodyを渡しています。
req.headers['x-line-signature']にはLINEサーバから付与されたSignatureが入っています。
req.bodyはRequest Bodyそのものです(JSON)。
次にvalidate_signatureの実装はこちらです。
var crypto = require("crypto");
function validate_signature(signature, body) {
const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
return signature == crypto.createHmac('sha256', LINE_CHANNEL_SECRET).update(Buffer.from(JSON.stringify(body))).digest('base64');
}
先頭の var crypto... はNode.jsのcryptoを利用するための宣言です。HMAC-SHA256を求めるのに利用します。
validate_signatureの先頭でLINE_CHANNEL_SECRETを環境変数(process.env)から読み込んでいます。
const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
これはHMAC-SHA256の秘密鍵として利用します。LINE_CHANNEL_SECRETの値はLINE Developersの管理画面に表示されるのでそちらからコピーしておきます。
環境変数から読み込んでいるのはGitHub上でコードを管理して、それを直接Azureにデプロイする運用にしているためです。
署名検証の手順としては、crypto.createHmacで生成したインスタンスに対してupdate関数を使ってbodyの内容をセットします。最後にBASE64に変換した値を出力します。
出力した値とsignatureが一致していれば署名検証に成功です。
サンプルコード
GitHubはこちらです。yorifuji/azure-functions-line-signature-bot
前回と同様に、Azure Functionsで「継続的インテグレーションの構成」の設定を行なっている方は、リポジトリをこちらに切り替えていただくとソースコードを丸ごと切り替えることができます。
なお、署名検証のために以下のキーを環境変数に登録する必要があります。
キー:
LINE_CHANNEL_SECRET
値:
LINE developersのChannel Secretの値
まとめ
LINE Messaging APIでX-Line-Signatureの署名検証を行いました。内容に誤りがあればコメントいただけると幸いです。
予定
- メッセージの種類に応じて応答を変える