Slack 交互消息:POST 请求负载具有意外格式

Slack Interactive Messages: POST request payload has an unexpected format

我在 Slack 的 Flask 应用程序中收到 POST 请求。当用户按下交互式消息按钮时发送请求。根据 Slack docs 我必须提取请求的正文以验证签名。 不过,我计算出的签名与 Slack 发送的签名不匹配。 事实上,请求的主体是一些编码字符串。正如预期的那样,该字符串实际上是一个编码字典而不是查询 str 参数。

这是我的观点的开头:

@app.route('/register', methods=['POST'])
def register_visit():
    data = request.get_data()
    signature = request.headers.get('X-Slack-Signature', None)
    timestamp = request.headers.get('X-Slack-Request-Timestamp', None)
    signing_secret = b'aaaaaaaaaaaaaaaa'


    # old message, ignore
    if round(actual_time.time() - float(timestamp)) > 60 * 5:
        return
    concatenated = ("v0:%s:%s" % (timestamp, data)).encode('utf-8')
    computed_signature = 'v0=' + hmac.new(signing_secret, msg=concatenated, digestmod=hashlib.sha256).hexdigest()
    if hmac.compare_digest(computed_signature, signature):
        ...

我尝试格式化接收到的数据,使其看起来像:

token=fdjkgjl&user_id=1234... 但我不知道数据中必须存在的所有必要参数。

非常感谢任何想法。

邮件正文如下 - URL 解码后(注意我已经修改了可能敏感的数据):

b'payload={"type":"interactive_message","actions": [{"name":"yes_button","type":"button","value":"236"}],"callback_id":"visit_button","team":{"id":"fffff","domain":"ffff"},"channel":{"id":"ffff","name":"directmessage"},"user":{"id":"ffffff","name":"fffft"},"action_ts":"1540403943.419120","message_ts":"1541403949.000100","attachment_id":"1","token":"8LpjBuv13J7xAjhl2lEajoBU","is_app_unfurl":false,"original_message":{"text":"Test","bot_id":"DDDDDDDDD","attachments":[{"callback_id":"visit_button","text":"Register","id":1,"color":"3AA3E3","actions":[{"id":"1","name":"yes_button","text":"Yes","type":"button","value":"236","style":""}],"fallback":"Register"}],"type":"message","subtype":"bot_message","ts":"1540413949.000100"},"response_url":"https://hooks.slack.com/actions/ffffff/ffffff/tXJjx1XInaUhrikj6oEzK08e","trigger_id":"464662548327.425084163429.dda35a299eedb940ab98dbb9386b56f0"}'

您获得 "garbled" 数据的原因是您正在使用 request.get_data()。该方法将 return 请求的 raw 数据,但不会为您进行任何解码。

更方便的是使用request.form.get('payload'),它会直接给你请求对象的JSON字符串。然后,您可以使用 json.loads() 将其转换为 dict 对象,以便在您的应用程序中进一步处理它。

请注意,您收到的格式是交互式消息的正确格式。您不会按照您的建议(例如斜杠命令请求)获得查询字符串(例如 "token=abc;user_id?def...")。交互式消息请求将始终以有效负载形式 属性 包含 JSON 字符串形式的请求。请参阅 here 以供参考。

这是一个简单的工作示例,它将向按下按钮的用户回复问候语。它将直接与 Slack 一起工作,但我建议使用 Postman 来测试它。

#app.py

from flask import Flask, request #import main Flask class and request object
import json

app = Flask(__name__) #create the Flask app

@app.route('/register', methods=['POST'])
def register_visit():
    slack_req = json.loads(request.form.get('payload'))
    response = '{"text": "Hi, <@' + slack_req["user"]["id"] + '>"}'
    return response, 200, {'content-type': 'application/json'}

if __name__ == '__main__':
    app.run(debug=True, port=5000) #run app in debug mode on port 5000

好的,问题与 Slack 向我发送消息的方式无关。这是关于误解哪些数据以字节形式出现,哪些数据是 unicode。在我的例子中,罪魁祸首是字符串格式——行 concatenated = ("v0:%s:%s" % (timestamp, data)).encode('utf-8') 应该是 concatenated = (b"v0:%b:%b" % (timestamp.encode("utf-8"), data))。数据已经是字节,同时时间戳是unicode。 不敢相信我已经为此苦苦思索了几个小时 -_-

@app.route('/register', methods=['POST'])
def register_visit():
    data = request.get_data()
    signature = request.headers.get('X-Slack-Signature', None)
    timestamp = request.headers.get('X-Slack-Request-Timestamp', None)
    signing_secret = b'aaaaaaaaaaaaaaaa'
    # old message, ignore
    if round(actual_time.time() - float(timestamp)) > 60 * 5:
        return
    concatenated = (b"v0:%b:%b" % (timestamp.encode("utf-8"), data))
    computed_signature = 'v0=' + hmac.new(signing_secret, msg=concatenated, 
    digestmod=hashlib.sha256).hexdigest()
    if hmac.compare_digest(computed_signature, signature):
        ...