Referer校验不可靠因可被客户端伪造、CDN回源丢失、移动端常为空;防盗链须用服务端可控因子如时效签名,签名需基于路径+密钥+过期时间生成,密钥从环境变量读取,算法用hmac-sha256,校验失败统一返回403。

为什么直接用 Referer 校验在音视频防盗链中不可靠
因为 Referer 是客户端可随意伪造的 HTTP 头,浏览器、curl 甚至 Postman 都能手动设置任意值;CDN 回源时也可能丢失或覆盖该字段;移动端 WebView 或 PWA 场景下 Referer 更常为空。单纯依赖 c.Request.Header.Get("Referer") 做判断,等于把门锁换成贴纸。
真实音视频防盗链必须叠加服务端可控因子,比如时效性签名、用户会话绑定或 IP 临时白名单。否则攻击者写个 Python 脚本循环请求就能批量下载你的 MP4 文件。
如何用签名中间件实现带过期时间的防盗链
核心思路:URL 中携带 sign 和 expires 参数,服务端验证签名是否匹配且未过期。签名基于资源路径 + 密钥 + 过期时间生成,不依赖客户端传入的任何不可信字段。
-
expires必须是 Unix 时间戳(秒级),避免时区/格式歧义 - 密钥(如
secretKey)绝不硬编码在代码里,应从环境变量或配置中心读取 - 签名算法推荐
hmac-sha256,Go 标准库crypto/hmac直接支持,无需第三方依赖 - 校验失败时统一返回
http.StatusForbidden,不要泄露“签名错”还是“已过期”等细节
示例逻辑片段:
func VideoAuthMiddleware(secretKey string) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
expiresStr := c.Query("expires")
sign := c.Query("sign")
<pre class="brush:php;toolbar:false;"> expires, err := strconv.ParseInt(expiresStr, 10, 64)
if err != nil || time.Now().Unix() > expires {
c.AbortWithStatus(http.StatusForbidden)
return
}
expectedSign := fmt.Sprintf("%x", hmac.New(func() hash.Hash { return sha256.New() }, []byte(secretKey)).Sum([]byte(path+expiresStr)))
if !hmac.Equal([]byte(sign), []byte(expectedSign)) {
c.AbortWithStatus(http.StatusForbidden)
return
}
c.Next()
}}
静态资源路由必须显式注册,不能依赖 gin.Static
gin.Static() 会绕过所有中间件,包括你刚写的签名校验逻辑——它直接走文件系统读取并响应,中间件链根本不会触发。音视频资源一旦被这样暴露,防盗链形同虚设。
正确做法是:用 r.GET("/videos/*filepath", VideoAuthMiddleware(...), serveVideo) 显式声明路由,并在 serveVideo 中手动读取文件、设置 Content-Type 和 Content-Disposition 等头。
- 务必检查
filepath是否包含../,防止路径遍历(用filepath.Clean()+ 前缀校验) - 大文件建议用
c.DataFromFS或http.ServeContent支持断点续传,而不是c.String - 若使用 CDN,需确保 CDN 回源请求也携带
sign和expires,否则回源校验会失败
容易被忽略的部署细节:CDN 缓存与签名参数
CDN 默认会缓存带查询参数的 URL,但如果你的签名链接每次生成都不同(比如精确到秒级过期),会导致缓存命中率归零,全部回源——这反而放大了源站压力。
折中方案是:将 expires 设为整分钟或整5分钟对齐(如 time.Now().Add(5 * time.Minute).Truncate(5 * time.Minute).Unix()),并在 CDN 缓存策略中排除 sign 参数(只按路径 + expires 缓存)。这样既保证安全性,又维持合理缓存率。
另一个坑:Nginx 反向代理默认不透传查询参数给上游,需确认 proxy_pass 后面的 URL 末尾有 /,且未用 rewrite 错误截断参数。


















