实时音视频
  • 平台类型
  • 框架 / 引擎
  • iOS
  • Android
  • macOS
  • Windows
  • Linux
  • Web
  • 小程序

登录房间鉴权

更新时间:2021-05-12 11:11

1 登录房间基本流程

登录房间流程如下:

时序图
  1. App 与业务后台建立通信,获取 Token 信息。
  2. App 调用 loginRoom 接口登录 ZEGO 服务器,传入 Token 信息,验证通过后,完成登录。
  3. App 会保持与 ZEGO 服务器的长连接,处理发送或接收的消息。
  4. App 调用 logoutRoom 接口退出 ZEGO 服务器。

2 login_token 信息

“login_token” 信息为标准 JSON 格式的字符串,具体为:

{
    "ver": 1,
    "hash": “adfdkjakka1213aa”,
    "nonce": “1934892311”,
    "expired": 1531393997 //单位:秒
}

“login_token” 字段说明如下:

参数 类型 描述
ver int 版本号,填 “1” 即可。
hash String hash = MD5(app_id+app_sign_32+userID+nonce+expired),MD5 最终结果为 32 字节的小写 hex 编码。
nonce String

随机字符串,长度为 16 字节。

需要保证同一 “userID” 在 Token 有效期内不重复,建议按 guid 生成。

expired int64 Token 失效时间,为 Unix 时间戳,单位:秒。

“hash” 计算中各字段说明如下:

参数 类型 说明
app_id int ZEGO 给开发者分配的 AppID,唯一标识一个应用。
app_sign_32 String

通过 app_sign 运算获得,获取前面 32 字节即为 app_sign_32。

