
热链接是怎样偷走你的带宽的
当浏览器请求一个页面时,HTTP 请求头中会带有一个 Referer 字段,用以告诉服务器当前请求是从哪个页面跳转过来的。如果一个网站 A 在自己的 HTML 中直接使用 <img src="https://ippipp.com/pic.jpg"> 的形式嵌入网站 B 的图片,那么当用户访问网站 A 时,浏览器向网站 B 发送的图片请求会带上 Referer: https://www.siteA.com/page.html。服务器 B 由此就能判断,这个请求并非来自本站的页面,而是被其他网站所引用——这就是典型的热链接,也就是俗称的盗链。
对小型站点而言,几张图片被引用可能无伤大雅;但如果你的服务器上存放了大量高清图片、CSS 样式文件或视频资源,被流量较大的网站直接“搬运”,你的服务器就会白白承担大量的带宽开销,页面加载速度反而变慢,而原站点的内容创作者却没有获得任何展示和回报。更极端的情况下,一些爬虫程序会不断抓取你的静态资源,造成服务器的严重负载。因此,在 Apache 层面设置热链接保护,是平衡资源安全与访问效率的重要手段。
需要注意的是,Referer 头的可靠程度并不高。出于隐私保护或安全策略,某些浏览器、防火墙或者隐私插件会主动移除或篡改 Referer 字段,当用户从 HTTPS 页面跳转到 HTTP 资源时,Referer 也会被默认屏蔽。这些场景下请求会把空 Referer 发送到服务器,如果规则过于严格,会将正常用户拒之门外。所以,一个严谨的热链接防护方案既要能识别异常来源,又要给空白 Referer 留出合理的放行通道。
用 mod_rewrite 搭建第一道防盗链防线
如果你的 Apache 已经开启了 mod_rewrite 模块,通过 .htaccess 或虚拟主机配置文件编写改写规则,是实施热链接保护最灵活的方式。它的核心思路是:检查 HTTP_REFERER 变量的值,如果发现是从不允许的域名发起的请求,或者来自特定类型的爬虫,就对该请求进行重定向或直接禁止。
下面是一段最基础的防盗链配置,它的作用是:当请求以 .jpg、.jpeg、.png、.gif 结尾的图片时,如果 Referer 为空,或者 Referer 中的域名不以 yourdomain.com 结尾,就将请求重定向到一张“请勿盗链”的提示图片上。
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http(s)?://(www.)?yourdomain.com [NC]
RewriteCond %{HTTP_REFERER} !^http(s)?://(www.)?another-allowed.com [NC]
RewriteRule .(jpg|jpeg|png|gif)$ https://yourdomain.com/images/nohotlink.jpg [R,L]
这里需要逐条理解 RewriteCond 的逻辑。第一条 !^$ 表示“Referer 不是空字符串”,这意味着如果请求不包含 Referer 头,这条条件为真,但下面一条限制不被满足时就会触发拦截。第二条 !^http(s)?://(www.)?yourdomain.com 意思是“Referer 不以你的域名开头”(忽略大小写),满足这条条件时说明请求来自外部网站。多个 Cond 之间默认是 AND 关系,所以只有当 Referer 不为空且不属于你的域名时,重写规则才会生效。如果你有多个环境或备用域名,比如 CDN 域名、www 和非 www 都需要放行,就用多条 Cond 罗列出来。
但是,上面这个例子显然没有考虑空白 Referer 的情况。如果用户直接从地址栏打开图片链接,或者使用隐私浏览器,Referer 为空,这条规则会将其重定向到 nohotlink.jpg,这显然不是我们所期望的。常见的处理方式是:允许空白 Referer 直接放行,需要把第一条 RewriteCond 反转或结合其他条件。下面这种写法更为常见:
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$ [NC]
RewriteCond %{HTTP_REFERER} !^http(s)?://(www.)?yourdomain.com [NC]
RewriteCond %{HTTP_REFERER} !^http(s)?://(www.)?search-engine.com [NC]
RewriteRule .(jpg|jpeg|png|gif|svg|webp)$ - [F,L]
这里变化在于最后一条 RewriteRule 使用了 [F,L] 标志。F 表示返回 403 Forbidden,禁止访问而不做重定向。这比返回一张提示图片更简洁,也能节省一次额外的 HTTP 请求。而对于空白 Referer,由于第一条 RewriteCond 是 !^$,当 Referer 为空时,这条条件不满足,整个条件组合为假,重写规则就不会触发,请求会正常响应。一些搜索引擎在抓取图片时,Referer 可能是 search-engine.com 的形式,你也可以像示例那样额外允许。
基于 SetEnvIf 与 Require 的轻量化方案
除了 mod_rewrite,Apache 还可以利用 SetEnvIf 指令结合访问控制来实现防盗链。这种方法不需要重写引擎,配置相对简单,并且能直接与 Apache 的 Require 指令配合,特别适合仅需限制特定文件类型的场景。
其原理是:用 SetEnvIfNoCase 检查 Referer 头,如果来源符合预期,就设置一个环境变量(比如 allow_access);然后在 FilesMatch 或 Location 容器中,通过 Require 指令判断该环境变量是否存在,不存在则拒绝访问。下面是一个典型配置:
SetEnvIfNoCase Referer "^http(s)?://(www.)?yourdomain.com" local_referer
SetEnvIfNoCase Referer "^$" allow_blank
# 如果需要允许某些搜索引擎
SetEnvIfNoCase Referer "google.com" search_engine
SetEnvIfNoCase Referer "bing.com" search_engine
<FilesMatch ".(jpg|jpeg|png|gif|svg|webp)$">
Require env local_referer
Require env allow_blank
Require env search_engine
</FilesMatch>
这段配置创建了三个环境变量:local_referer 在 Referer 匹配本站域名时设置,allow_blank 在 Referer 为空时设置,search_engine 在 Referer 中包含 google.com 或 bing.com 时设置。在 <FilesMatch> 块中,Require env 语句之间是“或”的关系,即只要上述三个环境变量任意一个被设置,请求就会被允许;否则返回 403。这种方式比 mod_rewrite 更直观,排查问题时也更容易看清哪个条件被满足。
需要注意的是,Apache 2.4 以上版本才支持 Require env 的多条件或逻辑。如果还在使用 2.2 版本,则需要借助 Order 和 Deny 指令配合 Satisfy any,但这种方式配置较为繁琐,建议升级。此外,SetEnvIf 的所有条件检查同样基于 Referer 头,因此也同样面临 Referer 被屏蔽的问题,必须对空白 Referer 做出明确处理,否则用户直接访问图片会被 403 拦截。
实战:兼顾空白 Referer、搜索引擎与直接访问的完整配置
结合前面两种方案的优点,我们可以构建一个既能拦截恶意盗链,又能友好对待正常用户和搜索引擎的配置。这里以 mod_rewrite 为例,因为它更灵活,能处理更复杂的需求。
首先,需要明确允许的列表:你自己的域名及其子域名、可能使用的 CDN 域名、主流搜索引擎的 Referer,以及空白 Referer。对于 CDN,如果你使用了阿里云 OSS 或 CloudFlare,通常请求通过 CDN 回源时,Referer 可能是 CDN 自身的域名,需要将其加入白名单。搜索引擎不仅包括 Google、Bing,还有百度、搜狗等,你可以根据实际访问日志来补齐。
RewriteEngine on
# 定义允许的域名和特殊情况
RewriteCond %{HTTP_REFERER} ^$ [NC,OR]
RewriteCond %{HTTP_REFERER} ^http(s)?://(www.)?yourdomain.com [NC,OR]
RewriteCond %{HTTP_REFERER} ^http(s)?://(www.)?your-cdn-domain.com [NC,OR]
RewriteCond %{HTTP_REFERER} ^http(s)?://(www.)?google. [NC,OR]
RewriteCond %{HTTP_REFERER} ^http(s)?://(www.)?bing. [NC,OR]
RewriteCond %{HTTP_REFERER} ^http(s)?://(www.)?baidu. [NC,OR]
RewriteCond %{HTTP_REFERER} ^http(s)?://(www.)?sogou. [NC]
# 如果以上条件都不满足,则禁止访问特定类型的文件
RewriteRule .(jpg|jpeg|png|gif|svg|webp|mp4|zip)$ - [F,L]
注意这里第一条 RewriteCond 后面的 [NC,OR] 标志非常关键:OR 使得多个 Cond 之间形成“或”逻辑,只要其中一条匹配,就不会触发最终的 RewriteRule。也就是说,只要 Referer 为空、来自你的域名、来自 CDN 或来自搜索引擎中的任意一个,请求都会被放行。最后一条 Cond 不加 OR,是为了结束这一组条件的串联。如果所有条件都不匹配,就会执行 RewriteRule 并返回 403。
对于空白 Referer 的放行,建议还是保留。虽然在极端情况下有绕过防盗链的可能(例如某些工具可以伪造空 Referer 发送请求),但对于绝大多数正常用户来说,这是保证可用性的必要妥协。如果你的资源极为敏感,也可以去掉空 Referer 的放行规则,但需要评估对用户体验和搜索引擎抓取的影响。此外,还可以结合 SetEnvIf 或其他手段记录日志,当发现异常流量时再做精准封禁。
测试配置是否正确,可以使用 curl 命令行工具模拟各种场景:
# 模拟没有 Referer 的请求(应该正常返回 200) curl -I https://yourdomain.com/images/test.jpg # 模拟来自白名单域名的请求(200) curl -I -e "https://www.yourdomain.com/page" https://yourdomain.com/images/test.jpg # 模拟外部盗链请求(应当返回 403) curl -I -e "https://www.bad-site.com" https://yourdomain.com/images/test.jpg
通过观察 HTTP 状态码,可以快速验证规则是否按预期工作。配置完成后,建议持续关注访问日志,如果发现某些合法来源(如社交媒体、第三方工具)的 Referer 被误拦,将其逐一加入白名单即可。