openresty开发系列38--通过Lua+Redis 实现动态封禁IP
openresty开发系列38--通过Lua+Redis 实现动态封禁IP 一)需求背景为了封禁某些爬虫或者恶意用户对服务器的请求,我们需要建立一个动态的 IP 黑名单。对于黑名单之内的 IP ,拒绝提供服务。 二)设计方案实现 IP 黑名单的功能有很多途径:1、在操作系统层面,配置 iptables,拒绝指定 IP 的网络请求;2、在 Web Server 层面,通过 Nginx 自身的 deny 选项 或者 lua 插件 配置 IP 黑名单;3、在应用层面,在请求服务之前检查一遍客户端 IP 是否在黑名单。 为了方便管理和共享,我们通过 Nginx+Lua+Redis 的架构实现 IP 黑名单的功能 如图 配置nginx.conf在http部分,配置本地缓存,来缓存redis中的数据,避免每次都请求redis lua_shared_dict shared_ip_blacklist 8m; #定义ip_blacklist 本地缓存变量 location /ipblacklist { access_by_lua_file /usr/local/lua/access_by_limit_ip.lua; echo "ipblacklist";}
# 编辑 /usr/local/lua/access_by_limit_ip.lualocal function close_redis(red)if not red then returnend --释放连接(连接池实现) local pool_max_idle_time = 10000 --毫秒 local pool_size = 100 --连接池大小 local ok, err = red:set_keepalive(pool_max_idle_time, pool_size) if not ok then ngx.say("set keepalive error : ", err) end endlocal function errlog(...)ngx.log(ngx.ERR, "redis: ", ...) endlocal function duglog(...)ngx.log(ngx.DEBUG, "redis: ", ...) endlocal function getIp()local myIP = ngx.req.get_headers()["X-Real-IP"]if myIP == nil thenmyIP = ngx.req.get_headers()["x_forwarded_for"]endif myIP == nil thenmyIP = ngx.var.remote_addrendreturn myIP; endlocal key = "limit:ip:blacklist" local ip = getIp(); local shared_ip_blacklist = ngx.shared.shared_ip_blacklist--获得本地缓存的最新刷新时间 local last_update_time = shared_ip_blacklist:get("last_update_time");if last_update_time ~= nil then local dif_time = ngx.now() - last_update_time if dif_time < 60 then --缓存1分钟,没有过期if shared_ip_blacklist:get(ip) thenreturn ngx.exit(ngx.HTTP_FORBIDDEN) --直接返回403endreturnend endlocal redis = require "resty.redis" --引入redis模块 local red = redis:new() --创建一个对象,注意是用冒号调用的--设置超时(毫秒) red:set_timeout(1000) --建立连接 local ip = "10.11.0.215" local port = 6379 local ok, err = red:connect(ip, port) if not ok then close_redis(red)errlog("limit ip cannot connect redis"); elselocal ip_blacklist, err = red:smembers(key);if err thenerrlog("limit ip smembers");else--刷新本地缓存,重新设置 shared_ip_blacklist:flush_all();--同步redis黑名单 到 本地缓存for i,bip in ipairs(ip_blacklist) do--本地缓存redis中的黑名单shared_ip_blacklist:set(bip,true);end--设置本地缓存的最新更新时间shared_ip_blacklist:set("last_update_time",ngx.now());end end if shared_ip_blacklist:get(ip) thenreturn ngx.exit(ngx.HTTP_FORBIDDEN) --直接返回403 end
当redis设置了密码时代码如下:
[root@node5 lua]# cat /usr/local/lua/access_by_limit_ip.lua
local function close_reis(red)if not red thenreturnendlocal pool_max_idle_time = 10000local pool_size = 100local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)if not ok thenngx.say("set keepalive error :", err)end endlocal function errlog(...)ngx.log(ngx.ERR, "redis: ", ...) endlocal function duglog(...)ngx.log(ngx.DEBUG, "redis: ",...) endlocal function getIp()local myip = ngx.req.get_headers()["X-Real-IP"]if myip == nil thenmyip = ngx.req.get_headers()["x_forwarded_for"]endif myip == nil thenmyip = ngx.var.remote_addrendreturn myip endlocal key = "limit:ip:blacklist" local ip = getIp(); local shared_ip_blacklist = ngx.shared.shared_ip_blacklistlocal last_update_time = shared_ip_blacklist:get("last_update_time");if last_update_time ~= nil thenlocal dif_time = ngx.now() - last_update_timeif dif_time < 60 thenif shared_ip_blacklist:get(ip) thenreturn ngx.exit(ngx.HTTP_FORBIDDEN)endreturnend endlocal redis = require "resty.redis" local red = redis:new()red:set_timeout(1000) local ip = "10.11.0.215" local port = 6379 local ok, err = red:connect(ip,port)local count, err = red:get_reused_times() if 0 == count then ----新建连接,需要认证密码ok, err = red:auth("redis123")if not ok thenngx.say("failed to auth: ", err)returnend elseif err then ----从连接池中获取连接,无需再次认证密码ngx.say("failed to get reused times: ", err)return endif not ok thenclose_redis(red)errlog("limit ip cannot connect redis"); elselocal ip_blacklist, err = red:smembers(key)if err thenerrlog("limit ip smembers")elseshared_ip_blacklist:flush_all();for i,bip in ipairs(ip_blacklist) doshared_ip_blacklist:set(bip, true);endshared_ip_blacklist:set("last_update_time", ngx.now());end endif shared_ip_blacklist:get(ip) thenreturn ngx.exit(ngx.HTTP_FORBIDDEN) end
用户redis客户端设置:
添加黑名单IP:
sadd limit:ip:blacklist 10.11.0.148
获取黑名单IP:
smembers limit:ip:blacklist
10.11.0.215:6379> sadd limit:ip:blacklist 10.11.0.148
10.11.0.215:6379> sadd limit:ip:blacklist 10.11.0.215
10.11.0.215:6379> smembers limit:ip:blacklist
1) "10.11.0.215"
2) "10.11.0.148"
10.11.0.215:6379> smembers limit:ip:blacklist
1) "10.11.0.215"
2) "10.11.0.148"
此方法目前只能实现手动添加黑名单IP进行IP封禁,在某些场景如:半夜如果有人恶意爬取网站服务器可能导致服务器资源耗尽崩溃或者影响业务
下面是改进后的代码,可以实现自动将访问频次过高的IP地址加入黑名单封禁一段时间
nginx.conf配置部分:
location /goodslist {
set $business "USER";
access_by_lua_file /usr/local/lua/access_count_limit.lua;
echo "get goods list success";
}
lua代码:
[root@node5 lua]# cat /usr/local/luaaccess_count_limit.lua
local function close_redis(red)if not red thenreturnendlocal pool_max_idle_time = 10000local pool_size = 100local ok, err = red:set_keepalive(pool_max_idle_tme, pool_size)if not ok thenngx.say("set keepalive err : ", err)end endlocal ip_block_time=300 --封禁IP时间(秒) local ip_time_out=30 --指定ip访问频率时间段(秒) local ip_max_count=20 --指定ip访问频率计数最大值(秒) local BUSINESS = ngx.var.business --nginx的location中定义的业务标识符--连接redis local redis = require "resty.redis" local conn = redis:new() ok, err = conn:connect("10.11.0.215", 6379) conn:set_timeout(2000) --超时时间2秒--如果连接失败,跳转到脚本结尾 if not ok then--goto FLAG close_redis(conn) endlocal count, err = conn:get_reused_times() if 0 == count then ----新建连接,需要认证密码ok, err = conn:auth("redis123")if not ok thenngx.say("failed to auth: ", err)returnend elseif err then ----从连接池中获取连接,无需再次认证密码ngx.say("failed to get reused times: ", err)return end--查询ip是否被禁止访问,如果存在则返回403错误代码 is_block, err = conn:get(BUSINESS.."-BLOCK-"..ngx.var.remote_addr) if is_block == '1' thenngx.exit(403)close_redis(conn) end--查询redis中保存的ip的计数器 ip_count, err = conn:get(BUSINESS.."-COUNT-"..ngx.var.remote_addr)if ip_count == ngx.null then --如果不存在,则将该IP存入redis,并将计数器设置为1、该KEY的超时时间为ip_time_outres, err = conn:set(BUSINESS.."-COUNT-"..ngx.var.remote_addr, 1)res, err = conn:expire(BUSINESS.."-COUNT-"..ngx.var.remote_addr, ip_time_out) elseip_count = ip_count + 1 --存在则将单位时间内的访问次数加1if ip_count >= ip_max_count then --如果超过单位时间限制的访问次数,则添加限制访问标识,限制时间为ip_block_timeres, err = conn:set(BUSINESS.."-BLOCK-"..ngx.var.remote_addr, 1)res, err = conn:expire(BUSINESS.."-BLOCK-"..ngx.var.remote_addr, ip_block_time)elseres, err = conn:set(BUSINESS.."-COUNT-"..ngx.var.remote_addr,ip_count)res, err = conn:expire(BUSINESS.."-COUNT-"..ngx.var.remote_addr, ip_time_out)end end-- 结束标记 local ok, err = conn:close()
# redis的数据
10.11.0.215:6379> get USER-COUNT-10.11.0.148
"16"
10.11.0.215:6379> get USER-BLOCK-10.11.0.148
(nil)
四、总结 以上,便是 Nginx+Lua+Redis 实现的 IP 黑名单功能,具有如下优点: 1、配置简单、轻量,几乎对服务器性能不产生影响; 2、多台服务器可以通过Redis实例共享黑名单; 3、动态配置,可以手工或者通过某种自动化的方式设置 Redis 中的黑名单。
转载于:https://www.cnblogs.com/reblue520/p/11419918.html
openresty开发系列38--通过Lua+Redis 实现动态封禁IP相关推荐
- Openresty通过Lua+Redis 实现动态封禁IP
需求背景 为了封禁某些爬虫或者恶意用户对服务器的请求,我们需要建立一个动态的 IP 黑名单.对于黑名单之内的 IP ,拒绝提供服务.并且可以设置失效 环境准备 linux version:centos ...
- Nginx 通过 Lua + Redis 实现动态封禁 IP 1
欢迎关注方志朋的博客,回复"666"获面试宝典 来源:segmentfault.com/a/1190000018267201 背景 为了封禁某些爬虫或者恶意用户对服务器的请求,我们 ...
- Nginx 通过 Lua + Redis 实现动态封禁 IP
背景 为了封禁某些爬虫或者恶意用户对服务器的请求,我们需要建立一个动态的 IP 黑名单.对于黑名单之内的 IP ,拒绝提供服务. 架构 实现 IP 黑名单的功能有很多途径: 1.在操作系统层面,配置 ...
- openresty开发系列27--openresty中封装redis操作
openresty开发系列27--openresty中封装redis操作 在关于web+lua+openresty开发中,项目中会大量操作redis, 重复创建连接-->数据操作-->关闭 ...
- openresty开发系列26--openresty中使用redis模块
openresty开发系列26--openresty中使用redis模块 在一些高并发的场景中,我们常常会用到缓存技术,现在我们常用的分布式缓存redis是最知名的, 操作redis,我们需要引入re ...
- openresty开发系列24--openresty中lua的引入及使用
openresty开发系列24--openresty中lua的引入及使用 openresty 引入 lua 一)openresty中nginx引入lua方式 1)xxx_by_lua ---> ...
- Nginx+Lua脚本+Redis 实现自动封禁访问频率过高IP
前言:由于公司前几天短信接口被一直攻击,并且攻击者不停变换IP,导致阿里云短信平台上的短信被恶意刷取了几千条,然后在Nginx上对短信接口做了一些限制 临时解决方案: 1.查看Nginx日志发现被攻击 ...
- openresty开发系列40--nginx+lua实现获取客户端ip所在的国家信息
openresty开发系列40--nginx+lua实现获取客户端ip所在的国家信息 为了实现业务系统针对不同地区IP访问,展示包含不同地区信息的业务交互界面.很多情况下系统需要根据用户访问的IP信息 ...
- openresty开发系列37--nginx-lua-redis实现访问频率控制
openresty开发系列37--nginx-lua-redis实现访问频率控制 一)需求背景 在高并发场景下为了防止某个访问ip访问的频率过高,有时候会需要控制用户的访问频次 在openresty中 ...
最新文章
- cv2.cvtColor(img,p)图片格式转换的用法
- 如何给英特尔致命一击——高通公布10纳米ARM服务器芯片
- springboot: ajax异步提交表单
- OpenCV与Python之图像阈值化
- 会签 数据库表设计_关于数据库表设计和实体类设计的思考
- 减治法在查找算法中的应用(JAVA)--二叉查找树的查找、插入、删除
- C/C++语言函数参数里的“...”作用,va_list的使用(stdarg.h)
- 磁盘文件的正常读写与异步读写
- 两个简洁的页面:404和Loading
- 手机游戏开发工程师培训教程
- 设计模式(一) 简单工厂模式
- python查询12306余票_使用 Python 在 12306 查询火车票余票
- Web.17.EL表达式JSTL标签的使用
- 第一章 厕所被揍 校园江湖
- Pycharm的python interpreter选择
- [清华集训2014]玛里苟斯
- html保存至心愿单按钮,王者荣耀添加心愿单有什么用 心愿单怎么实现
- 电阻应变式测力压力称重传感器工作原理
- 在IDEA中使用MCR调用运行matlab代码
- 系统架构设计-安全篇
热门文章
- tinyxml语法讲解之写xml
- Map与Set的经典OJ题
- 蓝桥杯 历届试题 分考场(DFS+枚举)
- 优点 spark_spark(一)
- captura录屏没声音_电脑录屏有哪些好用的软件呢?
- 【必看】新手妹子一键删库,老司机机智救场
- 【实验】小型网络WLAN架构实战案例
- KubeDL 0.4.0 - Kubernetes AI 模型版本管理与追踪
- Mirantis 收购 Docker EE | 云原生生态周报 Vol. 28
- sqlite+php+函数大全,PHP SQLite SQLite 函数_编程学问网