Slack APIでリクエストを検証する。を実装してみる。


やりたいこと

HTTPリクエストが、Slackからのリクエストなのか確認する
Slackからのリクエストを検証する

AWS周り

API Gatewayを使っていたのでHTTPヘッダを編集

POST->メソッドリクエスト->HTTPリクエストヘッダー

POST->統合リクエスト->HTTPヘッダー

サーバー側コード

flaskで書いてます。

app = Flask(__name__)
@app.route("/", methods=["POST"])
def root():

    slack_signing_secret = b'MY_SLACK_SIGNING_SECRET'
    request_body = request.stream.read().decode()
    timestamp = request.headers['X-Slack-Request-Timestamp']
    if absolute_value(int(time.time()) - int(timestamp)) > 60 * 5:
        #タイムスタンプが5分以上ずれていたら、それはDDoS攻撃の可能性が高いので無視する。
        return
    sig_basestring = 'v0:' + timestamp + ':' + request_body
    my_signature = 'v0=' + hmac.new(
            slack_signing_secret,
            sig_basestring.encode("utf-8"),
            hashlib.sha256
            ).hexdigest()
    slack_signature = request.headers['X-Slack-Signature']
    if hmac.compare_digest(my_signature, slack_signature):
        print("request is ok")
    else:
        print("bad request")

ライブラリにありました

python-slackclientに関数がありました。ありがたい。

from slack import WebClient
client = WebClient(slack_bot_token)

app = Flask(__name__)
@app.route("/", methods=["POST"])
def root():

    slack_signing_secret = "MY_SLACK_SIGNING_SECRET"
    request_body = request.stream.read().decode()
    timestamp = request.headers['X-Slack-Request-Timestamp']
    signature = request.headers["X-Slack-Signature"]
    if absolute_value(int(time.time()) - int(timestamp)) > 60 * 5:
        #タイムスタンプが5分以上ずれていたら、それはDDoS攻撃の可能性が高いので無視する。
        return

    valification_flag = client.validate_slack_signature(
            signing_secret=slack_signing_secret,
            data=request_body,
            timestamp=timestamp,
            signature=signature
            )

    if valification_flag:
        print("request is ok")
    else:
        print("bad request")

困ったこと

もともとflask.request.formでペイロードを取得していたのですが、
flask.request.streamを使うとbyteになってしまいます。

そして、どちらかを使うとrequestの中身は空になります。
なので、今回はbyte to jsonを以下のように書きました。

import urllib.parse
dict_data = json.loads(urllib.parse.unquote_plus(request_body).split("=",1)[1])

むりやり

追記(2020.3.31)

先の項目でparseunquoteには以下の二種類ある。

urllib.parse.unquote()
urllib.parse.unquote_plus()

今回はunquote_plus()を使わないといけなかったので修正した。