之前介绍过在nginx里如何嵌入lua模块,利用nginx+lua可以很好的开发开发nginx的业务逻辑,并且达到高并发的效果。
下面我们就来介绍下利用nginx+lua+redis实现防采集的功能。
现象:
网站在为用户提供服务的同时也在被搜索引擎、采集器不断的抓取,可能会造成网站不堪重负,导致页面放回5XX错误。针对此种情况,我们就要对采集器及搜索引擎来进行访问控制,当然对搜索引擎的控制可能会影响网站的收录。
功能描述:
nginx+lua在前端实现客户端的访问控制,将客户端的访问信息记入redis中,如果超过访问频率的限制,则跳转到php生成的验证码界面;若验证通过则可以继续访问半小时,若验证不通过则被封锁半小时。由于采集器的ip可能变化,所以在此不会一直封锁。
1.nginx_lua模块安装
请参考前面“nginx和lua”的博文http://blog.csdn.net/yanggd1987/article/details/46679989
2.lua-resty-redis模块安装
cd /usr/local/src wget https://github.com/openresty/lua-resty-redis/archive/master.zip unzip master.zip cd lua-resty-redis-master mkdir -p /usr/local/nginx/lua #将lib拷贝到nginx安装目录下的lua文件夹内 cp -rf lib /usr/local/nginx/lua cd /usr/local/nginx/lua/lib ln -s redis.lua resty/redis.lua
3.在nginx目录下编写lua脚本
cd /usr/local/src wget https://github.com/openresty/lua-resty-redis/archive/master.zip unzip master.zip cd lua-resty-redis-master mkdir -p /usr/local/nginx/lua #将lib拷贝到nginx安装目录下的lua文件夹内 cp -rf lib /usr/local/nginx/lua cd /usr/local/nginx/lua/lib ln -s redis.lua resty/redis.lua cd /usr/local/nginx/lua vim access_test.lua package.path = "/usr/local/nginx/lua/?.lua;/usr/local/nginx/lua/lib/?.lua;" package.cpath = "/usr/local/nginx/lua/?.so;/usr/local/nginx/lua/lib/?.so;" --封禁ip时间 ip_bind_time = 300 --ip访问频率时间段 ip_time_out = 60 --ip访问频率计数最大值 connect_count = 60 --连接redis local redis = require "resty.redis" local cache = redis.new() local ok ,err = cache.connect(cache,"10.10.10.8","6381") cache:set_timeout(60000) --如果连接失败,跳转到label处 if not ok then goto label end --ip封禁key is_bind , err = cache:get("bind_"..ngx.var.remote_addr) --白名单 --验证码通过后,只需将white_ngx.var.remote_addr置为1并设置过期时间即可,在下次访问时将不会再做判断 is_white , err = cache:get("white_"..ngx.var.remote_addr) if tonumber(is_white) == 1 then goto label end --查询ip是否在封禁时间段内,若在则跳转到验证码页面 if tonumber(is_bind) == 1 then --ngx.say("block,跳转到验证码页") --base64编码 local source=ngx.encode_base64(ngx.var.scheme.."://"..ngx.var.host..ngx.var.request_uri) local dest="http://10.10.10.8/authcode.html".."?continue="..source --url_args编码 --local source=ngx.encode_args({continue=ngx.var.scheme.."://"..ngx.var.host..ngx.var.request_uri}) --local dest="http://10.10.10.8/authcode.html".."?"..source ngx.redirect(dest,302) goto label end --ip记录时间key start_time , err = cache:get("time_"..ngx.var.remote_addr) --ip计数key ip_count , err = cache:get("count_"..ngx.var.remote_addr) --如果ip记录时间的key不存在或者当前时间减去ip记录时间大于指定时间间隔,则重置时间key和计数key --如果当前时间减去ip记录时间小于指定时间间隔,则ip计数+1,并且ip计数大于指定ip访问频率,则设置ip的封禁key为1,同时设置封禁key的过期时间为封禁ip时间 if start_time == ngx.null or os.time() - tonumber(start_time) > ip_time_out then res , err = cache:set("time_"..ngx.var.remote_addr , os.time()) res , err = cache:set("count_"..ngx.var.remote_addr , 1) else ip_count = ip_count + 1 res , err = cache:incr("count_"..ngx.var.remote_addr) if ip_count >= connect_count then res , err = cache:set("bind_"..ngx.var.remote_addr , 1) --以下步骤交给php,若验证码不通过则设置bind过期时间,若验证码通过则设置white_ip为1并且设置其过期时间 --res , err = cache:expire("bind_"..ngx.var.remote_addr , ip_bind_time) end end ::label:: local ok , err = cache:close()
10.10.10.8/authcode.html为验证码页面,需要其他语言编写,在此就不再说了!
注意:
1.在生产环境中由于后端php需要记录远程客户端ip,因此需要在nginx代理上开启相关设置:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2.在应用生产环境中可能是多域名环境,ngx.exec和ngx.redirect的跳转方式不同,ngx.exec为内部跳转,ngx.redirect为外部跳转;
3.white_ip为白名单,若验证成功则添加白名单,并且设置白名单的过期时间;若验证不成功,则直接设置bind_ip的过期时间;若不验证,则会一直被封锁;
4.判断white_ip一定要放在bind_ip上面,因为验证已经添加白名单后则会直接跳过后续判断部分;
5.在跳转到验证码页面后,需要记录所要访问页面的url,以达到验证通过后跳转到所要访问的页面;
6.在访问时间内没有达到限制次数,则cout_ip和time_ip会被重置。
4.将lua脚本添加到相应的location下
location /test { access_by_lua_file '/usr/local/nginx1.6/lua/access.lua'; content_by_lua 'ngx.header.content_type = "text/plain" ngx.say("hello,world") '; }
在1分钟内,当访问次数达到100,就会跳转到验证码界面。
ps:在此感谢zengbin3013(http://blog.csdn.net/zengbin3013/article/details/9313979)博主,本脚本也是在他的基础上改的。
参考内容:
https://github.com/openresty/lua-resty-redis
https://github.com/openresty/lua-nginx-module
http://wiki.nginx.org/HttpLuaModule
版权声明:本文为博主原创文章,未经博主允许不得转载。