鉴权是指在用户访问你的系统前,对其进行身份校验。用户在使用声网服务,如加入音视频通话或登录信令系统时,声网使用 Token 对其鉴权。
为提供更好的鉴权体验和安全性保障,声网自 2022 年 8 月 18 日推出新版 Token:AccessToken2。如需从 AccessToken 升级至 AccessToken2,请参考 AccessToken 升级指南。
本文展示如何为 AccessToken2 在服务端部署一个 Token 生成器,以及如何搭建一个使用 Token 鉴权的客户端。
下图展示了鉴权的基本流程:
RTM Token 在 app 服务器上生成,其最长有效期为 24 小时。当用户从你的 app 客户端登录到 RTM 系统时,声网平台会读取该 Token 中包含的信息,并进行校验。Token 包含以下信息:
开始前,请确保你的项目或使用的声网产品满足如下条件:
SDK 类型 | 支持 AccessToken2 鉴权的首个版本 |
---|---|
RTM Android SDK | 1.5.0 |
RTM iOS SDK | 1.5.0 |
RTM macOS SDK | 1.5.0 |
RTM Web SDK | 1.5.0 |
RTM Windows SDK | 1.5.0 |
RTM Linux SDK | 1.5.0 |
本节介绍如何使用声网提供的代码生成并提供 Token,对用户及其权限进行校验。
本节介绍如何获取生成 Token 所需的安全信息,如你的项目的 App ID 及 App 证书。
声网会给每个项目自动分配一个 App ID 作为项目唯一标识。
在声网控制台的项目管理页面,找到你的项目,点击 App ID 右侧的 图标,即可获取项目的 App ID。
参考以下步骤获取 App 证书:
在声网控制台的项目管理页面,找到你的项目,点击配置。
点击主要证书下面的复制图标,即可获取项目的 App 证书。
Token 需要在你的服务端部署生成。当客户端发送请求时,服务端部署的 Token Generator 会生成相应的 Token,再发送给客户端。
本节展示如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。
创建一个 server.go
文件,然后贴入如下代码。将其中的 <Your App ID>
和 <Your App Certificate>
替换为你的 App ID 和 App 证书。
package main
import (
rtmtokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtmtokenbuilder2"
"fmt"
"log"
"net/http"
"time"
"encoding/json"
"errors"
"strconv"
)
type rtm_token_struct struct{
Uid_rtm string `json:"uid"`
}
var rtm_token string
var rtm_uid string
func generateRtmToken(rtm_uid string){
appID := "Your_App_ID"
appCertificate := "Your_Certificate"
expireTimeInSeconds := uint32(3600)
currentTimestamp := uint32(time.Now().UTC().Unix())
expireTimestamp := currentTimestamp + expireTimeInSeconds
result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, expireTimestamp)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Rtm Token: %s\n", result)
rtm_token = result
}
}
func rtmTokenHandler(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_rtm_str rtm_token_struct
var unmarshalErr *json.UnmarshalTypeError
str_decoder := json.NewDecoder(r.Body)
rtm_err := str_decoder.Decode(&t_rtm_str)
if (rtm_err == nil) {
rtm_uid = t_rtm_str.Uid_rtm
}
if (rtm_err != nil) {
if errors.As(rtm_err, &unmarshalErr){
errorResponse(w, "Bad request. Please check your params.", http.StatusBadRequest)
} else {
errorResponse(w, "Bad request.", http.StatusBadRequest)
}
return
}
generateRtmToken(rtm_uid)
errorResponse(w, rtm_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(){
// Handling routes
// RTM token from RTM uid
http.HandleFunc("/fetch_rtm_token", rtmTokenHandler)
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 mod init sampleServer
运行如下命令行安装依赖:
$ go get
运行如下命令行启动服务器:
$ go run server.go
本节以 Web 客户端为例,展示如何使用 Token 对客户端的用户进行鉴权。
创建一个项目文件夹,其中包含如下文件:
index.html
:用户界面client.js
:使用 RTM SDK 的 app 逻辑|
|-- index.html
|-- client.js
下载声网 RTM SDK for Web。将 libs
中的 JS 文件保存到你的项目下。
在 index.html
中加入以下代码,创建用户界面。
<path to the JS file>
替换为你上一步保存的 JS 文件的路径。<html>
<head>
<title>RTM Token demo</title>
</head>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<body>
<h1>Token demo</h1>
<script src="<path to the JS file>"></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
。
// login 方法参数
let options = {
token: "",
uid: ""
}
// 是否开启 Token 更新循环
let stopped = false
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
function fetchToken(uid) {
return new Promise(function (resolve) {
axios.post('http://<Your Host URL and port>/fetch_rtm_token', {
uid: uid,
}, {
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 loginRTM()
{
// 你的 app ID
const appID = "<Your App ID>"
// 初始化客户端
const client = AgoraRTM.createInstance(appID)
// 显示连接状态变化
client.on('ConnectionStateChanged', function (state, reason) {
console.log("State changed To: " + state + " Reason: " + reason)
})
// 设置 RTM 用户 ID
options.uid = "1234"
// 获取 Token
options.token = await fetchToken(options.uid)
// 登录 RTM 系统
await client.login(options)
while (!stopped)
{
// 每 30 秒更新一次 Token。此更新频率是为了功能展示。生产环境建议每小时更新一次。
await sleep(30000)
options.token = await fetchToken(options.uid)
client.renewToken(options.token)
let currentDate = new Date();
let time = currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
console.log("Renew RTM token at " + time)
}
}
loginRTM()
在上述代码示例中,你可以看到 Token 与客户端的以下代码逻辑有关:
login
方法,使用 Token 和用户 ID 登录 RTM 系统。用户 ID 必须和用于生成 Token 的用户 ID 一致。renewToken
方法更新 SDK 的 Token。声网建议你定时(例如每小时)从服务端生成 Token 并调用 renewToken
方法更新 SDK 的 Token,保证 SDK 的 Token 一直处于有效状态。用支持的浏览器打开 index.html
文件,进入开发者模式。在控制台可以看到客户端执行以下操作:
renewToken
方法更新 Token。声网在 GitHub 上提供一个开源的 AgoraDynamicKey 仓库,支持使用 C++、Java、Go 等语言在你自己的服务器上生成 Token。
语言 | 算法 | 核心方法 | 示例代码 |
---|---|---|---|
C++ | HMAC-SHA256 | buildToken | RtmTokenBuilder2Sample.cpp |
Go | HMAC-SHA256 | buildToken | sample.go |
Java | HMAC-SHA256 | buildToken | RtmTokenBuilder2Sample.java |
PHP | HMAC-SHA256 | buildToken | RtmTokenBuilder2Sample.php |
Python 2 | HMAC-SHA256 | buildToken | RtmTokenBuilder2Sample.py |
Python 3 | HMAC-SHA256 | buildToken | RtmTokenBuilder2Sample.py |
本节介绍生成 AccessToken2 的 API 参数和描述。以 Golang 为例:
func BuildToken(appId string, appCertificate string, userId string, expire uint32) (string, error) {
token := accesstoken.NewAccessToken(appId, appCertificate, expire)
serviceRtm := accesstoken.NewServiceRtm(userId)
serviceRtm.AddPrivilege(accesstoken.PrivilegeLogin, expire)
token.AddService(serviceRtm)
return token.Build()
}
参数 | 描述 |
---|---|
appId |
你在声网控制台创建项目时生成的 App ID。 |
appCertificate |
你的 App 证书。 |
userId |
用于登录 RTM 系统的用户 ID。你需要自行设定。支持的字符参考 login 方法中的 userId 参数。 |
expire |
RTM Token 过期的 Unix 时间戳,单位为秒。该值为当前时间戳和 Token 有效期的总和。 例如,如果你将 expire 设为当前时间戳再加 600 秒,则 RTM Token 会在 10 分钟内过期。 RTM Token 的最大有效期为 24 小时。 如果你将此参数设为 0,或时间长度超过 24 小时,Token 有效期依然为 24 小时。 |
生成 RTM Token 时填入的用户 ID,需要和登录 RTM 系统时填入的用户 ID 一致。
生成 RTM Token 需要先在控制台启用对应项目的 App 证书。项目一旦开启了 App 证书,就必须使用 RTM Token 鉴权。
你可以根据业务需求指定 RTM Token 的有效期 (最长为 24 小时)。当 RTM Token 临 30 秒过期时,会触发 onTokenPrivilegeWillExpire
回调,提醒用户 Token 即将过期。收到该回调时,你可以在服务端重新生成 RTM Token,然后调用 renewToken
方法,将新生成的 RTM Token 传给 SDK。
Token 过期时,分为以下两种情况:
CONNECTION_STATE_CONNECTED
),会收到 onTokenExpired
回调和因 Token 过期 (CONNECTION_CHANGE_REASON_TOKEN_EXPIRED (9)
) 触发的 onConnectionStateChanged
回调,提醒用户连接状态切换至停止登录 (CONNECTION_STATE_ABORTED
)。此时,用户需要调用 login
方法重新登录。CONNECTION_STATE_RECONNECTING
),会在网络恢复时收到 onTokenExpired
回调。此时,用户需要调用 renewToken
方法恢复连接。onTokenPrivilegeWillExpire
回调和 onTokenExpired
回调进行 Token 过期处理,但声网推荐你通过定时(例如每小时)更新 Token 来解决 Token 过期问题。