Skip to content

获取连接 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_keyaccess_id 配套的应用密钥
peer_id这次要连接的目标,例如 device://dev_xxx
device_secret_key目标设备对应的设备密钥
sub这次请求的主体标识,例如你的 user_iduid 或其他稳定主键

如果你手上拿到的是设备 license,对 Nano 来说它的格式是:

text
<device_id>,<device_secret_key>

生成连接 Token 时,真正参与签名的是后半段 device_secret_key

如果这次连接的目标就是某台设备,可以直接把 peer_id 写成 device://<device_id>

连接 Token 里需要包含什么,以及它是怎么算出来的

连接 Token 至少要表达下面这些信息:

  • sub:这次请求的主体标识
  • scope:这次授权的目标范围,例如 connect:device://dev_xxx
  • iss:当前应用的 access_id
  • iat:签发时间
  • exp:过期时间
  • nonce:每次签发都不同的随机值,用于防重放

这类 Token 建议短时有效,例如 300 秒。

最终下发给客户端的字符串格式是:

text
v1.<payload_b64>.<app_sig>

其中:

  • v1 是版本号
  • payload_b64 是 payload JSON 的 base64url 编码结果
  • app_sig 是最终签名

生成这个字符串时,可以按下面 5 步计算:

  1. 先构造 payload JSON,写入 subscopeissiatexpnonce
  2. 对 payload JSON 做 base64url 编码,得到 payload_b64
  3. device_secret_keypayload_b64 做一次 HMAC-SHA256,得到中间值 device_sig
  4. 再用 secret_keypayload_b64 + "." + device_sig 做一次 HMAC-SHA256,得到最终签名 app_sig
  5. 最后拼成 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_id
  • sub 由你自己决定写入哪个稳定主体标识
  • ttlSeconds 应当较短
  • nonce 每次都要重新生成
  • device_secret_key 先参与一次签名,secret_key 再参与最终签名
  • 最终返回给客户端的 Token 只包含 app_sig,不单独附带 device_sig

安全建议

  • secret_key 不下发给客户端
  • device_secret_key 不下发给客户端
  • 返回给客户端的应当是短时有效的连接 Token
  • 每次生成连接 Token 时都使用新的 nonce
  • 如果你需要按用户或按设备维度做访问控制,应当在签发前先完成判断

Ti RTC 开发文档