💃

Lambda上で動くPythonにてSlack Appからのリクエストを検証する

2021/04/04に公開

ネットを調べると、こちら の記事を元ネタに作り込まれている方が割と出てきますが、
Python Slack SDK に当該処理をやってくれる便利クラスがあるのでご紹介です
めっちゃ小ネタですが忘れがちなので備忘メモ✍

方法

Serverless FrameworkなどでAPI Gatewayからのリクエストを受け付けるケースを仮定します
Slack Appをワークスペースに追加した際に作成される Signing Secret が必要となるので、環境変数など任意の方法でLambdaに渡してあげます

また、今回の話とは関係ないですが、Slackのリクエストタイムアウトは3秒と短いので、 async: true にして非同期受け付けにするとそこそこ長い処理でも扱えるようになるものと思います

serverless.yml
service: example
frameworkVersion: "2"
provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  region: ap-northeast-1
  timeout: 60
  memorySize: 256
  environment:
    SLACK_OAUTH_TOKEN: ${env:SLACK_OAUTH_TOKEN}
    SLACK_SIGNING_SECRET: ${env:SLACK_SIGNING_SECRET}
functions:
  main:
    handler: main.handler
    events:
      - http:
          path: example
          method: post
          async: true
plugins:
  # pipenvでライブラリ管理ができるのでオススメ
  - serverless-python-requirements
custom:
  pythonRequirements:
    dockerizePip: true

本記事のキモです
slack_sdk にて SignatureVerifier というクラスが用意されており、こちらを使うと引数にオブジェクト等を渡すだけで検証をしてもらえるのですが、
Api Gatewayから来るイベントは、 event['body'] がDictのため、そのままだと is_valid_request 関数に渡すことができないため、URLエンコードを施します

main.py
import json
import os

from urllib.parse import urlencode

from slack_sdk import WebClient
from slack_sdk.signature import SignatureVerifier

SLACK_OAUTH_TOKEN = os.environ['SLACK_OAUTH_TOKEN']
SLACK_SIGNING_SECRET = os.environ['SLACK_SIGNING_SECRET']


def handler(event, context):
    """
    eventはこんな値が来る
    {
      "body": {
        "xxx": "xxx",
        "yyy": "yyy"
      },
      "headers": {
        "x-slaxk-xxx": "xxx"
        "y-slaxk-yyy": "yyy"
      },
      その他は略
    }
    """
    print(json.dumps(event))

    # ポイント1: このクラスを使うと楽
    signature_verifier = SignatureVerifier(signing_secret=SLACK_SIGNING_SECRET)
    # ポイント2: bodyはURLエンコードして渡す
    encoded_body = urlencode(event['body'])
    if not signature_verifier.is_valid_request(body=encoded_body, headers=event['headers']):
        # リクエスト検証に失敗
        return {
            'statusCode': 403,
            'body': json.dumps({})
        }

    # インストールをおこなったSlackワークスペースからのリクエストであることが保証される
    web_client = WebClient(token=SLACK_OAUTH_TOKEN)

    # ユースケースに合わせていろいろする

    return {
        'statusCode': 200,
        'body': json.dumps({})
    }

お試しを🍛

Discussion