(具体算法请参考 3 login_token 生成示例代码

userID String 与初始化(ZegoExpressEngine)时设置的 “userID” 保持一致。
nonce String 随机字符串,与 json 中的 “nonce” 保持一致。
expired int64 token 失效时间,与 json 中的 “expired” 保持一致。
  1. login_token 传输过程中,会经过 base64 加密,加密后再传入到 loginRoom 接口中。
  2. 每次登录房间都需要重新获取 login_token。
  3. 业务方开发的 Web 或小程序应用需要和业务后台建立一种安全通讯和鉴权机制,业务方使用自有的账户体系或第三方认证体系的登录完成后,业务方小程序和业务后台交互获取该 login_token,AppSecret 是存储在业务后台的。

3 login_token 生成示例代码

需要业务后台自行实现生成 Token 信息的业务逻辑。

  • nodejs 语言
let crypto = require('crypto');


function md5(content) {
return crypto.createHash('md5').update(content).digest("hex")
}

// 计算此刻后 minute分钟后的时间
function expiredTime(minute) {
return Math.floor(new Date().getTime() / 1000 + minute * 60);
}

/**
* 拉流端获取登录token
* @param appId 即构分配的appId
* @param appSign 即构分配的appSign
* @param userID 这里的userID需要和websdk前端初始化时传入的userID一致,
// 否则校验失败(因为这里的userID是为了校验和前端传进来的userID是否一致)
* @return
*/
function getToken(appId, appSign, userID, minute) {

let nonce = new Date().getTime().toString();
let time = expiredTime(minute)
let appSign32 = appSign.replace(/0x/g, '').replace(/,/g, '').substring(0, 32);
console.log('appSign:' + time + ' ' + appSign32 + ' ' + nonce);

if (appSign32.length < 32) {
console.log('private sign erro!!!!');
return null;
}

let sourece = md5(appId + appSign32 + userID + nonce + time);
console.log('hash:' + sourece);


let jsonStr = JSON.stringify({
'ver': 1,
'expired': time,
'nonce': nonce,
'hash': sourece
});
console.log('json', jsonStr);
return Buffer.from(jsonStr).toString('base64');
}
  • go 语言
package util

import (
    "crypto/md5"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "math/rand"
    "time"
    "unsafe"
)

type tokenInfo struct {
    Ver     int    `json:"ver"`
    Hash    string `json:"hash"`
    Nonce   string `json:"nonce"`
    Expired int64  `json:"expired"`
}

// appid,app_sign 是控制台上申请账号的时候获得,这里的 userID 需要和 web sdk 前端传入的 userID 一致,
// 否则校验失败(因为这里的 userID 是为了校验和前端传进来的 userID 是否一致)。
func GetToken(appid uint32, app_sign string, userID string, expired_add int64) (ret string, err error) {
    nonce := RandString(16)
    expired := time.Now().Unix() + expired_add //单位:秒

    if len(app_sign) < 32 {
        return "", fmt.Errorf("app_sign wrong")
    }

    app_sign_32 := app_sign[:32]
    source := fmt.Sprintf("%d%s%s%s%d", appid, app_sign_32, userID, nonce, expired)
    sum := GetMd5String(source)

    token := tokenInfo{
        Ver:     1,
        Nonce:   nonce,
        Expired: expired,
        Hash:    sum,
    }

    buf, err := json.Marshal(token)
    if err != nil {
        return "", err
    }

    encodeString := base64.StdEncoding.EncodeToString(buf)
    return encodeString, nil
}

func RandString(n int) string {
    letterBytes := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    var letterIdxMask int64 = 1<<6 - 1
    b := make([]byte, n)
    r := rand.NewSource(time.Now().UnixNano())
    for i, cache, remain := n-1, r.Int63(), 10; i >= 0; {
        if remain == 0 {
            cache, remain = r.Int63(), 10
        }
        b[i] = letterBytes[int(cache&letterIdxMask)%len(letterBytes)]
        i--
        cache >>= 6
        remain--
    }
    return *(*string)(unsafe.Pointer(&b))
}

func GetMd5String(str string) string {
    data := []byte(str)
    return fmt.Sprintf("%x", md5.Sum(data))
}
  • php 语言
<?php
// app_id,app_sign 是控制台上申请账号的时候获得,这里的 userID 需要和 web sdk 前端传入的userID 一致,
// 否则校验失败(因为这里的 userID 是为了校验和前端传进来的 userID 是否一致)。
function getToken(int $app_id, string $app_sign, string $userID, int $expired_add)
{
    $nonce = uniqid();
    $expired = time() + $expired_add; //单位:秒

    if(strlen($app_sign) < 32) {
        return false;
    }
    $app_sign_32 = substr($app_sign, 0, 32);

    $source = $app_id.$app_sign_32.$userID.$nonce.$expired;
    $sum = md5($source);

    $tokenInfo = [
        'ver' => 1,
        'hash'  => $sum,
        'nonce' => $nonce,
        'expired' => $expired,
    ];
    $token = base64_encode(json_encode($tokenInfo));
    return $token;
}
  • java 语言
package com.example.test;

import org.json.JSONObject;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.UUID;
import org.apache.commons.codec.binary.Base64;

public class ZegoUtils {

    public static void main(String[] args) {
        String appid = ""; //ZEGO 分配的 appId,需从 ZEGO 控制台获取
        String appSign = ""; //ZEGO 分配的 appSign,需从 ZEGO 控制台获取
        String userID = "xxx"; //这里的 userID 需要和 web sdk 前端传入的 userID 一致,
        //否则校验失败(因为这里的 userID 是为了校验和前端传进来的 userID 是否一致)。
        String Token = getToken(appid, appSign, userID);
        System.out.println("--Token--:" + Token);
    }

    /**
     * 拉流端获取登录 Token
     * @param appId  ZEGO分配的 appId
     * @param appSign ZEGO分配的 appSign
     * @param userID //这里的 userID 需要和 web sdk 前端传入的 userID 一致,
    //否则校验失败(因为这里的 userID 是为了校验和前端传进来的 userID 是否一致)。
     * @return
     */
    public static String getToken(String appId, String appSign, String userID) {

        String nonce = UUID.randomUUID().toString().replaceAll("-", "");
        long time = new Date().getTime() / 1000 + 30 * 60;
        String appSign32 = new String(appSign.substring(0, 32));
        System.out.println("appSign:" + time + "    " + appSign32 + "    " + nonce);

        if (appSign32.length() < 32) {
            System.out.println("private sign erro!!!!");
            return null;
        }

        String sourece = getPwd(appId + appSign32 + userID + nonce + time);
        System.out.println("hash:" + sourece);

        JSONObject json = new JSONObject();
        json.put("ver", 1);
        json.put("hash", sourece);
        json.put("nonce", nonce);
        json.put("expired", time); //Unix 时间戳,单位为秒
        Base64 base64 = new Base64();
        System.out.println("json" + json.toString());
        return base64.encodeAsString(json.toString().getBytes());
    }

    /**
     * 获取 MD5 加密
     * @param pwd 需要加密的字符串
     * @return String 字符串 加密后的字符串
     */
    public static String getPwd(String pwd) {
        try {
            // 创建加密对象
            MessageDigest digest = MessageDigest.getInstance("md5");

            // 调用加密对象的方法,加密的动作已经完成
            byte[] bs = digest.digest(pwd.getBytes());
            // 接下来,我们要对加密后的结果,进行优化,按照 mysql 的优化思路走
            // mysql 的优化思路:
            // 第一步,将数据全部转换成正数:
            String hexString = "";
            for (byte b: bs) {
                // 第一步,将数据全部转换成正数:
                int temp = b & 255;
                // 第二步,将所有的数据转换成16进制的形式
                // 注意:转换的时候注意if正数>=0&&<16,那么如果使用 Integer.toHexString(),可能会造成缺少位数
                // 因此,需要对 temp 进行判断
                if (temp < 16 && temp >= 0) {
                    // 手动补上一个“0”
                    hexString = hexString + "0" + Integer.toHexString(temp);
                } else {
                    hexString = hexString + Integer.toHexString(temp);
                }
            }
            return hexString;
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "";
    }

}
  • python 语言
import base64
import hashlib
import json
import sys
import time
import random

def get_app_sign_32(app_sign):

    app_sign_32 = app_sign[:32]

    return app_sign_32

def get_hash(app_id, app_sign_32, userID, nonce, expired):
    src = str(app_id) + app_sign_32 + userID + nonce + str(expired)
    md5 = hashlib.md5()
    md5.update(src.encode('utf-8'))
    result = md5.hexdigest()

    return result

def get_token(app_id, app_sign, userID, expired):
    """ 生成ZEGO小程序SDK或者ZEGO WebRTC SDK的登录token字符串

    Args:
        app_id: ZEGO分配的AppID
        app_sign: ZEGO分配的App Sign
        userID: 登录用户id,这里的userID需要和web sdk前端传入的userID一致,否则校验失败(因为这里的userID是为了校验和前端传进来的userID是否一致)。
        nonce: 算法要求的随机串
        expired: Token到期unix时间戳

    Returns:
        返回token字符串

    Raises:
        无

    """

    expired = int(time.time()) + expired


    nonce = random.sample('zyxwvutsrqponmlkjihgfedcba',13)
    nonce = ''.join(nonce)
    app_sign_32 = get_app_sign_32(app_sign)

    hashStr = get_hash(app_id, app_sign_32, userID, nonce, expired)

    login_token = {'ver': 1, 'hash': hashStr, 'nonce': nonce, 'expired': expired}
    login_token_json = json.dumps(login_token)
    print('login token json:', login_token_json)

    bytesStr = base64.b64encode(login_token_json.encode('utf-8'))
    login_token_str = bytesStr.decode('utf-8')

    return login_token_str