功能介绍
所谓安全防盗链,是一种加了防盗链签名的URL,经过签名的URL能够跟阿麦打流服务器的安全机制进行配合,可以将URL的使用权限定在您的APP上,恶意第三方拿到URL也不能使用和传播。
(1)推流 - 推流URL加防盗链的必要性极高,尤其是在直播码跟用户ID(或者DB ID)绑定的情况下,因为客户的直播码ID很容易被攻击者窃取,进而伪装客户自己进行抢占式推流,所以为推流URL增加防盗链签名,确保只有真正的客户主人自己才能在登录后拿到防盗链签名非常有必要。
(2)播放 - 播放URL加防盗链并不是100%必要的,只有在您的视频源是热门资源时(比如某个独家的赛事直播)才比较有价值,因为您要防止您的竞争对手拿到播放地址后在自家的APP里上架这个节目。播放地址防盗链引入后的副作用就是播放时可能需要申请防盗链签名,导致直播打开速度不稳定。
原理介绍
下图都是典型的安全防盗URL,您会发现这些地址都有一个共同的特点,就是在原来的推流或者播放地址后拼接txSecret和txTime参数。
AMAI打流全地址:rtmp://120.26.206.180/live/test?username=123456&password=123456
OBS打流:rtmp://120.26.206.180/live
KEY字符串:test?username=123456&password=123456
test?username=tinywan&password=123456&sign=8935737e61b6fdd586cdab3b15d79633
剔流地址:http://120.26.206.180/rtmp/controllortnoc/drop/client?app=live&name=tinywan
安全防护链的构建:
rtmp://120.26.206.180/live/100022_test111?tokenSecret=c7707f5550076bd1c731db0c94ed9366&tokenTime=579ACB15
txTime声明了该播放器的有效时间,比如5分钟,当这个地址在5分钟后被再次使用时,阿麦打流服务器将会判定其为过期地址而拒绝服务。
但我们知道,简单声明txTime是没有意义的,因为任何攻击者都可以在一个窃取的URL后面拼接上一个声明100天过期的txTime,所以腾讯云需要有一种安全措施确保攻击者无法伪造txTime,这个安全措施就是txSecret。
txSecret是您的服务器计算出来的一个安全签名,计算公式是:txSecret = MD5( KEY + stream_id + txTime )
KEY
需要您跟阿麦打流服务器在控制台上提交或者兑换的安全密钥,您应当确保除了您和阿麦打流服务器之外,外部无法获知,而且最好定期更换。stream_id
即直播码,当同时在线的主播比较多的时候,txTime 很容易碰撞,所以加入 stream_id 来防碰撞。MD5
最著名的单向不可逆HASH算法了,因为不可逆性,所以攻击者无法根据txSecret逆向计算出KEY,也就无法根据自己伪造的txTime计算出一个可以通过腾讯云验证的txSecret来。
合成方法
安全防盗链机制需要您的服务器和阿麦打流服务器协同才能完成,如下图:
【1】step1 - 交换秘钥
首先,您需要在官网的控制台,协商一个加密密钥,这个加密密钥用于在您的服务器上生成防盗链签名,由于阿麦打流服务器跟您持有同样的密钥,所以您生成的防盗链签名,阿麦打流服务器是可以进行解密确认的。
加密秘钥分为推流防盗链KEY和播放防盗链KEY,前者用于生成推流防盗链URL,后者用于生成播放防盗链URL。目前在上可以自助配置推流防盗链KEY,如下图:
播放防盗链KEY由于同步周期长,不适合调试期频繁修改,如您需要配置请联系我们。
【2】step2 - 生成txTime
签名中明文部分为txTime,含义是该链接的有效期,比如现在我当前的时间是2016-07-29 11:13:45,而且期望新生成的URL是在5分钟后即作废,那么txTime就可以设置为 2016-07-29 11:18:45。
不过这么长一串时间字符串放在URL里显然不太“经济”,实际适用中,我们是把 2016-07-29 11:18:45 转换成Unix时间戳,也就是1469762325 (转换方式各种后台编程语言都由直接可用的时间函数来处理),然后转换成16进制,也就是 txTime=579ACB15。
/** * tokenTime * $currentTime :开始时间 */ public function getTokenTime($currentTime , $expireTime) { $tokenTime = dechex(strtotime($currentTime)+$expireTime); return strtoupper($tokenTime); }
【3】 step3 - 生成txSecret
txSecret的生成方法是 = MD5(KEY+ stream_id + txTime),这里的 KEY 就是您在Step1中跟腾讯云交换的加密密钥,stream_id在本例中为 8888_test001,txTime为刚才计算的579ACB15,MD5即标准的MD5单向不可逆哈希算法。
/** * tokenSecret * 顺序:MD5(KEY+ streamId + txTime) * @param int $streamId * @param string $currentTime * @param string $expireTime * @return string */ public function getTokenSecret($tokenTime,$streamId,$tokenTime) { $KEY = '8935737e61b6fdd586cdab3b1'; $tokenSecret = md5($KEY . $streamId . $tokenTime); return $tokenSecret; }
【4】step4 - 合成防盗链地址
现在我们有了推流(或者播放)URL,有了用来告知腾讯云该URL过期时间的txTime,有了只有腾讯云才能解密并且验证的txSecret,就可以拼合成一个防盗链的安全URL了。
public function indexAction() { $this->tag->setTitle('详情'); $request = $this->request; $domain = Domain::findFirst(array("id = :id:", "bind" => array("id" => 3))); if ($domain == false) { return $this->showError("未找到推流对象", array( "返回域名管理" => "/domainaliyun", "返回首页" => "/" )); } $this->view->setVar("domain", $domain); if ($request->isPost()) { $original_url = $request->getPost('original_url'); $deviceId = '1028'; $currentTime = $request->getPost('startTime'); $userId = $request->getPost('user_id'); $expireTime = $request->getPost('expire'); $streamId = $deviceId.$userId; $tokenTime = $this->getTokenTime($currentTime,$expireTime*60); $tokenSecret = $this->getTokenSecret($tokenTime,$streamId,$tokenTime);
// 拼接url $tokenUrl = $deviceId.'_test'.$userId."?tokenSecret=$tokenSecret&tokenTime=$tokenTime&userId=$userId"; $redis = RedisInstance::getInstance(); $redis->hMset('SIGNTOKEN:'.$userId, [ 'user_id' => $userId, 'sign' => $tokenSecret, 'expire' => $expireTime*60, 'original_url' => $original_url, 'status' => 'enable' ]); //这里保存过期时间,为了给客户短信通知,这个可以根据User_id去存储哦 $redis->setex($userId,$expireTime*60,$tokenTime); $this->view->setVar("token_url", $tokenUrl); $this->view->setVar("tokenSecret", $tokenSecret); return TRUE; } }
注意:Redis的键过期的时间设置应该是在用户已近开始打流的时候,也就是你用户的权限全部验证通过时候,设置改用户的userId为一个主键的过期时间,该时间也就是键过期的键值哦;注意在这里添加一句代码即可
/** * 验证签名是否合适 */ $sign = $redis->hGet('SIGNTOKEN:' . $userId, 'sign'); $expire = $redis->hGet('SIGNTOKEN:' . $userId, 'expire'); if ($tokenSecret != $sign) { echo "the sign errors"; header('HTTP/1.0 404 Not Found'); exit(1); } else { //这里保存过期时间,为了给客户短信通知,这个可以根据User_id去存储哦 $redis->setex($userId,$expire,$tokenTime); //这里表示用户已近开始打流了,这时候计算打流时间就可以了 echo "Username and Password OK"; } return true;