生成Token
生成方式
SDK 到 Pano Cloud 的所有交互都需要使用 Token,Token的生成方式有两种。
- 第一种是 App Server(应用服务器)通过 RESTful API 向 Pano 服务器获取。
- 第二种是 App Server(应用服务器)按照 Pano 定义的规则自行计算。
此方式需要相应的 SDK 版本才能识别,最低 SDK 版本要求如下:- Windows/macOS/iOS/Android: 1.4.1
- Web: 1.3.4
- Web Whiteboard: 2.1.0
- Electron: 1.0.4
- Flutter/React Native: 0.9.6
向Pano获取
请求示例
POST /auth/token
Host: api.pano.video
Content-Type: application/json
Authorization: PanoSign <PanoSign>
Tracking-Id: ef9b2acc8e1f4d598090eb6d9cbe8596
{
"channelId": "HelloPano", // String,必填,频道ID
"userId": "12345", // String,必填,用户ID
"privileges": 0, // int, 可选,权限
"channelDuration": 60, // int, 可选,频道最大时长,单位为分钟,默认为0(表示不限时长)
"duration": 86400, // int, 可选,token有效期,单位为秒,默认24小时
"size": 100, // int, 可选,频道人数上限
"delayClose": 0 // int, 可选,白板延迟关闭时间,单位为秒,默认为0,最长24小时
}
参数说明
- PanoSign - 请参考权限控制
- channelId - 请参考Channel ID命名规则
- userId - 请参考User ID
- privileges - 请参考Token Privileges
- channelDuration - 频道最大时长,如需限制频道时长,可以使用此参数。另请参考频道最大时长说明。
频道开始时间取决于频道内第一位用户的加入时间;频道最大时长取决于第一位用户携带的
channelDuration
。当时间到达时Pano服务器会自动结束频道并踢出所有用户。 - duration - token有效期(有效期内token可重复使用),可配置小于等于24小时的有效期。
客户端加入频道时,Pano服务器会校验token的有效性,超过有效期则无法加入频道。
向Pano获取token时,有效期起点为Pano服务器生成该token的时间点。 - size - 频道人数上限,单个频道容纳的人数上限,默认为100。
如果默认的人数上限不能满足应用需求,比如语聊房、视频会议等场景,通常需要较高的人数上限,而有的场景可能需要限制为较低的人数上限,请联系技术支持了解调整该默认值的方法。
- delayClose - 白板延迟关闭时间,所有人都离开后延迟多久关闭白板频道。默认为0(立即关闭/不延迟),如果大于0,当所有人都离开后,白板频道会延迟关闭。
如果
channelDuration
时间到达,或者主动调用服务端 API 关闭频道,则delayClose
失效。
响应码说明
HTTP状态码 | 错误码 | 描述 |
---|---|---|
200 | 正常返回 | |
403 | Authorization.Type.Invalid | HTTP请求头Authorization类型不对 |
403 | Authorization.Signature.Invalid | HTTP请求头Authorization 值有误,请检查signature签名信息 |
403 | App.Status.Error | app 状态可能不对,请到控制台检查app是否禁用 |
403 | Org.Status.Error | 公司信息状态不对,请检查是否欠费 |
403 | Invalid.Parameter.Value | 请求参数不合法,请检查是否按文档规定传递参数 |
404 | InvalidEntity.NotFound | app 信息找不到,请查看appId是否正确 |
406 | App.Secret.Empty | app Secret 信息有误,请到控制台获取正确app Secret |
406 | Sign.Error | 请检查PanoSign 是否按照规定生成签名信息 |
500 | Server.Error | 服务器内部出错 |
响应示例
请求正常处理的返回示例:
200 OK
{
"token": "0100000AUHZUTER......cClLFAtM=" // 省略了中间部分字符串
}
请求出现错误的返回示例请参考:返回状态码
自行计算
如果开发者需要自行生成token,出于安全考虑,我们强烈建议在应用服务器上进行计算并下发给客户端使用。如无特别必要,不要在客户端计算token,否则可能存在潜在的安全风险。
规则介绍
Token由五部分组成,用.
拼接在一起,格式为<version>.<appId>.<timestamp>.<params>.<signature>
,各字段含义如下:
<version>
- 版本号,固定为02
。<appId>
- 应用ID,请登录 控制台 后切换到「应用管理」查看应用ID。<timestamp>
- 当前UTC时间戳(精确到秒),该时间戳与joinParams
中的duration
值相加,得到token的有效期(也就是说,自行计算token时,token的有效期起点为timestamp
对应的时间点)。Pano服务器会校验时间戳的有效性,所以请注意设置正确的时间(建议同步NTP)。joinParams
请参考 向Pano获取token 时传入的body(请求示例中大括号内的内容)。<params>
- 频道参数,由joinParams
计算获得,计算规则为:base64(json(joinParams)) // 对 joinParams 进行 JSON 编码后再进行 base64 编码
<signature>
- 签名,由<version>
、<appId>
、<timestamp>
、<params>
、<appSecret>
计算获得,计算规则为:signature = base64(HmacSHA256(version+appId+timestamp+params, appSecret))
示例代码
可以参考以下示例代码来生成Token。
package com.example.lib;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/* [注意] 如果开发者需要自行生成token,出于安全考虑,我们强烈建议在应用服务器上进行计算并下发给客户端使用。
如无特别必要,不要在客户端计算token,否则可能存在潜在的安全风险。
如果确需在客户端计算,请注意,android.util.Base64 和 java.util.Base64 结果可能不一致。
根据开发者反馈,android.util.Base64 的 encodeToString 的默认行为会为编码结果添加换行符(\n)。
请注意消除相应差异,以 java.util.Base64 结果为准,否则后续token鉴权将会失败。
使用 android.util.Base64 的 encodeToString 时,尝试将 flags 参数从 DEFAULT 改为 NO_WRAP。
*/
public class MyClass {
static final String APP_ID = "Your appId";
static final String APP_SECRET = "Your appSecret";
// 此处直接使用 JSON 编码后的 joinParams(joinParams 请按实际情况配置并自行对其进行 JSON 编码)
static String jsonJoinParams = "{\"channelId\":\"1234\",\"userId\":\"1234\",\"channelDuration\":60,\"privileges\":0,\"duration\":86400,\"size\":25,\"delayClose\":0}";
public static String signature(String data, String appSecret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes("UTF-8"), "HmacSHA256"));
byte[] signData = mac.doFinal(data.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(signData);
}
public static String generatePanoToken(String appId, String appSecret, String jsonJoinParams) throws Exception {
String version = "02";
long timestamp = System.currentTimeMillis() / 1000L;
String params = Base64.getEncoder().encodeToString(jsonJoinParams.getBytes("UTF-8"));
String signContent = new StringBuilder().append(version).append(appId).append(timestamp).append(params).toString();
String signatureValue = signature(signContent, appSecret);
return new StringBuilder()
.append(version).append(".")
.append(appId).append(".")
.append(timestamp).append(".")
.append(params).append(".")
.append(signatureValue).toString();
}
public static void main(String[] args) {
try {
String token = generatePanoToken(APP_ID, APP_SECRET, jsonJoinParams);
System.out.println(token);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"time"
)
func signature(signContent string, appSecret string) string {
mac := hmac.New(sha256.New, []byte(appSecret))
mac.Write([]byte(signContent))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
func generatePanoToken(appId string, appSecret string, jsonJoinParams string) string {
version := "02"
timestamp := time.Now().Unix()
params := base64.StdEncoding.EncodeToString([]byte(jsonJoinParams))
signContent := fmt.Sprintf("%s%s%d%s", version, appId, timestamp, params)
signatureValue := signature(signContent, appSecret)
return fmt.Sprintf("%s.%s.%d.%s.%s", version, appId, timestamp, params, signatureValue)
}
func main() {
const appId string = "Your appId"
const appSecret string = "Your appSecret"
// 此处直接使用 JSON 编码后的 joinParams(joinParams 请按实际情况配置并自行对其进行 JSON 编码)
var jsonJoinParams string = "{\"channelId\":\"1234\",\"userId\":\"1234\",\"channelDuration\":60,\"privileges\":0,\"duration\":86400,\"size\":25,\"delayClose\":0}"
var token string = generatePanoToken(appId, appSecret, jsonJoinParams)
fmt.Printf(token)
}
# coding=utf-8
import base64
import hmac
import time
import json
from hashlib import sha256
def signature(signContent, appSecret):
return base64.b64encode(hmac.new(appSecret, signContent, digestmod=sha256).digest())
def generatePanoToken(appId, appSecret, joinParams):
version = "02"
timestamp = str(int(time.time()))
params = base64.b64encode(json.dumps(joinParams))
signContent = version + appId + timestamp + params
signatureValue = signature(signContent, appSecret)
return version + "." + appId + "." + timestamp + "." + params + "." + signatureValue
appId = "Your appId"
appSecret = "Your appSecret"
# joinParams 请按实际情况配置,此处仅为示例
joinParams = {"channelId": "1234", "userId": "1234", "channelDuration": 60, "privileges": 0, "duration": 86400, "size": 25, "delayClose": 0}
token = generatePanoToken(appId, appSecret, joinParams)
print(token)
import base64
import hmac
import time
import json
from hashlib import sha256
def signature(signContent, appSecret):
return base64.b64encode(hmac.new(bytes(appSecret, 'utf-8'), bytes(signContent, 'utf-8'), digestmod=sha256).digest()).decode('utf-8')
def generatePanoToken(appId, appSecret, joinParams):
version = "02"
timestamp = str(int(time.time()))
params = base64.b64encode(bytes(json.dumps(joinParams), 'utf-8')).decode('utf-8')
signContent = version + appId + timestamp + params
signatureValue = signature(signContent, appSecret)
return version + "." + appId + "." + timestamp + "." + params + "." + signatureValue
appId = "Your appId"
appSecret = "Your appSecret"
# joinParams 请按实际情况配置,此处仅为示例
joinParams = {"channelId": "1234", "userId": "1234", "channelDuration": 60, "privileges": 0, "duration": 86400, "size": 25, "delayClose": 0}
token = generatePanoToken(appId, appSecret, joinParams)
print(token)
<?php
$appId = "Your appId";
$appSecret = "Your appSecret";
$timestamp = time();
// joinParams 请按实际情况配置,此处仅为示例
$joinParams = ["channelId" => "1234", "userId" => "1234", "channelDuration" => 60, "privileges" => 0, "duration" => 86400, "size" => 25, "delayClose" => 0];
$token = getToken($appId, $appSecret, $timestamp, $joinParams);
echo $token;
function getToken($appId, $appSecret, $timestamp, $joinParams){
$version = "02";
$params = base64_encode(json_encode($joinParams));
$signData = $version . $appId . $timestamp . $params;
$signature = base64_encode(hash_hmac("sha256", $signData, $appSecret, true));
return $version . "." . $appId . "." . $timestamp . "." . $params . "." . $signature;
}
// End of file
下面是本地生成Token的结果示例:
02.abcdefghijklmnopq01234567890.1622211277.eyJjaGFubmVsSWQiOiIxMjM0IiwidXNlcklkIjoiMTIzNCIsImNoYW5uZWxEdXJhdGlvbiI6NjAsInByaXZpbGVnZXMiOjAsImR1cmF0aW9uIjo4NjQwMCwic2l6ZSI6MjUsImRlbGF5Q2xvc2UiOjB9.7bsKmTjVC2te+W4+n7XxqbrZE+VrFaxRVkTZobacaWg=