通过nginx防盗链实现拒绝直接访问图片

有用户提了个需求,要实现拒绝图片直接访问,只允许在站内引用。
之前说过关于防盗链,现在还是通过防盗链方式实现。

# nginx 配置

server
{
	略·
	location ~* .(gif|jpg|png|jpeg)$
	{
		valid_referers server_names rootop.org; 

		if ($invalid_referer) 
		{ 
			return 403;
		}
	}
}

防盗链 配置中none,blocked去掉就是不允许referer头为空,去掉blocked就是不以http://或https://开头的referer拒绝。
这样直接访问图片会返回403,只有在页面中引用时,才可访问。

PS:
valid_referers 可以忽略客户端请求头referer头中的端口。

nginx实现响应内容修改

ngx_http_sub_module模块是一个替换响应内容的模块,可以实现对请求返回的内容进行修改。
此模块nginx自带,但是需要编译nginx时加上此模块

--with-http_sub_module

官方文档:http://nginx.org/en/docs/http/ngx_http_sub_module.html

server
{
    listen 80 default_server;
    server_name www.a.com;
    index index.html;
    root /www/www.a.com;
    
    sub_filter 'index.html' '替换的内容1';
    sub_filter 'index.php' '替换的内容2';
    sub_filter_once on;
    sub_filter_types *;
}

# 测试html代码

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>测试</title>
</head>
<body>

	
<h3>第1行内容index.html</h3>

	
<h3>第2行内容index.html</h3>

	
<h3>第3行内容index.php</h3>

	
<h3>第4行内容index.php</h3>


</body>
</html>

当sub_filter_once on;值为on时,替换的内容只会替换一次,如果为off,则所有匹配的字符都会替换。
sub_filter_types 指对哪些mime-type类型替换。可以参考nginx下的mime.types文件。星号代表所有。

比如当反向代理后端服务时,后端服务代码里把资源地址写死时,可用此方法应急。

看网上有说开启gzip on;压缩功能时,导致无法替换,经过测试发现是可以。
可能是以前老版本的nginx不支持,这里测试本功能用的版本为nginx/1.16.0

nginx反向代理加防盗链配置

# 配置例子

server
{
	listen   80;
	server_name  www.rootop.org;

	root /home/web/www.rootop.org;
	
	location ~ .*\.(jpg|png|js|css)$
	{
		valid_referers none blocked server_names rootop.org;
		if ($invalid_referer)
		{
			return 404;
		}
		
		proxy_pass http://127.0.0.1:8080;
	}
	
	location /
	{
		proxy_pass http://127.0.0.1:8080;
	}
}

# 注意在防盗链的location中也要加一句proxy_pass,否则图片会从本地找,肯定会返回404状态码。

location ~ .*\.(jpg|png|js|css)$
{
	valid_referers none blocked server_names rootop.org;
	if ($invalid_referer)
	{
		return 404;
	}
	
	proxy_pass http://127.0.0.1:8080;
}

如果不加proxy_pass,在return 404这个地方很容易扰乱排错思路,以为是防盗链返回的404,实际是从本地找不到文件才返回的404.

# 防盗链参数解释

valid_referers 定义一个防盗链白名单
none 表示http请求头中没有Referer这个头信息
blocked 表示http请求头中虽然有Referer头信息,但是中间被防火墙或者被代理服务器删除。
server_names 表示设置的server_name
rootop.org 表示允许的Referer,支持通配符,也支持正则

符合以上条件的,nginx会将$invalid_referer这个内置变量的值设置为空字符串。
不符合的,也就是说Referer头信息不符合以上条件,比如 baidu.com,则会将$invalid_referer变量设置为1
这样通过if条件判断进行返回状态码,或者重定向到其他页面,比如一张图片。

参考官方文档:http://nginx.org/en/docs/http/ngx_http_referer_module.html#valid_referers

nginx if多条件判断的实现

if判断本身不支持多条件判断,用不了 && 或者|| 这种逻辑运算,而且if也不支持嵌套。
可以通过set变量来迂回实现。

比如这里我要实现访问a.rootop.org跳转到匹配子域名到指定的html页面。
dns和nginx已经配置了泛域名

实现效果:
a.rootop.org -> rootop.org/pages/a.html
b.rootop.org -> rootop.org/pages/b.html

但是这里要排除www.rootop.org的跳转

server
{
	listen 80;
	server_name rootop.org www.rootop.org *.rootop.org;
	root html;
	
	# 子域名跳转指定html页面
	# 先设置个变量
	set $subdomain "";
	
	# 正则获取三级域(也就是主机名)
	if ($http_host ~* "^(.*)\.rootop\.org$")
	{
		set $subdomain $1;
	}

	# 排除www
	if ($subdomain = "www")
	{
		set $subdomain "";
	}

	# 除www外的实现跳转
	if ($subdomain != "")
	{
		# 注意加上$scheme,如果是rootop.org/pages/xxx.html,则会认为是在域名后面加了个目录地址。
		return 301 $scheme://rootop.org/pages/$subdomain.html;
	}
}

如果想反过来实现也可以

# html页面跳转子域名
if ($uri ~* ".*/pages/(.*)\.html")
{
	return 301 $scheme://$1.rootop.org;
}

