本文作者: Hechao
防盗链的必要性,我这里就不再赘述了,这是网站设计的最基本要求。而在nginx中,一般比较容易实现的防盗链手段就是通过ungx_http_referer_module模块(官方文档链接) 检查访问请求的referer信息是否有效来实现防盗链功能。
所谓referer检查,举个例子来说,在正常情况下当用户在浏览 http://example.com/abc.html 时点击一个链接去到 http://example.com/123.mp3 文件时,浏览器在发出请求123.mp3 资源时还会附带当刻浏览器所处的页面地址(即http://example.com/abc.html),所以当你的网站程序接收到下载 jacky.mp3 资源请求的时候,先判断http的referer字段的值,如果是从 自己的域名(example.com)过来的,则可以认为是合法的连接请求,否则就返回一个错误的提示信息。
这种方法通常用于图片、mp3这种容易被人用html“嵌入”到其他网站的资源,使用这种方法可以防止你的图片直接出现在别人的网页里(或者防止mp3直接被其他网站嵌入到flash播放器里),不过访客使用下载工具还是可以轻松下载,因为现在的下载工具一般会自动用你的域名构造一个引用地址,所以如果想再进一步防范的话,可以使用一个对应表限制每个资源的引用地址,例如将 123.mp3 的引用地址限制为 http://example.com/abc.htmlid=123456,这样下载工具就不太可能构造一个“正确”的引用地址了。
referer
要过滤掉盗链访问的referer信息,首先要明确知道,正常访问的referer有哪些。一般来说,正常的referer信息有以下四种:
- none:请求报文首部没有referer首部,比如用户直接在浏览器输入域名访问web网站,就没有referer信息。
- blocked:请求报文有referer首部,但无有效值,比如为空。
- 本站链接:referer首部中包含本站域名。
- 搜索引擎跳转:referer中为 * .baidu. * 、 * .google. * 、及其他搜索引擎(如360、必应)(具体图片或mp3媒体文件,不希望被搜索引擎引用,可单独设置,主页等html页面建议允许搜索引擎跳转)
所以根据官方文档,我们只需制定合适的匹配规则,将正常的访问放过,对那些“非正常的”盗链访问,返回403错误代码,即可实现防盗链。
valid_referers none blocked server_names *.example.com example.* www.example.org/galleries/ ~\.google\.; if ($invalid_referer) { return 403; }
过滤规则设置
打开nginx配置文件,找到想要定义的location下,加入下面设置
location /blog/ {
root /apps/nginx/html/; #定义路径
valid_referers none blocked server_names *.example.com ~\.google\. ~\.baidu\.;
#设置允许访问的匹配规则,匹配规则可以写在一行,也可以分行写。
if ($invalid_referer) { #设置条件判断,不符合上述规则的,返回403状态码
return 403;
}
}
location ^~ /mp3/ {
alias /apps/nginx/html/blog/mp3/; #定义路径,也可用root
valid_referers none blocked server_names *.example.com ;
if ($invalid_referer) { #设置条件判断,不符合上述规则的,返回403状态码
return 403;
}
}
也可在全局配置server中做设置,不过还是建议每个location单独设置,因为对于图片和或者音频视频文件本身,还是不希望直接被搜索引擎所引用,造成网站资源的无意义的消耗。
跳转设置
对于盗链者,也可以予以反击,允许他们请求我们的资源,不过,只给他们我们指定的资源,例如百度使用的防盗链图:
配置上只需将return 403;改为rewrite ^/ http://www.example/images/return.jpg; 例如:
location ~ return\.jpg$ {
root /apps/nginx/html/blog/images/
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
access_log off;
root /apps/nginx/html/blog/images/;
valid_referers none blocked server_names *.example.com ~\.google\. ~\.baidu\.;
if ($invalid_referer) {
rewrite ^/ http://www.example/images/return.jpg;
}
}
PS:return.jpg要设置规则优先匹配到,这个图片不能被防盗链,不然会无限重定向,导致显示不正常。
规则细节
设置匹配规则时,根据官方文档,只有none、blocked、server_names、arbitrary string和regular expression五种规则。
Parameters can be as follows:
none
the “Referer” field is missing in the request header;
blocked
the “Referer” field is present in the request header, but its value has been deleted by a firewall or proxy server; such values are strings that do not start with “http://” or “https://”;
server_names
the “Referer” request header field contains one of the server names;
arbitrary string
defines a server name and an optional URI prefix. A server name can have an “*” at the beginning or end. During the checking, the server’s port in the “Referer” field is ignored;
regular expression
the first symbol should be a “~”. It should be noted that an expression will be matched against the text starting after the “http://” or “https://”.
这就要求我们在设置匹配规则的时候,要按照这个五种方式来,none、blocked直接写上就可以了,没有什么可说的,我们重点理解下剩下三种。
server_names
server names字面上理解很容易,就是匹配的域名。注意:这里的域名,指本服务器上所有监听的域名。而且这是一个包含的关系,只要referer头部信息中包含有本服务器的监听的任意域名,即可通过匹配。
arbitrary string
翻译过来是任意字符串,其实就是任意可以匹配到到字符串,这里支持通配符。大致有2种写法:
- 直接写域名
例如可以写*.example.com,也可写为www.example.*,可问题是为什么就偏偏不支持 * .example. * 呢。这我也很费解,不过确实不支持,有兴趣的朋友可以去试一下,也希望能有大佬告知这其中的原理是什么。
[root@CentOS8 ~]#/apps/nginx/sbin/nginx -t
nginx: [emerg] invalid hostname or wildcard "*.example.*" in /apps/nginx/conf/nginx.conf:95
nginx: configuration file /apps/nginx/conf/nginx.conf test failed
- 定义匹配域名加路径
例如:www.example.com/blog;
而博主试验过很很多次,如果写成例如www.example.com/*,在www.example.com/blog/页面下去引用页面下的/apps/nginx/html/mp3/123.mp3文件时就会报403错误,而写域名加确切地址如www.example.com/blog时才可以访问。仔细查阅了官方文档,才知道,有个很关键的细节就是,这个通配符的位置,只能在域名里。可以再看一下官方文档,
defines a server name and an optional URI prefix. A server name can have an “*” at the beginning or end. During the checking, the server’s port in the “Referer” field is ignored;
我们可以得知,只可以在域名的开头和结尾用 * 的通配符,而不是URI中,这也就是为什么我发现www.examlpe.com/* 无法匹配通过的原因。
跟server_names一样,只要包含自定义字符串就可以,例如匹配规则写成www.example.com/mp3/,在www.example.com/mp3/页面下就可以引用的/apps/nginx/html/mp3/123.mp3文件了,在www.example.com/mp3/abc/efg/页面下是同样可以跳转访问/apps/nginx/html/mp3/123.mp3文件的。
regular expression
被指定的正则表达式模式匹配到的字符串,要使用 ~ 开头,例如:~.*.google.com。这要严格按照正则表达式匹配到的referer写,否则就会无法访问。
总结
设置匹配规则时,必须符合其中的某一种,而不能想当然的把几种规则混合起来使用,想要放行的链接,一定要考虑好,到底确切适用于哪一种规则,才不会出现”误伤“、“漏网”的情况。