详解自建高防 CDN 的防盗链与 Referer 校验逻辑的工程实现
摘要:# 别让盗链把你家服务器“吃空”——聊聊自建高防CDN里那些防盗链的硬核操作 前两天,一个做在线教育的朋友半夜找我诉苦,说他们平台上的视频课程,莫名其妙流量暴涨,但付费用户数没动。我一听就感觉不对劲——这味儿太熟悉了。让他查了下日志,果然,大量请求的Re…
别让盗链把你家服务器“吃空”——聊聊自建高防CDN里那些防盗链的硬核操作
前两天,一个做在线教育的朋友半夜找我诉苦,说他们平台上的视频课程,莫名其妙流量暴涨,但付费用户数没动。我一听就感觉不对劲——这味儿太熟悉了。让他查了下日志,果然,大量请求的Referer字段,指向一堆乱七八糟的盗版资源站、甚至是一些“你懂的”小网站。说白了,就是课程视频被人直接嵌到别处去了,流量全跑他这儿,账单也全算他头上。
他之前用的是某云厂商的CDN,自带防盗链功能,但配置得比较粗放,只简单校验了空Referer。结果呢?道高一尺魔高一丈,人家现在伪造个Referer跟玩儿似的。
这其实是个挺典型的场景。很多团队在自建高防CDN时,把重心全放在抗DDoS、防CC上了,觉得扛住大流量就万事大吉。但对于很多内容型、资源型业务来说,防盗链(Anti-Leech)才是那个悄无声息、却能让你成本失控的“慢性失血点”。今天,我就结合这几年踩过的坑和做过的方案,跟你掰扯清楚,在自建高防CDN的架构里,怎么把防盗链,特别是Referer校验这块,做得既硬核又灵活。
一、先泼盆冷水:防盗链,真不是配条规则那么简单
很多云厂商的控制台,把防盗链功能做得特别“傻瓜”:勾选“开启”,填几个允许的域名,完事。这给人一个错觉,好像防盗链就是个开关。
但你自己搭高防CDN,直面流量时,会发现这里头门道多了去了。首先你得想明白,你要防的是什么?
- 防“空降兵”(空Referer):有些请求压根不带Referer头,比如直接从浏览器地址栏输入URL、或者某些客户端、爬虫。你是直接拦,还是放行?
- 防“李鬼”(伪造Referer):这是目前最头疼的。攻击者或者盗链者,可以轻易地用工具伪造HTTP请求头,Referer想写啥就写啥。你只校验域名白名单?人家就伪造一个在你白名单里的域名。
- 防“自己人”(站内滥用):有时候,你自家网站的不同页面间跳转,或者某些合法的合作伙伴,他们的Referer格式可能比较特殊,一杆子打死会误伤。
所以,工程实现的第一步,不是写代码,而是定策略。你得根据你的业务内容(是公开图片,还是付费视频?)、用户场景(主要是站内访问,还是大量被分享?)、成本敏感度,来决定防盗链的严格程度。
我个人的经验是,对于核心的、成本高的资源(比如高清课程视频、付费文档),策略要“宁可错杀,不可放过”,结合多种手段;对于一般的展示性图片,可以宽松点,主要防大规模盗链。
二、核心战场:Referer校验的几种“段位”实现
好了,策略定了,我们来聊聊在自建高防CDN的节点上(通常是Nginx、OpenResty或者你自研的网关),怎么具体干这个活。这里我按复杂度,分几个“段位”来说。
青铜段位:基础域名白名单
这最简单,就是在Nginx里用 $http_referer 变量做匹配。配置大概长这样:
location ~* \.(mp4|avi|flv)$ {
valid_referers none blocked server_names *.yourdomain.com trusted.com;
if ($invalid_referer) {
return 403;
# 或者更狠点,rewrite到一张“请勿盗链”的图片上
# rewrite ^/.*$ /anti-leech.jpg last;
}
}
解释一下:
valid_referers后面跟的是允许的Referer。none:允许空Referer。blocked:允许Referer头存在但值被防火墙或代理删除了的情况。server_names:允许本服务器名(通常用不到)。- 后面直接跟具体的域名,支持通配符
*。
$invalid_referer变量会在Referer不合法时变为1。
吐槽一句:这种办法,防君子不防小人。稍微懂点技术的人,用curl或者写个小脚本,就能轻松绕过。它只能应对最原始的、直接复制你资源链接贴到别处的行为。
白银段位:签名URL(动态令牌)
这是目前对付伪造Referer比较有效的一招。思路是,不给用户一个固定的资源链接,而是给一个“一次性”或“短期有效”的、带签名的链接。
流程大概是:
- 用户在你的站内点击播放一个视频,你的业务后端服务器,根据这个视频的唯一ID、用户的身份信息(可选)、一个过期时间戳(比如10分钟后失效),再加上一个只有你和CDN节点知道的密钥,通过MD5或SHA256等算法,生成一个签名字符串(token)。
- 把这个token、过期时间戳等参数,拼接到视频的URL后面,比如:
https://cdn.your.com/video.mp4?t=1741234567&token=xxxxxx - 用户浏览器拿到这个链接,去请求你的CDN。
- 关键来了:你的CDN节点(比如用OpenResty的Lua脚本)需要能解析这个URL,取出参数,用同样的密钥和算法再算一遍签名。如果签名匹配,且时间戳没过期,就放行返回资源;否则,返回403或404。
优点:安全性大幅提升。盗链者即使截获了这个链接,也很快会过期。而且,因为密钥不在客户端,他无法批量生成新链接。
缺点:实现复杂。需要改造你的业务后端和CDN边缘逻辑。而且,对CDN缓存不友好(因为每个用户的URL都不同)。不过可以通过一些技巧,比如签名只校验路径部分,让查询参数不影响缓存键。
黄金段位:Referer校验 + 签名 + 频率监控 组合拳
对于真正重要的资源,我建议上组合拳。光靠一种机制,总有被琢磨透的时候。
- 第一层:宽松的Referer白名单。先过滤掉绝大部分低级的、随意的盗链请求。
- 第二层:签名URL。作为核心验证手段,确保每个请求都是经你业务后端合法授权的。
- 第三层:请求频率监控。在CDN节点或日志分析侧,对同一个签名链接,或者同一个客户端IP,在极短时间内发起的大量请求进行告警和限速。这能防止有人拿到一个有效链接后,用工具疯狂下载或爬取。
这种架构下,你的自建高防CDN,就不再是一个单纯的流量转发器,而是一个具备一定业务逻辑判断能力的边缘安全节点。
三、工程落地时,那些容易踩的坑
方案听起来不错,但一上线就扑街的情况我见多了。说几个关键的坑:
- 坑1:移动端和特殊场景的Referer。很多移动端App内WebView的请求,或者从微信、QQ等社交平台跳转过来,Referer可能很诡异,甚至是空的。你的白名单策略如果没把它们考虑进去,直接导致用户看不了,投诉就来了。解决办法:对这些渠道,要么单独开白名单,要么考虑用签名URL方式,完全绕过Referer校验。
- 坑2:缓存污染。如果你用了签名URL,又希望利用CDN缓存,一定要精心设计你的缓存键(Cache Key)。确保同一个资源,尽管签名不同,但最终能命中同一个缓存对象。通常做法是,缓存键只包含资源的路径,忽略掉
token、t这类查询参数。 - 坑3:密钥管理。签名用的密钥,怎么安全地存储和分发到所有CDN边缘节点?硬编码在配置文件里?太危险。可以用配置中心动态下发,或者使用硬件安全模块(HSM)。密钥一旦泄露,整个防线形同虚设。
- 坑4:误杀和体验。防盗链太严,动不动就403,用户体验极差。一定要有降级策略和监控告警。比如,当发现大量403来自某个正常渠道时,能快速调整规则。同时,返回的错误页面也可以友好一点,比如提示“请从本站APP内观看”,而不是一个冷冰冰的“Forbidden”。
四、说点实在的:要不要自己搞?
看到这里,你可能头都大了。没错,把一套健壮的防盗链逻辑深度集成到自建高防CDN里,是个系统工程,需要你对HTTP协议、你的CDN软件(Nginx/OpenResty)、你的业务架构都有比较深的理解。
所以,我的建议是:
- 如果你的业务规模不大,资源没那么敏感,直接用成熟云CDN的防盗链功能,配置得精细一点(比如结合IP黑白名单、UA校验),基本也够用。别折腾。
- 如果你的核心业务严重依赖独家数字内容,成本压力巨大,或者有极强的定制化需求(比如要对不同用户组动态生成不同的访问策略),那么投入研发自建这套东西,从长远看是值得的。它能给你带来真正的控制力和成本优化空间。
说白了,安全没有银弹。防盗链更是一个持续对抗的过程。你今天上了签名,明天可能就有人研究你的算法弱点。关键是要建立起一套从监控、分析到快速响应的闭环,让你的防护策略能跟着“贼”的招式一起进化。
最后,如果你真的决定要自己动手,别忘了在测试环境里,用各种工具(比如Postman、爬虫脚本)把自己当成“攻击者”,狠狠地测试几轮。很多问题,不上线,你根本发现不了。
行了,关于自建高防CDN里的防盗链,就先聊这么多。这玩意儿细节不少,真要落地,每个团队可能都得摸索出自己的“野路子”。如果你在实操中遇到了什么奇葩问题,或者有更妙的招数,欢迎随时来聊聊。

