AccessToken 升级指南
本文介绍如何使用 AccessToken 鉴权以及如何升级至 AccessToken2。
如果你此前并未使用过 AccessToken 鉴权,可只参考使用 Token 鉴权文档使用 AccessToken2,无需了解 AccessToken。
使用 AccessToken 鉴权
鉴权原理
下图展示了鉴权的基本流程:
- 客户端根据需要,向 App 服务端申请 Token。
- App 服务端生成并返回 Token。
- 客户端以 UID、频道名以及获取到的 Token 加入频道。
- 声网平台读取该 Token 中包含的信息,并进行校验。
- 客户端收到加入频道成功回调,并获取用户 UID。
- Token 最大有效期为 24 小时。当即将过期时,客户端会收到 Token 即将过期的回调。
- 此时,如果客户端需要继续进行音视频互动,需要申请新的 Token。
- App 服务端生成并返回 Token。
- 客户端更新 Token。
Token 过期时,SDK 会触发 Token 过期回调。收到该回调后,客户端需要从服务器获取新的 Token,再使用新的 Token 重新加入频道。
你需要自行实现步骤 1、2、3、7、8、9 的代码逻辑。
Token 包含以下信息:
- 你在声网控制台创建项目时生成的 App ID
- 你的项目的 App 证书
- 频道名
- 用户 ID
- 用户权限,如是否能发流或收流
- Token 过期的 Unix 时间戳
前提条件
开始前,请确保你的项目或使用的声网产品满足如下条件:
-
一个有效的声网账户及已开启 App 证书的声网项目。请参考开通服务。
-
Golang 1.14 或以上版本,GO111MODULE 设置为开启。
注意如果你使用的是 Go 1.16 或以上版本,GO111MODULE 已默认开启。详情请参考 New module changes in Go 1.16。
实现鉴权流程
本节介绍如何使用声网提供的代码生成并提供 Token,对用户及其权限进行校验。
获取 App ID 及 App 证书
本节介绍如何获取生成 Token 所需的安全信息,如你的项目的 App ID 及 App 证书。
获取 App ID
成功创建项目后,声网会给每个项目自动分配一个 作为项目唯一标识。你可以直接在总览页面的项目信息栏,点击 App ID 右侧的复制按钮来获取当前项目的 App ID。
你需要保存好复制的 App ID,后续调用 API 进行初始化等操作时需要传入你获取的 App ID。
获取 App 证书
参考以下步骤获取 App 证书:
-
在声网控制台的项目管理页面,找到你的项目,点击配置。
-
点击主要证书下面的复制图标,即可获取项目的 App 证书。
部署 Token 服务器
Token 需要在你的服务端部署生成。当客户端发送请求时,服务端部署的 Token Generator 会生成相应的 Token,再发送给客户端。
本节展示如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。
此示例服务器仅用于演示,请勿用于生产环境中。
-
创建一个
server.go
文件,然后贴入如下代码。将其中的<Your App ID>
和<Your App Certificate>
替换为你的 App ID 和 App 证书。Gopackage main
import (
rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder"
"fmt"
"log"
"net/http"
"time"
"encoding/json"
"errors"
"strconv"
)
type rtc_int_token_struct struct{
Uid_rtc_int uint32 `json:"uid"`
Channel_name string `json:"ChannelName"`
Role uint32 `json:"role"`
}
var rtc_token string
var int_uid uint32
var channel_name string
var role_num uint32
var role rtctokenbuilder.Role
// 使用 RtcTokenBuilder 来生成 RTC Token
func generateRtcToken(int_uid uint32, channelName string, role rtctokenbuilder.Role){
// 填入项目 App ID
appID := "<Your App ID>"
// 填入 App 证书
appCertificate := "<Your App Certificate>"
// Token 过期的时间,单位为秒
// 为作演示,在此将过期的时间戳设为 40 秒。当 Token 即将过期时,你可以看到客户端自动更新 Token 的过程
expireTimeInSeconds := uint32(40)
// 获取当前时间戳
currentTimestamp := uint32(time.Now().UTC().Unix())
// Token 过期的 Unix 时间戳
expireTimestamp := currentTimestamp + expireTimeInSeconds
result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Token with uid: %s\n", result)
fmt.Printf("uid is %d\n", int_uid )
fmt.Printf("ChannelName is %s\n", channelName)
fmt.Printf("Role is %d\n", role)
}
rtc_token = result
}
func rtcTokenHandler(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
w.Header().Set("Access-Control-Allow-Headers", "*");
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
if r.Method != "POST" && r.Method != "OPTIONS" {
http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
return
}
var t_int rtc_int_token_struct
var unmarshalErr *json.UnmarshalTypeError
int_decoder := json.NewDecoder(r.Body)
int_err := int_decoder.Decode(&t_int)
if (int_err == nil) {
int_uid = t_int.Uid_rtc_int
channel_name = t_int.Channel_name
role_num = t_int.Role
switch role_num {
case 0:
// 已废弃。RoleAttendee 和 RolePublisher 的权限相同
role = rtctokenbuilder.RoleAttendee
case 1:
role = rtctokenbuilder.RolePublisher
case 2:
role = rtctokenbuilder.RoleSubscriber
case 101:
// 已废弃。RoleAdmin 和 RolePublisher 的权限相同
role = rtctokenbuilder.RoleAdmin
}
}
if (int_err != nil) {
if errors.As(int_err, &unmarshalErr){
errorResponse(w, "Bad request. Wrong type provided for field " + unmarshalErr.Value + unmarshalErr.Field + unmarshalErr.Struct, http.StatusBadRequest)
} else {
errorResponse(w, "Bad request.", http.StatusBadRequest)
}
return
}
generateRtcToken(int_uid, channel_name, role)
errorResponse(w, rtc_token, http.StatusOK)
log.Println(w, r)
}
func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(httpStatusCode)
resp := make(map[string]string)
resp["token"] = message
resp["code"] = strconv.Itoa(httpStatusCode)
jsonResp, _ := json.Marshal(resp)
w.Write(jsonResp)
}
func main(){
// 使用 int 型 uid 生成 RTC Token
http.HandleFunc("/fetch_rtc_token", rtcTokenHandler)
fmt.Printf("Starting server at port 8082\n")
if err := http.ListenAndServe(":8082", nil); err != nil {
log.Fatal(err)
}
} -
go.mod
文件定义导入路径及依赖项。运行如下命令行来为你的 Token 服务器创建go.mod
文件:Go$ go mod init sampleServer
-
运行如下命令行安装依赖。你可以使用 Go 镜像进行加速,例如 https://goproxy.cn。
Go$ go get
-
运行如下命令行启动服务器:
Go$ go run server.go
使用 Token 对用户鉴权
本节以 Web 客户端为例,展示如何使用 Token 对客户端的用户进行鉴权。
此示例仅用于演示,请勿用于生产环境中。
-
创建一个项目文件夹,其中包含如下文件:
index.html
:用户界面client.js
:使用声网 RTC Web SDK 4.x 的 App 逻辑
Shell|
|-- index.html
|-- client.js -
在
index.html
中加入以下代码,创建用户界面:html<html>
<head>
<title>Token demo</title>
</head>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<body>
<h1>Token demo</h1>
<script src="https://download.agora.io/sdk/release/AgoraRTC_N.js"></script>
<script src="./client.js"></script>
</body>
</html> -
将如下代码贴入
client.js
文件中,实现客户端鉴权逻辑。- 将
<Your App ID>
替换为你的 App ID。该 App ID 必须与 Token 生成代码中的 App ID 一致。 - 将
<Your Host URL and port>
替换为你部署好的本地 Golang 服务器的主机 URL 和端口,如10.53.3.234:8082
。
JavaScriptvar rtc = {
// 设置本地音频轨道和视频轨道
localAudioTrack: null,
localVideoTrack: null,
};
var options = {
// 传入 App ID
appId: "<Your App ID>",
// 传入频道名
channel: "ChannelA",
// 设置用户为 host (可发流) 或 audience(仅可收流)
role: "host"
};
// 从 Golang 服务器获取 Token
function fetchToken(uid, channelName, tokenRole) {
return new Promise(function (resolve) {
axios.post('http://<Your Host URL and port>/fetch_rtc_token', {
uid: uid,
channelName: channelName,
role: tokenRole
}, {
headers: {
'Content-Type': 'application/json; charset=UTF-8'
}
})
.then(function (response) {
const token = response.data.token;
resolve(token);
})
.catch(function (error) {
console.log(error);
});
})
}
async function startBasicCall() {
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
client.setClientRole(options.role);
const uid = 123456;
// 将获取到的 Token 赋值给 join 方法中的 Token 参数,然后加入频道
let token = await fetchToken(uid, options.channel, 1);
await client.join(options.appId, options.channel, token, uid);
rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack();
await client.publish([rtc.localAudioTrack, rtc.localVideoTrack]);
const localPlayerContainer = document.createElement("div");
localPlayerContainer.id = uid;
localPlayerContainer.style.width = "640px";
localPlayerContainer.style.height = "480px";
document.body.append(localPlayerContainer);
rtc.localVideoTrack.play(localPlayerContainer);
console.log("publish success!");
client.on("user-published", async (user, mediaType) => {
await client.subscribe(user, mediaType);
console.log("subscribe success");
if (mediaType === "video") {
const remoteVideoTrack = user.videoTrack;
const remotePlayerContainer = document.createElement("div");
remotePlayerContainer.textContent = "Remote user " + user.uid.toString();
remotePlayerContainer.style.width = "640px";
remotePlayerContainer.style.height = "480px";
document.body.append(remotePlayerContainer);
remoteVideoTrack.play(remotePlayerContainer);
}
if (mediaType === "audio") {
const remoteAudioTrack = user.audioTrack;
remoteAudioTrack.play();
}
client.on("user-unpublished", user => {
const remotePlayerContainer = document.getElementById(user.uid);
remotePlayerContainer.remove();
});
});
// 收到 token-privilege-will-expire 回调时,从服务器重新申请一个 Token,并调用 renewToken 将新的 Token 传给 SDK
client.on("token-privilege-will-expire", async function () {
let token = await fetchToken(uid, options.channel, 1);
await client.renewToken(token);
});
// 收到 token-privilege-did-expire 回调时,从服务器重新申请一个 Token,并调用 join 重新加入频道
client.on("token-privilege-did-expire", async function () {
console.log("Fetching the new Token")
let token = await fetchToken(uid, options.channel, 1);
console.log("Rejoining the channel with new Token")
await client.join(options.appId, options.channel, token, uid);
});
}
startBasicCall()在上述代码示例中,你可以看到 Token 与客户端的以下代码逻辑有关:
- 调用
join
方法,使用 Token、用户 ID 和频道名加入频道。用户 ID 和频道名必须和用于生成 Token 的用户 ID 和频道名一致。 - 在 Token 过期前 30 秒,SDK 会触发
token-privilege-will-expire
回调。收到该回调后,客户端需要从服务器获取新的 Token 并调用renewToken
方法将新生成的 Token 传给 SDK。 - Token 过期时,SDK 会触发
token-privilege-did-expire
回调。收到该回调后,客户端需要从服务器获取新的 Token 并调用join
方法,再使用新的 Token 重新加入频道。
- 将
-
用支持的浏览器打开
index.html
文件,可以看到客户端执行以下操作:- 成功加入频道。
- 每隔 10 秒更新 Token。
参考
本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。
Token 生成器代码
声网在 GitHub 上提供一个开源的 AgoraDynamicKey 仓库,支持使用 C++、Java、Go 等语言在你自己的服务器上生成 Token。
语言 | 算法 | 核心方法 | 示例代码 |
---|---|---|---|
C++ | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilderSample.cpp |
Go | HMAC-SHA256 | buildTokenWithUid | sample.go |
Java | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilderSample.java |
Node.js | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilderSample.js |
PHP | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilderSample.php |
Python | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilderSample.py |
Python3 | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilderSample.py |
API 参考
本节介绍生成 Token 的 API 参数和描述。以 C++ 为例:
static std::string buildTokenWithUid(
const std::string& appId,
const std::string& appCertificate,
const std::string& channelName,
uint32_t uid,
UserRole role,
uint32_t privilegeExpiredTs = 0);
参数 | 描述 |
---|---|
appId | 你在声网控制台创建项目时生成的 App ID。 |
appCertificate | 你的项目的 App 证书。 |
channelName | 频道名称,长度在 64 个字节以内。以下为支持的字符集范围:
|
uid | 待鉴权用户的用户 ID 32 位无符号整数,范围为 1 到 (2³² - 1), 并保证唯一性。 如不需对用户 ID 进行鉴权,即客户端使用任何 uid 都可加入频道,请把 uid 设为 0。 |
role | 用户权限,分为发流用户和接收用户。参数决定用户是否能在频道中发流。
|
privilegeExpiredTs | Token 过期的 Unix 时间戳,单位为秒。该值为当前时间戳和 Token 有效期的总和。 例如,如果你将 privilegeExpiredTs 设为当前时间戳再加 600 秒,则 Token 会在 10 分钟内过期。 Token 的最大有效期为 24 小时。 如果你将此参数设为 0,或时间长度超过 24 小时,Token 有效期依然为 24 小时。 |
升级至 AccessToken2
升级服务器
-
将导入
rtctokenbuilder
声明替换为以下代码,并且删除导入"time"
的声明:Goimport (
// 替换
// rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder"
rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
"fmt"
"log"
"net/http"
// 删除
// "time"
"encoding/json"
"errors"
"strconv"
) -
删除生成时间戳的声明,添加
tokenExpireTimeInSeconds
和privilegeExpireTimeInSeconds
相关代码:Go// expireTimeInSeconds := uint32(40)
// 获取当前时间戳
// currentTimestamp := uint32(time.Now().UTC().Unix())
// Timestamp when the token expires.
// expireTimestamp := currentTimestamp + expireTimeInSeconds
tokenExpireTimeInSeconds := uint32(40)
privilegeExpireTimeInSeconds := uint32(40) -
更新
tokenbuilder
方法代码:Go// 更新前代码
// result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
// 更新后代码
// 将 BuildTokenWithUID 更改为 BuildTokenWithUid
// 将 expireTimestamp 更改为 tokenExpireTimeInSeconds 和 privilegeExpireTimeInSeconds
result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds) -
删除不支持的角色:
Goswitch role_num {
// 删除
// case 0:
// 已废弃。RoleAttendee 和 RolePublisher 的权限相同
// role = rtctokenbuilder.RoleAttendee
case 1:
role = rtctokenbuilder.RolePublisher
case 2:
role = rtctokenbuilder.RoleSubscriber
// 删除
// case 101:
// 已废弃。RoleAdmin 和 RolePublisher 的权限相同
// role = rtctokenbuilder.RoleAdmin
}
升级客户端
如果 Web 客户端使用的 RTC Web SDK 的版本为 4.8.0 或更高版本,则无需升级客户端。AccessToken2 兼容的 SDK 版本详见 AccessToken2 对 SDK 的兼容性要求。
在 index.html
中做如下修改:
<html>
<head>
<title>Token demo</title>
</head>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<body>
<h1>Token demo</h1>
<!-- 删除 -->
<!-- <script src="https://download.agora.io/sdk/release/AgoraRTC_N.js"></script> -->
<!-- 将 SDK 升级至与 AccessToken2 兼容的版本。-->
<script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.8.0.js"></script>
<script src="./client.js"></script>
</body>
</html>
测试服务器
如果你同时在使用 RTC 拓展产品或服务,如云录制、旁路推流,声网推荐你在升级到 AccessToken2 前联系技术支持。
为了测试 AccessToken2 服务器是否正常运行,使用支持的浏览器打开 index.html
文件并进行以下操作:
- 成功加入频道。
- 每 10 秒更新一次 Token。