supervisor安装及配置

# 安装

[root@VM_0_7_centos ~]# yum install -y supervisor

# 查看释放了哪些文件

[root@VM_0_7_centos ~]# rpm -ql supervisor-3.1.4-1.el7.noarch
/etc/logrotate.d/supervisor
/etc/supervisord.conf
/etc/supervisord.d
/etc/tmpfiles.d/supervisor.conf
/usr/bin/echo_supervisord_conf
/usr/bin/pidproxy
/usr/bin/supervisorctl
/usr/bin/supervisord
略···

可以看出/etc/supervisord.conf是主配置文件。

[root@VM_0_7_centos ~]# cat /etc/supervisord.conf | grep -vE "^;|^$"
[unix_http_server]
file=/var/run/supervisor/supervisor.sock   ; (the path to the socket file)
[inet_http_server]         ; inet (TCP) server disabled by default
port=*:9001        ; (ip_address:port specifier, *:port for all iface)
username=root              ; (default is no username (open server))
password=root               ; (default is no password (open server))
[supervisord]
logfile=/var/log/supervisor/supervisord.log  ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB       ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10          ; (num of main logfile rotation backups;default 10)
loglevel=info               ; (log level;default info; others: debug,warn,trace)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false              ; (start in foreground if true;default false)
minfds=1024                 ; (min. avail startup file descriptors;default 1024)
minprocs=200                ; (min. avail process descriptors;default 200)
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor/supervisor.sock ; use a unix:// URL  for a unix socket
[include]
files = supervisord.d/*.ini

# 解释

[inet_http_server]
port=*:9001
username=root
password=root

这4行去掉前面的注释后,即为开启web端管理,默认端口9001,supervisor提供了一个简易的管理界面。

files = supervisord.d/*.ini

这行表明启动时加载supervisord.d目录下的所有ini文件,这样可以把要管理的程序独立配置。

# 添加一个shell脚本测试

[root@VM_0_7_centos ~]# cat test.sh 
#!/bin/bash

for i in {1..20}
do
	current_time=`date`
	echo $current_time
	sleep 1
done

循环20次,打印当前时间

# 创建配置文件

[root@VM_0_7_centos ~]# cd /etc/supervisord.d/
[root@VM_0_7_centos supervisord.d]# vi test_script_supervisor.ini
[program:test]
command=/root/test.sh           
directory=/root             
priority=999  
autostart=true         
autorestart=true 
startsecs=5
startretries=3
user=root       

配置参数含义可以参考主配置文件中的配置案例,每个参数后面都带着解释。或者百度可以找到。
# 看一下supervisorctl命令用法

[root@VM_0_7_centos supervisord.d]# supervisorctl --help
supervisorctl -- control applications run by supervisord from the cmd line.

Usage: /usr/bin/supervisorctl [options] [action [arguments]]

Options:
-c/--configuration -- configuration file path (default /etc/supervisord.conf)
-h/--help -- print usage message and exit
-i/--interactive -- start an interactive shell after executing commands
-s/--serverurl URL -- URL on which supervisord server is listening
     (default "http://localhost:9001").
-u/--username -- username to use for authentication with server
-p/--password -- password to use for authentication with server
-r/--history-file -- keep a readline history (if readline is available)

action [arguments] -- see below

Actions are commands like "tail" or "stop".  If -i is specified or no action is
specified on the command line, a "shell" interpreting actions typed
interactively is started.  Use the action "help" to find out about available
actions.

[root@VM_0_7_centos supervisord.d]# supervisorctl help

default commands (type help <topic>):
=====================================
add    clear  fg        open  quit    remove  restart   start   stop  update 
avail  exit   maintail  pid   reload  reread  shutdown  status  tail  version
上面为支持的动作

[root@VM_0_7_centos supervisord.d]# supervisorctl help update
update			Reload config and add/remove as necessary
update all		Reload config and add/remove as necessary
update <gname> [...]	Update specific groups

# 重载supervisor配置

[root@VM_0_7_centos supervisord.d]# supervisorctl update
test: added process group
[root@VM_0_7_centos supervisord.d]# supervisorctl status
test                             FATAL     command at '/root/test.sh' is not executable 

# 通过 supervisorctl status 加配置中 program 后面的名字可以查看添加的服务状态

[root@VM_0_7_centos supervisord.d]# supervisorctl status test
test                             FATAL     command at '/root/test.sh' is not executable

看到提示致命错误,提示不可执行。说明没有执行权限。

[root@VM_0_7_centos supervisord.d]# chmod 700 /root/test.sh  # 我是用root权限执行,所以给了个700权限
[root@VM_0_7_centos supervisord.d]# supervisorctl start test
test: started
[root@VM_0_7_centos supervisord.d]# supervisorctl status test
test                             RUNNING   pid 19487, uptime 0:00:07
[root@VM_0_7_centos supervisord.d]# ps aux | grep test
root     19824  0.0  0.0 115308  1504 ?        S    20:25   0:00 /bin/bash /root/test.sh

通过web界面可以查看、管理服务

如果想记录脚本执行日志,可通过/etc/supervisord.conf中的配置案例配置服务的输出日志。