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
      • 同一域名多密钥:确认实际启用的是哪一把(主/副)

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