文档中心
RoomKit RoomKit
文档中心
体验 App
SDK 中心
API 中心
常见问题
代码市场
进入控制台
立即注册
登录
中文站 English
  • 文档中心
  • RoomKit
  • 服务端 API
  • RoomKit 服务端 API
  • 使用服务端事件回调

使用服务端事件回调

更新时间:2022-11-17 11:46

1 功能简介

RoomKit 服务端针对房间和白板的一些事件,例如用户进入房间、用户共享白板等事件开放了事件通知,当这些事件被触发时,RoomKit 服务端会通过回调的方式通知开发者,开发者后台可以基于这些事件来进行房间的管理。

2 接入参数

2.1 配置/获取接入参数

RoomKit 服务端事件回调相关的接入参数如下:

参数 说明 示例
回调地址 用于接收推送事件的地址,支持 HTTP 或者 HTTPS,由开发者提供,必须保证外网可访问。 http://roomkit-api.developer.im/
callbackSecret 用于生成签名的 key,由 ZEGO 生成提供给开发者。 D54euRgW8H4jZvuCTF1gFdj
EncodingKey 消息体加密密钥,使用 AES 对称加密,长度必须 >=16 个字符。由开发者提供,如果不提供或者提供的参数不符合要求,则不对消息体进行加密。 N8PkYt0FO1R4OqwmYiPT8PykQ4wQEtAcBaJVR
  1. 登录 RoomKit 管理后台。
  2. 在 “项目管理 > 项目详情 > 项目配置” 页面填写回调地址,回调地址可以是域名形式或者 IP:Port 形式。
  3. 在 “鉴权信息” 页面获取 callbackSecret。
  4. 如果开发者希望回调消息的消息体为密文,请联系 ZEGO 技术支持配置 EncodingKey。

2.2 请求示例

请求方式:POST

请求地址:http://roomkit-api.developer.im/?signature=ASDFQWEXZCVAQFASDFASDFSS&timestamp=13500001234&nonce=123412323

请求消息体:

// 未加密
{
    "event_type": 1,
    "room_id": "19827033659",
    "timestamp": 1614149165898
}

// 加密后 16 进制字符串
abcoapadjandakwdnwakdwa...

参数说明:

参数 说明
signature 签名信息。
timestamp 时间戳。
nonce 随机数字符串。

消息体携带的参数的含义请参考 RoomKit 服务端 API 中的 服务端事件回调。

开发者后台收到回调通知之后,需要进行签名验证,防止数据被篡改。然后再对消息体进行解密(如果开了消息体加密的话),拿到通知事件。

3 签名验证

3.1 校验方法

签名校验步骤如下:

/Pics/server/Callback/encryptVerifyProcess.png

  1. 拼接签名字符串。

    字段 说明
    nonce 随机数字符串,回调请求中的参数。
    timestamp 时间戳,回调请求中的参数。
    callbackSecret 由 ZEGO 生成提供给开发者。

    将以上参数按照字典序排序,排序之后进行拼接。

  1. 对步骤 1 拼接的字符串进行 sha1 计算得到签名信息,然后将签名信息转换成 16 进制字符串。

    示例如下:

    byteArr = sha1(str)  // hash
    signStr = byteToHexStr(byteArr) // 将hash信息转换为16进制字符串
  2. 对比从 URL 得到的签名,发现两者一致,签名通过,说明没被篡改。

    if signature == signStr {
            pass
    } else {
            fail
    }

3.2 示例代码

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

3.3 输出示例

$timestamp = 1470820198;
$nonce = 123412;
$callbackSecret = 'secret';

排序拼接后需要加密的原始串为:1234121470820198secret
加密的结果为:5bd59fd62953a8059fb7eaba95720f66d19e4517

4 消息体解密(可选)

4.1 解密方法

如果开发者配置了 EncodingKey,回调请求的消息体会以密文形式发送,开发者需要解密消息体后才能获取到 JSON 格式的通知事件。

加解密采用了基于 AES 的对称加密算法。AES 采用 CBC 模式,IV 初始向量大小为 16 字节,取 EncodingKey 前 16 字节。加密消息为 16 进制字符串,需首先将它转换成字节流,然后进行 AES 解密。

解密步骤如下:

  1. 将加密消息转换成字节流

    byteArr = HexStrToByte(encrypt_msg)
  2. 解密

    text = AESDecrypt(EncodingKey, EncodingKey[:16], byteArr)

4.2 示例代码

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
}

5 搭建简易 HTTP 服务

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)
}
本篇目录
  • 免费试用
  • 提交工单
    咨询集成、功能及报价等问题
    电话咨询
    400 1006 604
    咨询客服
    微信扫码,24h在线

    联系我们

  • 文档反馈