小度技术支持文档

小度技术支持文档


rtcToken生成算法

<h2>小度云rtcToken v2生成算法</h2> <h3>一、rtcToken版本为002,token信息解析出来后包含如下几个字段,生成token除以下信息外,还需要appSecret信息:</h3> <table> <thead> <tr> <th>信息</th> <th>类型</th> <th>含义</th> </tr> </thead> <tbody> <tr> <td>appId</td> <td>string</td> <td>这个token是哪个appId使用的,对应的由相应的业务服务器生成</td> </tr> <tr> <td>uid</td> <td>string</td> <td>用户业务系统中唯一的uid</td> </tr> <tr> <td>createTime</td> <td>uint32</td> <td>这个token的生成时间,unix时间戳,单位为秒。在rtc系统里面,同一个用户新创建的rtcToken会导致之前创建的token失效</td> </tr> <tr> <td>expireTime</td> <td>uint32</td> <td>这个token的过期时间,unix时间戳,单位为秒,expireTime必须比createTime大。在连接rtc长连接服务时,rtc服务器会判断这个token是否超时,超时也拒绝连接。已经连接上来的长连接在到达这个超时时间时不会被断开。</td> </tr> <tr> <td>random</td> <td>uint32</td> <td>一个随机数,在生产token时带上这个随机数</td> </tr> </tbody> </table> <h2> 二、token组成:</h2> <h3>2.1 token字符串分为header长度(headerLength),header,token信息(tokenInfo)三部分:</h3> <table> <thead> <tr> <th>字段</th> <th>说明</th> </tr> </thead> <tbody> <tr> <td>headerLength</td> <td>定长为两个字节,表明header字段的长度(长度计算不包括headerLength)。所以header最长为99个字节</td> </tr> <tr> <td>header</td> <td>header信息以-分隔,第一个字段固定为version,第二个字段固定为appid,后续按版本可以扩展其他字段。</td> </tr> <tr> <td>tokenInfo</td> <td>token信息,是做了base64urlSafe编码的字符串,具体包含createTime,expireTime,random,uid,签名等信息</td> </tr> </tbody> </table> <h3>2.2 tokenInfo部分生成:</h3> <p>tokenInfo按照下面方式首先生成一个byte流(buffer):</p> <pre><code class="language-html">createTime按照uin32 big endian方式写入buffer。 expireTime按照uin32 big endian方式写入buffer。 random按照uin32 big endian方式写入buffer。 uid字符串写入buffer之前,先写入一个uint16 big endian的长度(字符串的长度),然后再把uid字符串的byte流写入。 sign信息,为下面的字符串进行拼接,然后取md5后的byte数组(这里是byte数组,不是16进制显示的ascii字符串)。先写入一个uint16 big endian的长度到buffer,然后把生成后的md5 byte流写入buffer。 createTime转换为10进制字符串 expireTime转换为10进制字符串 random转换为10进制字符串 uid字符串 appId字符串 appSecret字符串</code></pre> <p><code>上面的字节流做base64 urlSafe编码,即可生成tokenInfo。</code></p> <h2>三、例子:</h2> <p>假设appId是10000, appSecret是thisisaexample, uid是hellotom, token createTime 1579412009, token expireTime是1606752000,random是1277422310,则生成token信息如下:</p> <p>09002-10000XiPqKV_FFwBMI-rmAAhoZWxsb3RvbQAQ5zpBq_FGwR2A7cMmfxYZAw== 其中前两个字节是headerLength,数值09表明header占9个字节。 根据headerLength,后面九个字节为header,内容为 002-10000, 其中002为版本号,10000为appId 剩下部分为tokenInfo, base64 urlSafe编码:XiPqKV_FFwBMI-rmAAhoZWxsb3RvbQAQ5zpBq_FGwR2A7cMmfxYZAw==,解码后即为上面说明的tokenInfo的组成字段。</p> <h2>四、调试工具和demo</h2> <h3>4.1 调试工具</h3> <p><a href="https://dueros.baidu.com/business/emp/view/restdebug?pageType=getRtcToken&amp;toolsType=restApi">https://dueros.baidu.com/business/emp/view/restdebug?pageType=getRtcToken&amp;toolsType=restApi</a></p> <h3>4.2 demo</h3> <h4>4.2.1 PHP版本</h4> <pre><code class="language-php"> &amp;lt;?php class RTCToken { private $rtcAppId; private $rtcAppSecret; private $duration; private $tokenVersion = &amp;quot;002&amp;quot;; function __construct($rtcAppId, $rtcAppSecret, $duration) { $this-&amp;gt;rtcAppId = $rtcAppId; $this-&amp;gt;rtcAppSecret = $rtcAppSecret; $this-&amp;gt;duration = $duration; } public function buildToken($uid) { $binaryString = &amp;quot;&amp;quot;; $createTime = time(); $expireTime = $createTime + $this-&amp;gt;duration; $random = mt_rand(1, 1294967295); $binaryString .= pack(&amp;quot;N&amp;quot;, $createTime); $binaryString .= pack(&amp;quot;N&amp;quot;, $expireTime); $binaryString .= pack(&amp;quot;N&amp;quot;, $random); $binaryString .= pack(&amp;quot;n&amp;quot;, strlen($uid)); for($i=0; $i&amp;lt;strlen($uid); $i++) { $binaryString .= pack(&amp;quot;C&amp;quot;, ord($uid[$i])); } $signStr = &amp;quot;$createTime$expireTime$random$uid&amp;quot;; $sign = $this-&amp;gt;generateSignature($signStr); $binaryString .= pack(&amp;quot;n&amp;quot;, strlen($sign)); for($i=0; $i&amp;lt;strlen($sign); $i++) { $binaryString .= pack(&amp;quot;C&amp;quot;, ord($sign[$i])); } $header = $this-&amp;gt;tokenVersion . &amp;quot;-&amp;quot; . $this-&amp;gt;rtcAppId; $headerLen = strlen($header); $token = sprintf(&amp;quot;%02d&amp;quot;,$headerLen) . $header . $this-&amp;gt;base64url_encode($binaryString, true); return $token; } private function generateSignature($string) { $str = $string . $this-&amp;gt;rtcAppId . $this-&amp;gt;rtcAppSecret; return hash(&amp;quot;md5&amp;quot;, $str, true); } private function base64url_encode($data, $pad = null) { $data = str_replace(array(&amp;quot;+&amp;quot;, &amp;quot;/&amp;quot;), array(&amp;quot;-&amp;quot;, &amp;quot;_&amp;quot;), base64_encode($data)); if (!$pad) { $data = rtrim($data, &amp;quot;=&amp;quot;); } return $data; } } $tokenObj = new RTCToken(&amp;quot;thisisappId&amp;quot;, &amp;quot;thisisappSecret&amp;quot;, 3*30*24*3600); echo $tokenObj-&amp;gt;buildToken(&amp;quot;thisisuserid&amp;quot;) . &amp;quot;\n&amp;quot;; </code></pre> <h4>4.2.2 GOLANG版本</h4> <pre><code class="language-go"> package main import ( &amp;quot;bytes&amp;quot; &amp;quot;crypto/md5&amp;quot; &amp;quot;encoding/base64&amp;quot; &amp;quot;encoding/binary&amp;quot; &amp;quot;fmt&amp;quot; &amp;quot;io&amp;quot; &amp;quot;math/rand&amp;quot; &amp;quot;strconv&amp;quot; &amp;quot;testing&amp;quot; &amp;quot;time&amp;quot; ) const ( RtcTokenVersion2 = &amp;quot;002&amp;quot; ) // RtcTokenV2 token结构,此结构只在服务器使用,不暴露给客户端 type RtcTokenV2 struct { AppID string //AppSecret string // 此appid对应的secret,不能提供给客户端,只能在服务器保存 UID string // 用户系统中唯一的uid,由调用方提供 CreateTime uint32 // token生成时间,unix时间戳,秒 ExpireTime uint32 // token过期时间,unix时间戳,秒 Random uint32 // 随机数 } // GetVersion 实现RtcToken接口方法 func (token *RtcTokenV2) GetVersion() string { return RtcTokenVersion2 } // GetAppId 实现RtcToken接口方法 func (token *RtcTokenV2) GetAppId() string { return token.AppID } // GetUid 实现RtcToken接口方法 func (token *RtcTokenV2) GetUid() string { return token.UID } // GetCreateTime 实现RtcToken接口方法 func (token *RtcTokenV2) GetCreateTime() uint32 { return token.CreateTime } // GetExpireTime 实现RtcToken接口方法 func (token *RtcTokenV2) GetExpireTime() uint32 { return token.ExpireTime } // NewRtcTokenV2 创建rtc token // aliveSeconds是token的保活时长,最好不要太长,代码中没做限制 func NewRtcTokenV2(AppID, uid string, aliveSeconds uint32) RtcTokenV2 { // 缺省过期时间为1天 createTime := uint32(time.Now().Unix()) expireTime := createTime + aliveSeconds rand := uint32(random(1, 1294967295)) return RtcTokenV2{AppID, uid, createTime, expireTime, rand} } // BuildToken 生成token字符串 func (token *RtcTokenV2) BuildToken(appSecret string) (string, error) { ret := &amp;quot;&amp;quot; version := RtcTokenVersion2 buf := new(bytes.Buffer) if err := packUint32(buf, token.CreateTime); err != nil { return ret, err } if err := packUint32(buf, token.ExpireTime); err != nil { return ret, err } if err := packUint32(buf, token.Random); err != nil { return ret, err } if err := packString(buf, token.UID); err != nil { return ret, err } // 计算签名 sign := token.generateSignature(appSecret) // 写入签名部分 if err := packString(buf, sign); err != nil { return ret, err } bytesContent := buf.Bytes() // 前两个字节是header长度,header中不同字段以-分割,版本1第一个字段是version,第二个字段为appid,header长度要小于100字节 header := version + &amp;quot;-&amp;quot; + token.AppID headerLen := len(header) headerLenStr := fmt.Sprintf(&amp;quot;%02d&amp;quot;, headerLen) ret = headerLenStr + header + base64.URLEncoding.EncodeToString(bytesContent) fmt.Sprintf(&amp;quot;generate token %s&amp;quot;, ret) return ret, nil } // util方法,生成签名 func (token *RtcTokenV2) generateSignature(appSecret string) string { val := strconv.FormatUint(uint64(token.CreateTime), 10) + strconv.FormatUint(uint64(token.ExpireTime), 10) + strconv.FormatUint(uint64(token.Random), 10) + token.UID + token.AppID + appSecret md5Bytes := md5SumBytes(val) return string(md5Bytes) } // pack func packUint16(w io.Writer, n uint16) error { return binary.Write(w, binary.BigEndian, n) } func packUint32(w io.Writer, n uint32) error { return binary.Write(w, binary.BigEndian, n) } func packString(w io.Writer, s string) error { err := packUint16(w, uint16(len(s))) if err != nil { return err } _, err = w.Write([]byte(s)) return err } func random(min int, max int) int { rand.Seed(time.Now().UnixNano()) return rand.Intn(max-min) + min } // md5SumBytes 计算string的md5值,二进制字节格式 func md5SumBytes(orig string) []byte { srcBuf := new(bytes.Buffer) srcBuf.Write([]byte(orig)) SrcBytes := srcBuf.Bytes() bytesSig := md5.Sum(SrcBytes) return bytesSig[:] } func TestExample(t *testing.T) { // token := RtcTokenV2{&amp;quot;10000&amp;quot;, &amp;quot;hellotom&amp;quot;, 1579412009, 1606752000, 1277422310} token := NewRtcTokenV2(&amp;quot;10000&amp;quot;, &amp;quot;hellotom&amp;quot;, 3*30*24*3600) // 推荐过期时间三个月 tokenStr, err := token.BuildToken(&amp;quot;thisisaexample&amp;quot;) t.Log(&amp;quot;token &amp;quot;, tokenStr, &amp;quot;, error &amp;quot;, err) } </code></pre> <h4>4.2.3 java版本</h4> <pre><code class="language-go">// 通过如下链接下载完整的java版demo代码 https://dumi-dueros-bj-tob.cdn.bcebos.com/amis/2022-11/1669348498814/rtcToken_java.zip</code></pre> <h4>4.2.4 C++ 版本</h4> <pre><code class="language-go">// 通过如下链接下载完整的c++版demo代码 https://dumi-dueros-bj-tob.cdn.bcebos.com/amis/2022-11/1669348449751/rtcToken_cpp.zip</code></pre>

页面列表

ITEM_HTML