权限控制
Pano提供了完善的权限控制功能,确保安全访问。
App Server到Pano Cloud
App Server到Pano Cloud的访问为 RESTful API over HTTPS 。为了安全性考虑,对Pano Cloud的RESTful API调用建议经由用户自己的服务器,即由App Server发起。比如,一些会控操作,申请Token等。
将一些请求经由App Server发起主要是考虑到保护用户的App Secret,App Secret相当于用户在Pano里的特殊密码,这些信息必须得到妥善保管,客户端不应直接使用。
Http请求头信息示例:
POST /<endpoint>
Host: api.pano.video
Content-Type: application/json
Authorization: PanoSign <PanoSign>
Cache-Control: no-cache
为了进一步增加安全性,RESTful请求时并不直接带上App Secret,而是带上 PanoSign,请将上面示例中的<PanoSign>
替换为真实的PanoSign(<>表示变量,实际的PanoSign里不需要)。
PanoSign格式
PanoSign由三部分组成,用.
拼接在一起,格式为<appId>.<timestamp>.<signature>
,各字段含义如下:
<appId>
- 你的App ID,请登录 控制台,在应用管理模块查看你的App ID<timestamp>
- 当前UTC时间戳(精确到秒)<signature>
- 签名,由App ID、当前UTC时间戳、App Secret计算获得,详细算法如下:
signature = base64(HmacSHA256(appId+timestamp,appSecret))
- 首先,将appId和当前UTC时间戳(精确到秒)拼接作为签名内容,appSecret作为签名key。加入当前UTC时间戳的目的是限制生成的signature只在很短的时间内有效,所以请注意设置正确的时间(建议同步NTP)
- 然后,通过
HmacSHA256
哈希算法以App Secret作为秘钥计算得到一个hash值 - 最后,将hash值经过base64编码
signature 计算示例和 PanoSign 生成示例:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
public class MyClass {
public static String signature(String appId, long timestamp, String appSecret)
throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
String message = appId + timestamp;
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(appSecret.getBytes("UTF-8"), "HmacSHA256");
hmacSha256.init(secretKey);
byte[] bytes = hmacSha256.doFinal(message.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(bytes);
}
public static String generatePanoSign(String appId, String appSecret)
throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
long timestamp = System.currentTimeMillis() / 1000L;
String sign = appId + "." + timestamp + "." + signature(appId, timestamp, appSecret);
return sign;
}
}
import (
"fmt"
"time"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
)
func signature(appId string, timestamp int64, appSecret string) string {
mac := hmac.New(sha256.New, []byte(appSecret))
mac.Write([]byte(fmt.Sprintf("%s%d", appId, timestamp)))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
func generatePanoSign(appId string, appSecret string) string {
current := time.Now().Unix()
return fmt.Sprintf("%s.%d.%s", appId, current, signature(appId, current, appSecret))
}
import time
import hmac
import base64
from hashlib import sha256
def genSignature(appId, timestamp, appSecret):
message = appId + timestamp
signature = base64.b64encode(hmac.new(appSecret, message, digestmod=sha256).digest())
return signature
def generatePanoSign(appId, appSecret):
timestamp = str(int(time.time()))
sign = appId + "." + timestamp + "." + genSignature(appId, timestamp, appSecret)
return sign
import time
import hmac
import base64
from hashlib import sha256
def genSignature(appId, timestamp, appSecret):
message = appId + timestamp
message_bytes = bytes(message, 'utf-8')
secret_bytes = bytes(appSecret, 'utf-8')
signature = base64.b64encode(hmac.new(secret_bytes, message_bytes, digestmod=sha256).digest())
return signature.decode('utf-8')
def generatePanoSign(appId, appSecret):
timestamp = str(int(time.time()))
sign = appId + "." + timestamp + "." + genSignature(appId, timestamp, appSecret)
return sign
function getPanoSign($appId, $appSecret){
$timestamp = time();
// https://www.php.net/manual/zh/function.hash-hmac.php
// raw_output 设置为 true 输出原始二进制数据
$signature = base64_encode(hash_hmac("sha256", $appId . $timestamp, $appSecret, true));
$panoSign = $appId . "." . $timestamp . "." . $signature;
return $panoSign;
}
下面是Http请求头的一个示例:
POST /auth/token
Host: api.pano.video
Content-Type: application/json
Authorization: PanoSign e7d3fb36131345f0a922b27c8c5c2019.1570498816.c31f97d3797de14f9d8e2c17f3ab165f070f9dc6547aadb5e9706763dc29a0c8
Tracking-Id: ef9b2acc8e1f4d598090eb6d9cbe8596
下面是生成的一个PanoSign示例:
e7d3fb36131345f0a922b27c8c5c2019.1570498816.c31f97d3797de14f9d8e2c17f3ab165f070f9dc6547aadb5e9706763dc29a0c8
校验PanoSign
Pano服务器会校验PanoSign,并对timestamp进行检查。
为了缩短和Pano服务器之间的时间误差,App Server端用于生成时间戳的机器必须要跟NTP保持同步。
为了保障安全,原则上要求每次请求都生成新的PanoSign。
SDK到Pano Cloud
SDK与Pano Cloud的所有交互都需要token
,因此所有交互之前都需要有一个合法有效的token,token可以在开始通话前生成,也可以提前生成。一般的流程如下:
- App Client调用App Server的接口申请token,这一步的安全保证由客户自行实现,客户采用自己的鉴权方式和调用方式来保证安全
- App Server调用Pano Cloud的接口申请token,这一步的安全保证参考上一节的内容
- Pano Cloud返回token给App Server,App Server返回给App Client
- App Client将token传给Pano SDK
- Pano SDK使用token与Pano Cloud交互,Pano Cloud通过校验token合法性来保证SDK的合法性
申请token需要appId、userId、channelId、duration、privileges等信息,从申请token的参数可以知道,token是和appId、channelId、userId绑定的,如果是不同的appId、不同的channelId、不同的userId,需要申请不同的token。
获取token的RESTful API细节请参考这里。同时,我们也提供了AppServer的示例代码供参考和调试使用。
token有效期由参数duration定义,默认为24小时,失效后需要重新申请一个有效的token。
参数privileges是Pano为客户提供的一个可选的权限机制,具体用法参考下一节。
Token Privileges
Pano SDK默认是具备发送音频、发送视频等各种能力的,你如果需要限制用户某些能力,你可以在App Client里控制End User的权限,譬如在用户界面上进行控制,不给没有权限的用户提供UI入口。
除了控制UI入口,开发者还可以选择给不同权限的用户申请具有不同权限的token。这样,Pano会根据token里的权限信息来帮助实现更完善的权限控制。
token privileges的使用流程一般如下:
- 申请token时,针对不同的用户,给不同的privileges参数
- Pano Cloud在生成token时增加权限属性
- SDK使用token与Pano Cloud交互,Pano Cloud校验token权限
- 如果权限不足,则拒绝相关操作
privileges定义了如下权限:发送音频、发送视频、进行白板互动、发送Screen Share等,在生成Token时有一个参数privileges
用于传入权限信息。
privileges参数的值是16bits的数字,每个bit代表一种权限:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| privileges |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
各个bit的定义如下(bit 0为高位):
- bit 0 - 是否启用权限控制, bit 0 = 1时后面的bits才有效
- bit 1 - 是否可以发送音频
- bit 2 - 是否可以发送视频
- bit 3 - 是否可以进行白板互动
- bit 4 - 是否可以发送Screen Share
- bit 5 ~ bit 15 - reserved as 0
用法举例:
- privileges = 0,不启用权限
- privileges = 49152,二进制:1100 0000 0000 0000,十六进制即0xC000,表示可以发送音频,其他权限没有
- privileges = 63488,二进制:1111 1000 0000 0000,十六进制即0xF800,表示可以发送音频、发送视频、进行白板互动、发送Screen Share
从上述权限定义可以发现,Pano并不限制接收方的权限,也就是说,所有人都有这些权限:接收音频、订阅视频、接收白板、订阅Screen Share等。
token privileges是一个可选的权限机制,可以使用,也可以不使用。