获取连接 Token
这篇文档说明如何生成一个能够连接指定 peer_id 的 Token。
Token 是什么
连接 Token 是一个短时有效的鉴权凭证,用于在建立连接时校验客户端是否有权限接入指定 peer_id。
一个典型闭环通常是这样:
- 客户端发起 HTTP 请求,向你的业务服务端请求一个 Token
- 业务服务端执行签发
- 签发之前,可以根据客户端的用户标识、目标
device_id或目标peer_id判断是否允许访问 - 只有确认允许访问后,才生成这次连接使用的 Token
- 最后通过 HTTP Response 把 Token 返回给客户端
这篇文档只聚焦中间这一件事:生成连接 Token 需要什么输入、Token 里要包含什么,以及它应该怎么算出来。
正式接入时,最常见的做法是由业务服务端完成签发。
生成 Token 前需要准备什么
在开始之前,先准备好下面这些输入:
| 项目 | 说明 |
|---|---|
access_id | 由 TiRTC 平台分配给你的应用凭证 ID |
secret_key | 与 access_id 配套的应用密钥 |
peer_id | 这次要连接的目标,例如 device://dev_xxx |
device_secret_key | 目标设备对应的设备密钥 |
sub | 这次请求的主体标识,例如你的 user_id、uid 或其他稳定主键 |
如果你手上拿到的是设备 license,对 Nano 来说它的格式是:
text
<device_id>,<device_secret_key>生成连接 Token 时,真正参与签名的是后半段 device_secret_key。
如果这次连接的目标就是某台设备,可以直接把 peer_id 写成 device://<device_id>。
连接 Token 里需要包含什么,以及它是怎么算出来的
连接 Token 至少要表达下面这些信息:
sub:这次请求的主体标识scope:这次授权的目标范围,例如connect:device://dev_xxxiss:当前应用的access_idiat:签发时间exp:过期时间nonce:每次签发都不同的随机值,用于防重放
这类 Token 建议短时有效,例如 300 秒。
最终下发给客户端的字符串格式是:
text
v1.<payload_b64>.<app_sig>其中:
v1是版本号payload_b64是 payload JSON 的 base64url 编码结果app_sig是最终签名
生成这个字符串时,可以按下面 5 步计算:
- 先构造 payload JSON,写入
sub、scope、iss、iat、exp、nonce - 对 payload JSON 做 base64url 编码,得到
payload_b64 - 用
device_secret_key对payload_b64做一次 HMAC-SHA256,得到中间值device_sig - 再用
secret_key对payload_b64 + "." + device_sig做一次 HMAC-SHA256,得到最终签名app_sig - 最后拼成
v1.<payload_b64>.<app_sig>
写成伪代码就是:
text
payload_json = {
"sub": <subject>,
"scope": "connect:" + <peer_id>,
"iss": <access_id>,
"iat": <issued_at>,
"exp": <expires_at>,
"nonce": <random_nonce>
}
payload_b64 = base64url(payload_json)
device_sig = HMAC_SHA256(device_secret_key, payload_b64)
app_sig = HMAC_SHA256(secret_key, payload_b64 + "." + device_sig)
token = "v1." + payload_b64 + "." + app_sig完整示例与 Go 实现
先看一个具体 payload 例子:
json
{
"sub": "user_123",
"scope": "connect:device://dev_xxx",
"iss": "ak_xxx",
"iat": 1740000000,
"exp": 1740000300,
"nonce": "random_128bit_nonce"
}对这段 payload 做 base64url 编码后,你就会得到 payload_b64。随后按上一节的双重签名规则得到 app_sig,最终拼成完整 Token。
下面这段 Go 示例表达的是同一套规则如何落到实现里:
go
package tirtc
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"time"
)
type TokenClaims struct {
Subject string `json:"sub"`
Scope string `json:"scope"`
Issuer string `json:"iss"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
Nonce string `json:"nonce"`
}
func hmacB64(key, content string) string {
h := hmac.New(sha256.New, []byte(key))
_, _ = h.Write([]byte(content))
return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}
func randomNonce() (string, error) {
buf := make([]byte, 16)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
func GenerateConnectToken(accessID, secretKey, peerID, deviceSecretKey, subject string, ttlSeconds int64) (string, error) {
now := time.Now().Unix()
nonce, err := randomNonce()
if err != nil {
return "", err
}
payload := TokenClaims{
Subject: subject,
Scope: "connect:" + peerID,
Issuer: accessID,
IssuedAt: now,
ExpiresAt: now + ttlSeconds,
Nonce: nonce,
}
payloadJSON, err := json.Marshal(payload)
if err != nil {
return "", err
}
payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON)
deviceSig := hmacB64(deviceSecretKey, payloadB64)
appSig := hmacB64(secretKey, payloadB64+"."+deviceSig)
return "v1." + payloadB64 + "." + appSig, nil
}这段代码里最关键的是:
scope必须绑定目标peer_idsub由你自己决定写入哪个稳定主体标识ttlSeconds应当较短nonce每次都要重新生成device_secret_key先参与一次签名,secret_key再参与最终签名- 最终返回给客户端的 Token 只包含
app_sig,不单独附带device_sig
安全建议
secret_key不下发给客户端device_secret_key不下发给客户端- 返回给客户端的应当是短时有效的连接 Token
- 每次生成连接 Token 时都使用新的
nonce - 如果你需要按用户或按设备维度做访问控制,应当在签发前先完成判断