如何在Golang Fiber中验证Trello Webhook签名?哈希不一致问题排查
Trello Webhook签名验证Golang Fiber实现问题
我参考Trello官方Webhook文档,将Node.js的签名验证逻辑转为Golang Fiber代码,但生成的哈希始终与Trello返回的哈希不一致,尝试了三种实现均失败,求指出遗漏点。
尝试代码1
func TrelloWebhook(c *fiber.Ctx) error { var ( TRELLO_SECRET = "****ea6308995" webhookURL = "https://*****ssum.ngrok-free.app/trello-webhook" ) base64Digest := func(secret string) string { key := []byte(TRELLO_SECRET) mac := hmac.New(sha1.New, key) mac.Write([]byte(secret)) return base64.StdEncoding.EncodeToString(mac.Sum(nil)) } hashedHeader := c.Get("x-trello-webhook") requestBody := string(c.BodyRaw()) doubleHashedHeader := base64Digest(hashedHeader) doubleHashedBody := base64Digest(base64Digest(requestBody + webhookURL)) fmt.Println("doubleHashedHeader : ", doubleHashedHeader) fmt.Println("doubleHashedBody : ", doubleHashedBody) if hmac.Equal([]byte(doubleHashedHeader), []byte(doubleHashedBody)) { fmt.Println("SUCCESS") } // Your existing return statement return c.Status(200).JSON(&fiber.Map{ "status": true, "information": "customers-data_request!", }) }
尝试代码2
func TrelloWebhook(c *fiber.Ctx) error { secret := "****ea6308995" callbackURL := "https://*****ssum.ngrok-free.app/trello-webhook" base64Digest := func(s string) string { h := hmac.New(sha1.New, []byte(secret)) h.Write([]byte(s)) return base64.StdEncoding.EncodeToString(h.Sum(nil)) } var request map[string]interface{} if err := json.Unmarshal(c.Body(), &request); err != nil { fmt.Println("Error decoding JSON:", err) return c.SendStatus(fiber.StatusBadRequest) } contentBuffer := bytes.NewBuffer(c.Body()) contentBuffer.WriteString(callbackURL) content := contentBuffer.String() doubleHash := base64Digest(content) headerHash := c.Get("x-trello-webhook") if doubleHash == headerHash { fmt.Println(" Working ? ") } fmt.Println("doubleHash : ", doubleHash) fmt.Println("headerHash : ", headerHash) // Your existing return statement return c.Status(200).JSON(&fiber.Map{ "status": true, "information": "customers-data_request!", }) }
尝试代码3
func TrelloWebhook(c *fiber.Ctx) error { secret := "****ea6308995" callbackURL := "https://*****ssum.ngrok-free.app/trello-webhook" base64Digest := func(s string) string { h := hmac.New(sha1.New, []byte(secret)) h.Write([]byte(s)) return base64.StdEncoding.EncodeToString(h.Sum(nil)) } var request map[string]interface{} if err := json.Unmarshal(c.Body(), &request); err != nil { fmt.Println("Error decoding JSON:", err) return c.SendStatus(fiber.StatusBadRequest) } requestBody, err := json.Marshal(request) if err != nil { fmt.Println("Error encoding JSON:", err) return c.SendStatus(fiber.StatusInternalServerError) } content := string(requestBody) + callbackURL doubleHash := base64Digest(content) headerHash := c.Get("x-trello-webhook") fmt.Println("doubleHash : ", doubleHash) fmt.Println("headerHash : ", headerHash) // Your existing return statement return c.Status(200).JSON(&fiber.Map{ "status": true, "information": "customers-data_request!", }) }
问题分析与正确实现
核心错误点
- 错误的哈希逻辑:尝试1中对结果进行了双重哈希,完全不符合Trello的签名规则(Trello只做一次HMAC-SHA1+Base64编码)
- Body被修改:尝试3中将原始Body解析后重新Marshal,导致JSON格式(空格、键顺序、缩进)与Trello发送的原始Body不一致,拼接后的字符串无法匹配签名生成的源数据
- 回调URL一致性问题:若代码中的回调URL与Trello后台配置的不一致(如大小写、末尾斜杠、HTTP/HTTPS),会直接导致哈希不匹配
正确实现代码
import ( "crypto/hmac" "crypto/sha1" "encoding/base64" "fmt" "github.com/gofiber/fiber/v2" ) func TrelloWebhook(c *fiber.Ctx) error { const ( trelloSecret = "你的Trello Secret密钥" callbackURL = "与Trello后台完全一致的回调URL" // 注意大小写、末尾斜杠、协议必须完全匹配 ) // 生成Trello格式的签名 generateSignature := func(data string) string { h := hmac.New(sha1.New, []byte(trelloSecret)) _, _ = h.Write([]byte(data)) // 写入字节流无失败可能,忽略错误 return base64.StdEncoding.EncodeToString(h.Sum(nil)) } // 获取原始请求Body(必须用BodyRaw保证未被修改) rawBody := string(c.BodyRaw()) // 拼接原始Body与回调URL(顺序不可调换) signatureSource := rawBody + callbackURL // 计算本地签名 computedSignature := generateSignature(signatureSource) // 获取Trello发送的签名 trelloSignature := c.Get("x-trello-webhook") // 用hmac.Equal避免时序攻击,安全比较签名 if hmac.Equal([]byte(computedSignature), []byte(trelloSignature)) { fmt.Println("签名验证成功") return c.Status(fiber.StatusOK).JSON(fiber.Map{ "status": true, "msg": "Webhook请求验证通过", }) } fmt.Println("签名验证失败") fmt.Println("本地计算签名:", computedSignature) fmt.Println("Trello发送签名:", trelloSignature) return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "status": false, "msg": "签名验证失败", }) }
额外注意事项
- 回调URL严格匹配:必须与Trello后台配置的Webhook回调URL完全一致,包括协议(HTTP/HTTPS)、域名、路径、大小写、末尾是否带斜杠
- 避免修改原始Body:不要对请求Body做解析后重新序列化的操作,必须使用原始的请求字节
- 哈希算法正确性:Trello使用HMAC-SHA1算法,不要误用SHA256等其他算法
- 安全比较签名:始终使用
hmac.Equal而非直接字符串比较,防止时序攻击
内容的提问来源于stack exchange,提问作者javmah
相关产品推荐
相关产品推荐

