Miku 直播

  • Miku 直播 > API 文档 > 直播推拉流时间戳防盗链

    直播推拉流时间戳防盗链

    最近更新时间: 2025-11-14 10:53:45

    七牛直播推流/拉流时间戳防盗链

    本文覆盖两部分:

    • 推流时间戳防盗链:RTMP 推流
    • 拉流时间戳防盗链:HLS(.m3u8)、FLV(.flv)、RTMP 播放等

    术语

    • publishDomain:推流域名
    • playDomain:播放域名
    • bucket:直播空间名称
    • stream:直播流名称
    • publishKey:推流鉴权密钥(配置在推流域名/空间
    • playKey:播放防盗链密钥(配置在播放域名
    • t(expireAt):过期时间的 UNIX 时间戳(秒)
    • path:URL 的路径部分,必须以 / 开头;如 rtmp://test.miku.com/sdk-live/test 的 path 为 /sdk-live/test

    签名串中涉及 URL 编码时,需保留斜杠 /(参考下文示例代码的处理方式)。


    一、推流时间戳防盗链

    推流域名上设置 publishKey,并在推流地址后追加 ?sign=...&t=...

    签名公式

    sign = md5(publishKey + path + t).to_lower()
    
    • path:如 /sdk-live/test
    • t:过期时间 UNIX 时间戳(秒)

    示例

    publishDomain: test.miku.com
    bucket: sdk-live
    stream: test
    publishKey: test
    t (expireAt): 1756110618
    path: /sdk-live/test
    
    SignStr = "test/sdk-live/test1756110618"
    sign = md5(SignStr).to_lower() = 6a1b665f529c8b57d6408b72e4d21350
    
    最终推流地址:
    rtmp://test.miku.com/sdk-live/test?sign=6a1b665f529c8b57d6408b72e4d21350&t=1756110618
    

    Portal 中主/副密钥(均可以进行鉴权)均可使用;建议使用副密钥进行轮换,降低主密钥更换风险。


    二、拉流时间戳防盗链

    播放防盗链可以有效避免直播资源被非法盗用的问题。
    播放域名上开启“时间戳防盗链”并设置 playKey。访问播放地址时,边缘节点按相同规则校验,非法则拒绝(常见表现:HLS/FLV 403;RTMP 播放连接失败/服务返回错误)。

    推流地址格式

    [rtmp/http]://<playDomain>/<bucket>/<stream>.[flv/m3u8]
    

    签名公式

    sign = md5(playKey + path + t).to_lower()
    

    path 取值示例

    协议 地址示例 用于签名的 path
    HLS http://play.example.com/bucket/stream.m3u8 /bucket/stream.m3u8
    FLV http://play.example.com/bucket/stream.flv /bucket/stream.flv
    RTMP播放 rtmp://play.example.com/bucket/stream /bucket/stream

    注意:不同协议的 path 不同

    生成步骤

    1. 开启时间戳防盗链并获取 playKey
      控制台 → 直播空间 → 播放域名 → 开启时间戳防盗链 → 配置主/副密钥
    2. 确定过期时间 t(秒)
      # macOS:
      t=$(date -j -f "%Y-%m-%d %H:%M:%S" '2025-10-29 20:00:00' +%s)
      # 结果示例:1761739200
      
    3. 计算 sign
      sign=md5(playKey + path + t).to_lower()
      

    HLS 示例

    未鉴权地址:

    http://pili-hls.pilitest.com/bucket/stream.m3u8
    

    开启后:

    playDomain: pili-hls.pilitest.com
    playKey = test
    path    = /bucket/stream.m3u8
    t       = 1761739200
    
    sign    = md5("test/bucket/stream.m3u81761739200").to_lower()
            = 3acc8aa865f23adfdbceba694e7dc4b9
    
    最终播放地址:
    http://pili-hls.pilitest.com/bucket/stream.m3u8?sign=3acc8aa865f23adfdbceba694e7dc4b9&t=1761739200
    

    三、参考代码(兼容推流/拉流)

    同一签名方法适用于推流与拉流。区别仅在于:使用的 Key(publishKey/playKey)path(推拉流 RTMP /bucket/stream;拉流 HLS /bucket/stream.m3u8;拉流 FLV /bucket/stream.flv)。

    Java

    package com.qiniu.hugo.miku;
    
    import java.net.URLEncoder;
    import java.nio.charset.Charset;
    import java.security.MessageDigest;
    
    public class TimestampAntiLeechUrl {
    
        private static final char[] DIGITS_LOWER = {
            '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
        };
    
        public static void main(String[] args) {
            // 推流(Publish)
            String publishUrl = "rtmp://test.miku.com/sdk-live/test";
            long t1 = System.currentTimeMillis()/1000 + 20*60;
            String publishKey = "test";
            System.out.println(signUrl(publishUrl, publishKey, t1));
    
            // HLS 播放(Play)
            String hlsUrl = "http://test.miku.com/sdk-live/test.m3u8";
            long t2 = System.currentTimeMillis()/1000 + 20*60;
            String playKey = "test";
            System.out.println(signUrl(hlsUrl, playKey, t2));
        }
    
        public static String signUrl(String url, String key, long expireTs) {
            try {
                String path = url.substring(url.indexOf("/", url.indexOf("//") + 2));
                // 保留斜杠,仅编码其他字符
                String encodedPath = URLEncoder.encode(path, "UTF-8").replace("%2F", "/");
                String t = String.valueOf(expireTs);
                String toSign = key + encodedPath + t;
                String sign = md5Lower(toSign);
                return String.format("%s?sign=%s&t=%s", url, sign, t);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        private static String md5Lower(String src) throws Exception {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(src.getBytes(Charset.forName("UTF-8")));
            byte[] bytes = md.digest();
            return encodeHex(bytes);
        }
    
        private static String encodeHex(byte[] data) {
            char[] out = new char[data.length << 1];
            for (int i = 0, j = 0; i < data.length; i++) {
                out[j++] = DIGITS_LOWER[(0xF0 & data[i]) >>> 4];
                out[j++] = DIGITS_LOWER[0x0F & data[i]];
            }
            return new String(out);
        }
    }
    

    Go

    package main
    
    import (
    	"crypto/md5"
    	"encoding/hex"
    	"fmt"
    	"net/url"
    	"strings"
    	"time"
    )
    
    func main() {
    	// 推流(Publish)
    	pub := "rtmp://test.miku.com/sdk-live/test"
    	fmt.Println(SignURL(pub, "test", time.Now().Unix()+20*60))
    
    	// HLS 播放(Play)
    	hls := "http://test.miku.com/sdk-live/test.m3u8"
    	fmt.Println(SignURL(hls, "test", time.Now().Unix()+20*60))
    }
    
    func SignURL(baseURL, key string, expire int64) (string, error) {
    	u, err := url.Parse(baseURL)
    	if err != nil {
    		return "", err
    	}
    	// path 必须以 / 开头
    	path := u.Path
    	// 保留斜杠,仅编码其他字符
    	encodedPath := strings.ReplaceAll(url.QueryEscape(path), "%2F", "/")
    	t := fmt.Sprintf("%d", expire)
    	toSign := key + encodedPath + t
    	sign := md5Lower(toSign)
    	return fmt.Sprintf("%s?sign=%s&t=%s", baseURL, sign, t), nil
    }
    
    func md5Lower(s string) string {
    	sum := md5.Sum([]byte(s))
    	return hex.EncodeToString(sum[:])
    }
    

    四、易错点与最佳实践

    1. 密钥配置位置

      • 推流鉴权用 publishKey(配置在推流域名/空间
      • 播放防盗链用 playKey(配置在播放域名
    2. path 必须准确

      • 推拉流 RTMP:/bucket/stream
      • 拉流 HLS:/bucket/stream.m3u8
      • 拉流 FLV:/bucket/stream.flv
    3. URL 编码

      • 仅编码必要字符并保留 /,参考示例代码
    4. 时间戳单位

      • t秒级 UNIX 时间戳
    5. 主/副密钥轮换

      • 支持主/副密钥;使用副密钥灰度切换更稳妥
    6. 排查清单

      • 403/连接失败:检查 path 是否正确、t 是否过期、签名大小写、是否双重编码
      • 跨协议失败:核对协议对应的 path 后缀(.m3u8/.flv
      • 同一域名多密钥:确认实际启用的是哪一把(主/副)

    以上内容是否对您有帮助?