高级 Token 鉴权
更新时间: 2024/08/20 15:20:22
网易云信音视频通话和互动直播产品中,鉴权方式分为安全模式和调试模式。如果您在控制台中为指定应用开启了安全模式,则对应应用的用户在加入房间时,需要通过 Token 进行身份校验。
其中您可以选择在您的 App 中实现基础 Token 鉴权或者高级 Token 鉴权,两种鉴权模式的区别如下表。
身份校验项 | 基础 Token 鉴权 | 高级 Token 鉴权 |
---|---|---|
检查 App ID | ✔ | ✔ |
校验用户加入房间的权限 | ✔ | ✔ |
校验用户创建房间的权限 | ✖ | ✔ |
校验用户发送音、视频流的权限 | ✖ | ✔ |
因此若您的应用中存在对安全性要求较高的语音或视频通话场景,或者对观众上麦有权限控制的场景,建议您选择高级 Token 鉴权模式,以有效避免客户端遭遇破解攻击的问题。
鉴权原理
开启用户权限控制后,当用户加入房间时,云信服务器会在校验 Token 的同时也校验权限密钥(permKey
),若符合约定的算法规则,则会允许用户加入房间且赋予指定的发流权限。
PermKey
中的权限使用了一个 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。
- 用户权限由您的应用服务器在生成
permKey
时确定,所以您需要在您的服务器管理好用户权限列表。
高级 Token 鉴权的流程如下:
sequenceDiagram
participant 应用层
participant 应用服务器
participant NERtcSDK
participant 云信服务器
Note over 应用层, 云信服务器: 申请 permKey
应用层 ->> 应用服务器: 请求 uid 对应的 permKey
应用服务器 -->> 应用层: 生成并返回 uid 对应的 permKey
Note over 应用层, 云信服务器: 加入房间时鉴权
应用层 ->> NERtcSDK: 调用 joinChannel 方法加入房间并传入 token 和 permKey
NERtcSDK ->> 云信服务器: 校验 token 和 permKey
云信服务器 -->> NERtcSDK: 校验通过并返回成功加入房间的回调
NERtcSDK -->> 应用层: 返回成功加入房间的回调
实现鉴权
步骤一 开通高级 Token 鉴权功能
您需要为指定应用设置用户权限控制,开通高级 Token 鉴权功能。
- 若您开启了用户权限控制开关,使用该 App Key 的所有用户都必须要在加入房间时传入权限密钥参数,否则无法正常加入房间。
- 若您关闭了用户权限控制开关,云信服务器默认不会校验权限密钥。
-
登录网易云信控制台。
-
在首页单击指定应用名称。
-
在产品总览区域,单击音视频通话 2.0 产品选项卡中的功能配置。
-
单击基础功能页签,在鉴权方式区域中,单击编辑,鉴权方式选择安全模式(高级token鉴权),并单击保存。
-
在弹出对话框中单击确定。
-
获取
permSecret
的值。单击子功能配置,单击 permKeySecret 右侧的按钮复制 permKeySecret。
在您的服务器生成 permissionKey 时,需要用到该 permKeySecret,对应
GetPermissionKey
中的permSecret
参数的值。具体请参见下文步骤二的示例代码。
步骤二 在您的服务器生成 permKey 并下发给客户端
为了防止客户端遭遇破解攻击的问题,由 permKey
定义的权限控制只能由您的服务器计算并返回给您的客户端。
请参考云信在 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 和 permKey 传递给 SDK 以对用户进行鉴权
您可以在用户加入房间或用户角色变更时,将权限控制参数传递给 SDK 以对用户进行鉴权,两种场景下鉴权的具体实现方式如下。
场景一 用户加入房间
在用户调用 join
方法加入房间时,需要设置 JoinOptions
中的 token
和 permKey
。
适用于加入房间前就明确用户权限的情况。
示例代码如下:
//加入房间携带permKey进行校验
rtc.client.join({
channelName,
uid: uid,
token: '',//开通安全模式校验,需要填写token
permKey:'',//开通短时token控制,并且有权限控制,需要填写permkey
}).then(()=>{
console.log('加入房间成功')
}).catch(err => {
console.error(`加入房间失败: erroCode: ${err.code}, errorMessage: ${err.message}`)
})
//相关错误码请参考本文末尾的错误码信息
//刷新permKey
const newPermKey = ''
rtc.client.updatePermKey(newPermKey).then(()=>{
console.log('刷新permKey成功')
}).catch(err => {
console.error(`刷新permKey失败: erroCode: ${err.code}, errorMessage: ${err.message}`)
})
//相关错误码请参考本文末尾的错误码信息
//过期前30s反馈,服务器通知
rtc.client.on('permkey-will-expire', evt => {
console.warn('permKey 即将过期')
});
//超时了,服务器会把你踢出房间
rtc.client.on('permkey-timeout', evt => {
console.warn(`===== ${evt.uid} permkey超时被踢出房间`)
})
rtc.client.on('error', (type) => {
console.error('===== 发生错误事件:', type)
if (type === 'SOCKET_ERROR') {
addLog('==== 网络异常,已经退出房间')
} else if (type === 'no-publish-audio-permission') {
console.error('permkey控制,没有发布音频的权限')
} else if (type === 'no-publish-audio-slave-permission') {
console.error('permkey控制,没有发布音频辅流的权限')
} else if (type === 'no-publish-video-permission') {
addLog(`permkey控制,没有发布视频的权限`)
} else if (type === 'no-publish-screen-permission') {
console.error('permkey控制,没有发布屏幕共享的权限')
} else if (type === 'no-subscribe-audio-permission') {
console.error('permkey控制,没有订阅音频的权限')
} else if (type === 'no-subscribe-audio-slave-permission') {
console.error('permkey控制,没有订阅音频辅流的权限')
} else if (type === 'no-subscribe-video-permission') {
console.error('permkey控制,没有订阅视频的权限')
} else if (type === 'no-subscribe-screen-permission') {
console.error('permkey控制,没有订阅屏幕共享的权限')
}
})
- 加入房间时,用户 ID 和房间名称需要与申请 Token 时使用的用户 ID 和房间名称一致。
- 在
permKey
过期前 30 s,SDK 会触发permkey-will-expire
回调,此时用户客户端可以从您的业务服务器获取新的permKey
并调用updatePermKey
方法将新生成的permKey
传递给 SDK。 - 若在
permKey
过期前仍未完成上述操作,则 SDK 会触发permkey-timeout
回调,同时客户端会与音视频服务器断开连接。若用户需要再次加入房间,则需要从您的业务服务器获取新的token
和permKey
并调用join
方法,再使用新的token
和permKey
重新加入房间。
场景二 用户角色变更
在用户需要连麦时,需要将自己的角色从观众切换到主播,此时需要再次校验用户的发流权限。因此在用户调用 setClientRole
方法切换角色时,需要调用 updatePermKey
方法设置新的权限密钥。
示例代码如下:
//刷新 permKey
const newPermKey = ''
rtc.client.updatePermKey(newPermKey).then(()=>{
console.log('刷新permKey成功')
}).catch(err => {
console.error(`刷新permKey失败: erroCode: ${err.code}, errorMessage: ${err.message}`)
})
//相关错误码请参考本文末尾的错误码信息
相关参考
权限密钥的生命周期图
错误码
若鉴权失败,SDK 会返回相关错误码,错误码如下:
错误码(code) | errorMessage | 错误原因 |
---|---|---|
4011 | kNERtcErrUserPermKeyAuthFailed | 应用已开通高级 Token 鉴权,但用户鉴权时没有传入 permKey 参数。 |
4012 | 应用已开通高级 Token 鉴权,且用户鉴权时传入了 permKey 参数,但用户没有对应权限。 | |
4013 | 应用已开通高级 Token 鉴权,且用户鉴权时传入了 permKey 参数,但用户的 permKey 已失效。 | |
4014 | 其他鉴权失败错误。 |
若相关接口调用失败,SDK 会在返回的 client.on(error
) 回调中同时返回错误信息。用户权限导致的相关错误信息如下表所示。
错误信息 | 错误原因 |
---|---|
no-publish-audio-permission | 用户无发布音频主流(麦克风)的权限。 |
no-publish-audio-slave-permission | 用户无发布音频辅流(屏幕共享系统声卡声音)的权限。 |
no-publish-video-permission | 用户无发布视频主流(摄像头)的权限。 |
no-publish-screen-permission | 用户无发布视频辅流(屏幕共享画面)的权限。 |
no-subscribe-audio-permission | 用户无订阅其他人的音频主流(麦克风)的权限。 |
no-subscribe-audio-slave-permission | 用户无订阅其他人的音频辅流(屏幕共享系统声卡声音)的权限。 |
no-subscribe-video-permission | 用户无订阅其他人的视频主流(摄像头)的权限。 |
no-subscribe-screen-permission | 用户无订阅其他人的视频辅流(屏幕共享画面)的权限。 |