RoomKit 服务端针对房间和白板的一些事件,例如用户进入房间、用户共享白板等事件开放了事件通知,当这些事件被触发时,RoomKit 服务端会通过回调的方式通知开发者,开发者后台可以基于这些事件来进行房间的管理。
RoomKit 服务端事件回调相关的接入参数如下:
| 参数 | 说明 | 示例 |
|---|---|---|
| 回调地址 | 用于接收推送事件的地址,支持 HTTP 或者 HTTPS,由开发者提供,必须保证外网可访问。 | http://roomkit-api.developer.im/ |
| callbackSecret | 用于生成签名的 key,由 ZEGO 生成提供给开发者。 | D54euRgW8H4jZvuCTF1gFdj |
| EncodingKey | 消息体加密密钥,使用 AES 对称加密,长度必须 >=16 个字符。由开发者提供,如果不提供或者提供的参数不符合要求,则不对消息体进行加密。 | N8PkYt0FO1R4OqwmYiPT8PykQ4wQEtAcBaJVR |
请求方式:POST
请求地址:http://roomkit-api.developer.im/?signature=ASDFQWEXZCVAQFASDFASDFSS×tamp=13500001234&nonce=123412323
请求消息体:
// 未加密
{
"event_type": 1,
"room_id": "19827033659",
"timestamp": 1614149165898
}
// 加密后 16 进制字符串
abcoapadjandakwdnwakdwa...
参数说明:
| 参数 | 说明 |
|---|---|
| signature | 签名信息。 |
| timestamp | 时间戳。 |
| nonce | 随机数字符串。 |
消息体携带的参数的含义请参考 RoomKit 服务端 API 中的 服务端事件回调。
开发者后台收到回调通知之后,需要进行签名验证,防止数据被篡改。然后再对消息体进行解密(如果开了消息体加密的话),拿到通知事件。
签名校验步骤如下:
)
拼接签名字符串。
| 字段 | 说明 |
|---|---|
| nonce | 随机数字符串,回调请求中的参数。 |
| timestamp | 时间戳,回调请求中的参数。 |
| callbackSecret | 由 ZEGO 生成提供给开发者。 |
将以上参数按照字典序排序,排序之后进行拼接。
对步骤 1 拼接的字符串进行 sha1 计算得到签名信息,然后将签名信息转换成 16 进制字符串。
示例如下:
byteArr = sha1(str) // hash
signStr = byteToHexStr(byteArr) // 将hash信息转换为16进制字符串
对比从 URL 得到的签名,发现两者一致,签名通过,说明没被篡改。
if signature == signStr {
pass
} else {
fail
}
Golang 示例代码如下:
signature := r.GetString("signature")
timestamp := r.GetString("timestamp")
nonce := r.GetString("nonce")
callbackSecret := "" // 由 ZEGO 生成提供给开发者。
strs := make([]string, 0, 3)
strs = append(strs, callbackSecret)
strs = append(strs, timestamp)
strs = append(strs, nonce)
sort.Strings(strs)
var s string
for _, str := range strs {
s += str
}
sha := sha1.New()
sha.Write([]byte(s))
return fmt.Sprintf("%x", sha.Sum(nil)) == signature
$timestamp = 1470820198;
$nonce = 123412;
$callbackSecret = 'secret';
排序拼接后需要加密的原始串为:1234121470820198secret
加密的结果为:5bd59fd62953a8059fb7eaba95720f66d19e4517
如果开发者配置了 EncodingKey,回调请求的消息体会以密文形式发送,开发者需要解密消息体后才能获取到 JSON 格式的通知事件。
加解密采用了基于 AES 的对称加密算法。AES 采用 CBC 模式,IV 初始向量大小为 16 字节,取 EncodingKey 前 16 字节。加密消息为 16 进制字符串,需首先将它转换成字节流,然后进行 AES 解密。
解密步骤如下:
将加密消息转换成字节流
byteArr = HexStrToByte(encrypt_msg)
解密
text = AESDecrypt(EncodingKey, EncodingKey[:16], byteArr)
Golang 示例代码如下:
package sample
import (
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"errors"
"fmt"
"crypto/sha1"
)
// 解密消息
func DecryptMsg(encryptMsg, encodingKey string) []byte {
// 1.将加密消息转换成字节流
byteArr, _ := hex.DecodeString(encryptMsg)
// 2.解密
key := []byte(encodingKey)
cipherText, _ := AESDecrypt(key, key[:aes.BlockSize], byteArr)
return cipherText
}
func AESDecrypt(key []byte, iv []byte, ciphertext []byte) ([]byte, error) {
if len(ciphertext) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
if len(ciphertext)%aes.BlockSize != 0 {
return nil, errors.New("ciphertext is not a multiple of the block size")
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
mode := cipher.NewCBCDecrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
mode.CryptBlocks(plaintext, ciphertext)
if len(plaintext)%aes.BlockSize != 0 {
return nil, errors.New("ciphertext is not a multiple of the block size")
}
return unpad(plaintext)
}
func unpad(paddedtext []byte) ([]byte, error) {
le := len(paddedtext)
pad := paddedtext[len(paddedtext)-1]
if (le-int(pad)) > le || (le-int(pad)) < 0 {
return []byte{}, errors.New(fmt.Sprintf("unpadding fail (le-int(pad)) > le:%v (le-int(pad)) < 0):%v", (le - int(pad)) > le, (le - int(pad)) < 0))
}
tmp := paddedtext[:len(paddedtext)-int(pad)]
return tmp, nil
}
Golang 示例代码如下:
// 本示例用于演示如何搭建一个简易 HTTP 服务接收 RoomKit 服务端事件回调并打印至控制台。
// 本示例对应的回调地址为 “http://{本机IP}:8080/callback”。
package main
import (
"crypto/sha1"
"fmt"
"net/http"
"net/url"
"sort"
)
// 回调密钥,替换成从 RoomKit 管理后台获取的 callbackSecret。
const callbackSecret = "123abc......"
// 签名算法
func GenSign(callbackSecret, timestamp, nonce string) string {
strs := make([]string, 0, 3)
strs = append(strs, callbackSecret)
strs = append(strs, timestamp)
strs = append(strs, nonce)
sort.Strings(strs)
var s string
for _, str := range strs {
s += str
}
sha := sha1.New()
sha.Write([]byte(s))
return fmt.Sprintf("%x", sha.Sum(nil))
}
// 回调handle
type callbackHandle struct {
}
func (h *callbackHandle) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
w.WriteHeader(405)
return
}
// 解析参数
vals, err := url.ParseQuery(req.URL.RawQuery)
if err != nil {
fmt.Println(err)
w.WriteHeader(500)
return
}
nonce := vals.Get("nonce")
timestamp := vals.Get("timestamp")
signature := vals.Get("signature")
// 签名验证
if GenSign(callbackSecret, timestamp, nonce) != signature {
fmt.Println("invalid signature")
w.WriteHeader(403)
return
}
// 本示例中消息体未加密
// 读取body并打印,例如:{
// "event_type": 1,
// "room_id": "19827033659",
// "timestamp": 1614149165898
//}
body := make([]byte, req.ContentLength)
req.Body.Read(body)
fmt.Println(string(body))
w.WriteHeader(200)
}
func main() {
http.Handle("/callback", &callbackHandle{})
http.ListenAndServe(":8080", nil)
}

联系我们
文档反馈