高级 Token 鉴权
更新时间: 2024/08/20 15:20:22
网易云信音视频通话和互动直播产品中,鉴权方式分为安全模式和调试模式。如果您在控制台中为指定应用开启了安全模式,则对应应用的用户在加入房间时,需要通过 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 的所有用户都必须要在加入房间时传入权限密钥参数,否则无法正常加入房间。
- 若您关闭了用户权限控制开关,云信服务器默认不会校验权限密钥。
-
登录网易云信控制台。
-
在首页单击指定应用名称。
-
在产品总览区域,单击音视频通话 2.0 产品选项卡中的功能配置。
-
单击基础功能页签,在鉴权方式区域中,单击编辑,鉴权方式选择安全模式(高级token鉴权),并单击保存。
-
在弹出对话框中单击确定。
-
获取
permSecret
的值。单击子功能配置,单击 permKeySecret 右侧的按钮复制 permKeySecret。
在您的服务器生成 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 的值。 |
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 以对用户进行鉴权,三种场景下鉴权的具体实现方式如下。
场景一 用户加入房间
在用户调用 joinChannelWithToken
方法加入房间时,需要设置 token
和 NERtcJoinChannelOptions
中的 permissionKey
。
适用于加入房间前就明确用户权限的情况。
示例代码如下:
//加入房间
NERtcJoinChannelOptions *channeOption = [[NERtcJoinChannelOptions alloc]init];
channeOption.permissionKey = permissionKey;
[[NERtcEngine sharedEngine] joinChannelWithToken:token channelName:channelName myUid:myUid completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd, uint64_t uid) {
}];
加入房间时,用户 ID 和房间名称需要与申请 Token 时使用的用户 ID 和房间名称一致。
场景二 用户角色变更
在用户需要连麦时,需要将自己的角色从观众切换到主播,此时需要再次校验用户的发流权限。因此在用户调用 setClientRole
方法切换角色时,需要调用 updatePermissionKey
方法设置新的权限密钥。
示例代码如下:
//更新权限密钥
NSSTring *permissionKey = [self getPermissionKey]; //向业务服务器重新申请一个 permissionKey
[coreEngine updatePermissionKey:permissionKey];
//SDK返回回调
//收到updatePermissionKey:permissionKey 的回调结果
- (void)onNERtcEngineUpdatePermissionKey:(NSString *)key error:(NERtcError)error timeout:(NSUInteger)timeout {
}
场景三 用户权限密钥需要更新
- 在
permissionKey
过期前 30 s,SDK 会触发onNERtcEnginePermissionKeyWillExpire
回调,此时用户客户端可以从您的业务服务器获取新的permissionKey
并调用updatePermissionKey
方法将新生成的permissionKey
传递给 SDK,更新成功后 SDK 会触发onNERtcEngineUpdatePermissionKey
回调。
示例代码如下:
//收到 onPermissionKeyWillExpire 回调时,向业务服务器重新申请一个 permissionKey,并调用 updatePermissionKey 将新的 permissionKey 传递给 SDK
- (void)onNERtcEnginePermissionKeyWillExpire {
NSSTring *permissionKey = [self getPermissionKey]; //向业务服务器重新申请一个 permissionKey
[[NERtcEngine sharedEngine] updatePermissionKey:permissionKey];
}
- 若在
permissionKey
过期前仍未完成上述操作,则 SDK 会触发onNERtcEngineDidDisconnectWithReason
回调,返回kNERtcErrChannelPermissionKeyTimeout
错误码,同时客户端会与音视频服务器断开连接。若用户需要再次加入房间,则需要从您的业务服务器获取新的token
和permissionKey
并调用joinChannelWithToken
方法,再使用新的token
和permissionKey
重新加入房间。
相关参考
权限密钥的生命周期图
错误码
错误码(ErrorCode | 错误原因 |
---|---|
kNERtcErrUserPermKeyAuthFailed = 30121 |
可能原因包括:
|
kNERtcErrChannelPermissionKeyError = 30901 | 权限密钥错误。 |
kNERtcErrChannelPermissionKeyTimeout = 30902 | 权限密钥超时。 |
kNERtcErrChannelNoPublishPermission = 30911 | 用户无发流权限。 |
kNERtcErrChannelNoSubscribePermission = 30912 | 用户无订阅权限。 |