高级 Token 鉴权

更新时间: 2024/03/15 16:58:01

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

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

鉴权原理

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

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

位数 二进制表示 十进制数字
(privilege 参数的值)
权限含义
第 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. 获取 permSecret的值。

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

    子功能配置.png

    复制权限密钥.png

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

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

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

请参考云信在 GitHub 上提供的示例代码,在您的应用服务器上生成 NERtc Token 和 permissionKey。示例代码的地址如下:

语言 示例代码 关键函数
Java 生成 Token-Java getPermissionKey
Go 生成 Token-Go GetPermissionKey
Node.js 生成 Token-Nodejs GetPermissionKey
PHP 生成 Token-PHP getPermissionKey
Python 生成 Token-Python get_permission_key
C++ 生成 Token-C++ getPermissionKey
C#(dotnet) 生成 Token-C# GetPermissionKey

生成 NERtc permissionKey 的关键参数说明如下表所示。

参数 类型 描述
channelName String RTC 房间名称。channelName 可以为空, 表示该 uid 可以使用这个 token 加入任意房间。
permSecret String 权限密钥。请从云信控制台获取对应 permKeySecret,具体请参见获取 permSecret 的值。
复制权限密钥.png
uid Long 用户在您应用中的 ID,请在您的业务服务器上自行管理并维护。
privilege Integer 权限等级。取值范围[1,63],具体参数的含义请参见鉴权原理。例如,设置为 63 表示拥有全部权限。
ttlSec Integer permissionKey 过期时间,单位为秒,最大为 86400 秒(1 天)。
appKey String 请登录网易云信控制台查看您的应用对应的AppKey,具体请参见创建应用并获取 AppKey

以 GO 语言为例, permissionKey 的计算代码如下:

package token

import (
	"bytes"
	"compress/zlib"
	"crypto/hmac"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"time"
)
// GetPermissionKey 根据传入的参数生成权限密钥并返回
func (t *TokenServer) GetPermissionKey(channelName, permSecret string, uid uint64, privilege uint8, ttlSec int64) (string, error) {
	curTime := time.Now().Unix()
	return t.getPermissionKeyWithCurrentTime(channelName, permSecret, uid, privilege, ttlSec, curTime)
}

func (t *TokenServer) getPermissionKeyWithCurrentTime(channelName, permSecret string, uid uint64, privilege uint8, ttlSec, curTime int64) (string, error) {
	permKeyMap := make(map[string]interface{})
	permKeyMap["appkey"] = t.AppKey
	permKeyMap["uid"] = uid
	permKeyMap["cname"] = channelName
	permKeyMap["privilege"] = privilege
	permKeyMap["expireTime"] = ttlSec
	permKeyMap["curTime"] = curTime

	// 计算 checksum
	permKeyMap["checksum"] = hmacsha256(t.AppKey, fmt.Sprintf("%d", uid), fmt.Sprintf("%d", curTime),
		fmt.Sprintf("%d", ttlSec), channelName, 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
	}

	// 编码为base64格式
	return base64Endoding.EncodeToString(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))
}

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

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

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

场景一 用户加入房间

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

示例代码如下:

    //加入房间
    nertc::NERtcJoinChannelOptions options;
    options.permission_key = (char *)perm_key.c_str();
    int res = nrtc_engine_->joinChannel(token_.c_str(), room_name.c_str(), cur_my_uid_, options);

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

场景二 用户角色变更

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

示例代码如下:

//更新权限密钥
std::string key = GetPermissonKey();  //向业务服务器重新申请一个 permissionKey
rtc_engine_->updatePermissionKey(key.c_str());

//SDK返回回调
void NRTCEngine::onUpdatePermissionKey(const char *key, NERtcErrorCode error, int timeout) {
    //ToDO
}

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

示例代码如下:

    // 收到 onPermissionKeyWillExpire 回调时,向业务服务器重新申请一个 permissionKey,
    // 并调用 updatePermissionKey 将新的 permissionKey 传递给 SDK,建议异步处理 
    void onPermissionKeyWillExpire() /*async*/ {
        std::string key = GetPermissonKey(); //向业务服务器重新申请一个 permissionKey
        return rtc_engine_->updatePermissionKey(key.c_str());
    }

    // onUpdatePermissionKey 返回本人的 updatePermissionKey 结果
    void onUpdatePermissionKey(const char *key, NERtcErrorCode error, int timeout) {
        (void)key;//对应更新的权限key
        (void)error;//更新结果 
            //kNERtcNoError
            //kNERtcErrChannelPermissionKeyError
            //kNERtcErrChannelPermissionKeyTimeout 
        (void)timeout;//超时时间,单位秒,成功时有效 
    }
  • 若在 permissionKey 过期前仍未完成上述操作,则 SDK 会触发 onDisconnect 回调,返回 kNERtcErrChannelPermissionKeyTimeout 错误码,同时客户端会与音视频服务器断开连接。若用户需要再次加入房间,则需要从您的业务服务器获取新的 tokenpermissionKey 并调用 joinChannel 方法,再使用新的 tokenpermissionKey 重新加入房间。

示例代码如下:

    //收到 onDisconnect(kErrorPermissionKeyTimeout)回调时,代表已经权限超时,被服务器提出房间
    void onDisconnect(NERtcErrorCode reason) {
        if (reason == nertc::kNERtcErrChannelPermissionKeyTimeout)
        {
            // 权限超时,已经被踢出房间
        }
    }

相关参考

权限密钥的生命周期图

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

错误码

错误码(ErrorCode 错误原因

kNERtcErrUserPermKeyAuthFailed = 30121

可能原因包括:

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