From 1c98a8d0acabadcd41b80807997bd6428442607f Mon Sep 17 00:00:00 2001 From: rianli Date: Wed, 11 Sep 2024 00:04:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=85=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface-framework/event-emit.md | 59 +++++++++++++++++-- .../dev-prepare/interface-framework/opcode.md | 4 +- .../dev-prepare/interface-framework/sign.md | 12 ++-- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/docs/develop/api-v2/dev-prepare/interface-framework/event-emit.md b/docs/develop/api-v2/dev-prepare/interface-framework/event-emit.md index 46a7a849..2f996581 100644 --- a/docs/develop/api-v2/dev-prepare/interface-framework/event-emit.md +++ b/docs/develop/api-v2/dev-prepare/interface-framework/event-emit.md @@ -36,10 +36,7 @@ QQ机器人开放平台支持通过使用HTTP接口接收事件。开发者可 | **CODE** | **名称** | **客户端行为** | **描述** | | --- | --- | --- | --- | | 0 | Dispatch | Receive | 服务端进行消息推送 | -| 12 | HTTP Callback ACK | Reply | 仅用于 http 回调模式的回包,代表机器人收到了平台推送的数据 | | 13 | 回调地址验证 | Receive | 开放平台对机器人服务端进行验证 | -| 14 | 回调地址验证 ACK | Reply | 机器人服务端响应开放平台的验证请求 | - ### 签名校验 机器人服务端需要对回调请求进行签名验证以保证数据没有被篡改过。 @@ -50,8 +47,8 @@ QQ机器人开放平台支持通过使用HTTP接口接收事件。开发者可 开发者需要提供一个HTTPS回调地址。并选定监听的事件类型。开放平台会将事件通过回调的方式推送给机器人。 event_subscription -开发者配置回调地址时,开放平台会对回调地址进行验证。机器人服务端需要按格式返回签名信息。签名算法同上。 机器人服务端需要在 3 秒内响应200或204,表示接受到事件。 -* 请求结构 +配置回调地址后,开放平台会对回调地址进行验证: +* 请求结构(Payload.d) | **字段** | **描述** | | --- |------| @@ -65,6 +62,58 @@ QQ机器人开放平台支持通过使用HTTP接口接收事件。开发者可 | plain_token | 要计算hash的字符串 | | signature | 签名 | +计算过程如下(golang): +```go +func handleValidation(rw http.ResponseWriter, r *http.Request, botSecret string) { + httpBody, err := io.ReadAll(r.Body) + if err != nil { + log.Println("read http body err", err) + return + } + payload := &Payload{} + if err = json.Unmarshal(httpBody, payload); err != nil { + log.Println("parse http payload err", err) + return + } + validationPayload := &ValidationRequest{} + if err = json.Unmarshal(payload.Data, validationPayload);err != nil { + log.Println("parse http payload failed:", err) + return + } + seed := botSecret + for len(seed) < ed25519.SeedSize { + seed = strings.Repeat(seed, 2) + } + seed = seed[:ed25519.SeedSize] + reader := strings.NewReader(seed) + // GenerateKey 方法会返回公钥、私钥,这里只需要私钥进行签名生成不需要返回公钥 + _, privateKey, err := ed25519.GenerateKey(reader) + if err != nil { + log.Println("ed25519 generate key failed:", err) + return + } + var msg bytes.Buffer + msg.WriteString(validationPayload.EventTs) + msg.WriteString(validationPayload.PlainToken) + signature := hex.EncodeToString(ed25519.Sign(privateKey, msg.Bytes())) + if err != nil { + log.Println("generate signature failed:", err) + return + } + rspBytes, err := json.Marshal( + &ValidationResponse{ + PlainToken: validationPayload.PlainToken, + Signature: signature, + }) + if err != nil { + log.Println("handle validation failed:", err) + return + } + rw.Write(rspBytes) +} + +``` + 例如机器人账号 ``` appid: 11111111 diff --git a/docs/develop/api-v2/dev-prepare/interface-framework/opcode.md b/docs/develop/api-v2/dev-prepare/interface-framework/opcode.md index 6b7d0f44..32ccb0e0 100644 --- a/docs/develop/api-v2/dev-prepare/interface-framework/opcode.md +++ b/docs/develop/api-v2/dev-prepare/interface-framework/opcode.md @@ -12,7 +12,9 @@ | 9 | Invalid Session | Receive | 当identify或resume的时候,如果参数有错,服务端会返回该消息 | | 10 | Hello | Receive | 当客户端与网关建立ws连接之后,网关下发的第一条消息 | | 11 | Heartbeat ACK | Receive/Reply | 当发送心跳成功之后,就会收到该消息 | -| 12 | HTTP Callback ACK | Reply | 仅用于 http 回调模式的回包,代表机器人收到了平台推送的数据 | +| 12 | HTTP Callback ACK | Reply | 仅用于 http 回调模式的回包,代表机器人收到了平台推送的数据() | +| 13 | 回调地址验证 | Receive | 开放平台对机器人服务端进行验证 | + 客户端操作含义如下: diff --git a/docs/develop/api-v2/dev-prepare/interface-framework/sign.md b/docs/develop/api-v2/dev-prepare/interface-framework/sign.md index 6a2c9d48..a978f05c 100644 --- a/docs/develop/api-v2/dev-prepare/interface-framework/sign.md +++ b/docs/develop/api-v2/dev-prepare/interface-framework/sign.md @@ -67,12 +67,14 @@ privateKey: [110 97 79 67 48 111 99 81 69 51 115 104 87 76 65 102 102 102 86 76 if timestamp == "" { return false } - // 按照timstamp+Body顺序组成签名体 + httpBody, err := io.ReadAll(req.Body) + if err != nil { + return false + } + // 按照timestamp+Body顺序组成签名体 var msg bytes.Buffer msg.WriteString(timestamp) - var body bytes.Buffer - // copy body into buffers - _, err = io.Copy(&msg, io.TeeReader(r.Body, &body)) + msg.Write(httpBody) if err != nil { return false } @@ -81,7 +83,7 @@ privateKey: [110 97 79 67 48 111 99 81 69 51 115 104 87 76 65 102 102 102 86 76 - 根据公钥、Signature、签名体调用 Ed25519 算法进行验证 ```go - ed25519.Verify(publicKey, msg.Bytes(), sig) + ed25519.Verify(publicKey, msg.Bytes(), sig) ``` DEMO