之前介绍过在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/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")
';
}
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