高级 Token 鉴权

更新时间: 2023/06/29 02:51:51

网易云信音视频通话和互动直播产品中,鉴权方式分为安全模式和调试模式。如果您在控制台中为指定应用开启了安全模式,则对应应用的用户在加入房间时,需要通过 Token 进行身份校验;其中您可以选择在您的 App 中实现基础 Token 鉴权或者高级 Token 鉴权,若您的应用中存在对安全性要求较高的语音或视频通话场景,或者对观众上麦有权限控制的场景,建议您选择高级 Token 鉴权模式,以有效避免客户端遭遇破解攻击的问题。两种鉴权模式的区别如下表。

身份校验项 基础 Token 鉴权 高级 Token 鉴权
检查 App ID
校验用户加入房间的权限
校验用户创建房间的权限 ×
校验用户发送音、视频流的权限 ×

鉴权原理

开启用户权限控制后,当用户加入房间时,云信服务器会在校验 Token 的同时也校验权限密钥(permissionKey,若符合约定的算法规则,则会允许用户加入房间且赋予指定的发流权限。

PermissionKey 中的权限使用了一个 byte 的前六个比特位来表示,其中每个比特位均代表一个权限,权限列表如下:

位数 二进制表示 十进制数字(输入参数) 权限含义
第 1 位 0000 0001 1 仅有发送音频流的权限
第 2 位 0000 0010 2 仅有发送视频流的权限
第 3 位 0000 0100 4 仅有订阅音频流的权限
第 4 位 0000 1000 8 仅有订阅视频流的权限
第 5 位 0001 0000 16 仅有创建房间的权限
第 6 位 0010 0000 32 仅有加入房间的权限

因此可以推算出,表示无权限的十进制参数为 0,表示仅有订阅音、视频流权限的十进制参数为 12,表示既可以发送又可以订阅视频流权限的十进制参数为 15,表示拥有全部权限的十进制参数为 63。

  • Token 由云信服务器或者由您自行计算生成,具体生成逻辑请参考获取 Token
  • 用户权限由您的应用服务器在生成 permissionKey 时确定,所以您需要在您的服务器管理好用户权限列表。

高级 Token 鉴权的流程如下:

sequenceDiagram
    participant 应用层
    participant 应用服务器
    participant NERtcSDK
    participant 云信服务器
    

    Note over 应用层, 云信服务器: 申请 permissionKey
    应用层 ->> 应用服务器: 请求 uid 对应的 permissionKey
    应用服务器 -->> 应用层: 生成并返回 uid 对应的 permissionKey

    Note over 应用层, 云信服务器: 加入房间时鉴权
    应用层 ->> NERtcSDK: 调用 joinChannel 方法加入房间并传入 token 和 permissionKey
    NERtcSDK ->> 云信服务器: 校验 token 和 permissionKey
    云信服务器 -->> NERtcSDK: 校验通过并返回成功加入房间的回调
    NERtcSDK -->> 应用层: 返回成功加入房间的回调

实现鉴权流程

步骤一 开通高级 Token 鉴权功能

您需要为指定应用设置用户权限控制,开通高级 Token 鉴权功能。

  • 若您开启了用户权限控制开关,使用该 App Key 的所有用户都必须要在加入房间时传入权限密钥参数,否则无法正常加入房间。
  • 若您关闭了用户权限控制开关,云信服务器默认不会校验权限密钥。
  1. 登录网易云信控制台

  2. 在首页单击指定应用名称。

  3. 产品总览区域,单击音视频通话 2.0 产品选项卡中的功能配置

    功能配置.png

  4. 单击基础功能页签,在鉴权方式区域中,单击编辑,鉴权方式选择安全模式(高级token鉴权),并单击保存

    Token鉴权.png

    高级Token鉴权.png

  5. 在弹出对话框中单击确定

  6. 单击子功能配置,单击 permKeySecret 右侧的按钮复制 permKeySecret。

    子功能配置.png

    复制权限密钥.png

    在您的服务器生成 permissionKey 时,需要用到该 permKeySecret。具体请参见下文步骤二示例代码中的 GenPermKey

步骤二 在您的服务器生成 permissionKey 并下发给客户端

为了防止客户端遭遇破解攻击的问题,由 permissionKey 定义的权限控制只能由您的服务器计算并返回给您的客户端。

网易云信提供 GO 语言版本的 permissionKey 的计算代码,您可以直接下载并集成至您的服务器,计算代码如下:

package permkey

import (
"bytes"
"compress/zlib"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
)

// GenPermKey 生成权限密钥函数,返回加密后的字符串和错误信息
func GenPermKey(uid, expireTime int64, appkey, cname, permSecret string, privilege uint32) (string, error) {
return genPermKey(uid, expireTime, appkey, cname, permSecret, privilege)
}

func genPermKey(uid, expireTime int64, appkey, cname, permSecret string, privilege uint32) (string, error) {
// 创建权限密钥映射
permKeyMap := make(map[string]interface{})
permKeyMap["appkey"] = appkey
permKeyMap["uid"] = uid
permKeyMap["cname"] = cname
permKeyMap["privilege"] = privilege
permKeyMap["expireTime"] = expireTime
curTime := time.Now().Unix()
permKeyMap["curTime"] = curTime

// 计算签名
permKeyMap["checksum"] = hmacsha256(appkey, fmt.Sprintf("%d", uid), fmt.Sprintf("%d", curTime), fmt.Sprintf("%d", expireTime), cname, permSecret, fmt.Sprintf("%d", privilege))

// 将映射转换为JSON格式
data, err := json.Marshal(permKeyMap)
if err != nil {
return "", err
}

// 压缩数据
var b bytes.Buffer
w := zlib.NewWriter(&b)
if _, err = w.Write(data); err != nil {
return "", err
}
if err = w.Close(); err != nil {
return "", err
}

// 编码数据并返回
return base64urlEncode(b.Bytes()), nil
}

// hmacsha256 计算签名函数,返回签名字符串
func hmacsha256(appidStr, uidStr, curTimeStr, expireTimeStr, cname, permSecret, privilegeStr string) string {
var contentToBeSigned string
contentToBeSigned = "appkey:" + appidStr + "\n"
contentToBeSigned += "uid:" + uidStr + "\n"
contentToBeSigned += "curTime:" + curTimeStr + "\n"
contentToBeSigned += "expireTime:" + expireTimeStr + "\n"
contentToBeSigned += "cname:" + cname + "\n"
contentToBeSigned += "privilege:" + privilegeStr + "\n"

h := hmac.New(sha256.New, []byte(permSecret))
h.Write([]byte(contentToBeSigned))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

// base64urlEncode 编码数据函数,返回编码后的字符串
func base64urlEncode(data []byte) string {
str := base64.StdEncoding.EncodeToString(data)
str = strings.Replace(str, "+", "*", -1)
str = strings.Replace(str, "/", "-", -1)
str = strings.Replace(str, "=", "_", -1)
return str
}

PermissionKey 的有效期默认为 24 小时,您可以根据业务调整,区间为 [1s,24h]。

步骤三 将 token 和 permissionKey 传递给 SDK 以对用户进行鉴权

您可以在用户加入房间用户角色变更用户权限密钥需要更新时,将权限控制参数传递给 SDK 以对用户进行鉴权,三种场景下鉴权的具体实现方式如下。

场景一 用户加入房间

在用户调用 joinChannel 方法加入房间时,需要设置 tokenNERtcJoinChannelOptions 中的 permissionKey
适用于加入房间前就明确用户权限的情况。

示例代码如下:

//加入房间
String channelName;
String token;
long uid;
String permissionKey;

NERtcJoinChannelOptions channelOptions = new NERtcJoinChannelOptions();
channelOptions.permissionKey = permissionKey;

int ret = NERtcEx.getInstance().joinChannel(token, channelName, uid, channelOptions);

加入房间时,用户 ID 和房间名称需要与申请 Token 时使用的用户 ID 和房间名称一致。

场景二 用户角色变更

在用户需要连麦时,需要将自己的角色从观众切换到主播,此时需要再次校验用户的发流权限。因此在用户调用 setClientRole 方法切换角色时,需要调用 updatePermissionKey 方法设置新的权限密钥。

示例代码如下:

//更新权限密钥
String permissionKey = getServerToken();
NERtcEx.getInstance().updatePermissionKey(permissionKey);

//SDK返回回调
//收到updatePermissionKey回调
void onUpdatePermissionKey(String key, int error, int timeout) {
    
}

场景三 用户权限密钥需要更新

示例代码如下:

//收到 onPermissionKeyWillExpire 回调时,向业务服务器重新申请一个 permissionKey,并调用 updatePermissionKey 将新的 permissionKey 传递给 SDK
public void onPermissionKeyWillExpire() {
    Log.i(TAG, "密钥即将过期");
    String permissionKey = getServerToken(); //向业务服务器重新申请一个 permissionKey
    NERtcEx.getInstance().updatePermissionKey(permissionKey);
}
  • 若在 permissionKey 过期前仍未完成上述操作,则 SDK 会触发 onDisconnect 回调,返回 ENGINE_ERROR_CHANNEL_PERMISSION_KEY_TIMEOUT 错误码,同时客户端会与音视频服务器断开连接。若用户需要再次加入房间,则需要从您的业务服务器获取新的 tokenpermissionKey 并调用 joinChannel 方法,再使用新的 tokenpermissionKey 重新加入房间。

示例代码如下:

//收到 onDisconnect(ENGINE_ERROR_CHANNEL_PERMISSION_KEY_TIMEOUT)回调时,向业务服务器重新申请一个 permissionKey,并调用joinChannel重新加入房间
public void onDisconnect(int reason) {
    Log.i(TAG, "onDisconnect reason: " + reason);
    if (reason == ENGINE_ERROR_CHANNEL_PERMISSION_KEY_TIMEOUT) {
        String permissionKey = getServerToken(); //向业务服务器重新申请一个 permissionKey
        channelOptions.permissionKey = permissionKey;
        postToUI->(NERtcEx.getInstance().joinChannel(token, channelName, uid, channelOptions););
    }
}

相关参考

权限密钥的生命周期图

native权限密钥的生命周期.png

错误码

错误码(ErrorCode 错误原因

ENGINE_ERROR_USER_PERM_KEY_AUTH_FAILED = 30121

可能原因包括:

  • 应用已开通高级 Token 鉴权,但用户鉴权时没有传入 permissionKey 参数。
  • 应用已开通高级 Token 鉴权,且用户鉴权时传入了 permissionKey 参数,但用户没有对应权限。
  • 应用已开通高级 Token 鉴权,且用户鉴权时传入了 permissionKey 参数,但用户的 permissionKey 已失效。
ENGINE_ERROR_CHANNEL_PERMISSION_KEY_ERROR = 30901 权限密钥错误。
ENGINE_ERROR_CHANNEL_PERMISSION_KEY_TIMEOUT = 30902 权限密钥超时。
ENGINE_ERROR_ChannelNoPublishPermission = 30911 用户无发流权限。
ENGINE_ERROR_ChannelNoSubscribePermission = 30912 用户无订阅权限。
此文档是否对你有帮助?
有帮助
去反馈
  • 鉴权原理
  • 实现鉴权流程
  • 步骤一 开通高级 Token 鉴权功能
  • 步骤二 在您的服务器生成 permissionKey 并下发给客户端
  • 步骤三 将 token 和 permissionKey 传递给 SDK 以对用户进行鉴权
  • 场景一 用户加入房间
  • 场景二 用户角色变更
  • 场景三 用户权限密钥需要更新
  • 相关参考
  • 权限密钥的生命周期图
  • 错误码