为什么要动态封禁IP?

动态封禁IP说白了就是给访问过于“勤快”的用户踩个急刹车。

核心需求总结:

  1. 封禁恶意请求:比如那些试图暴力破解、SQL注入的攻击者。
  2. 动态管理黑名单:自动化拉黑,不用手动更新配置文件。
  3. 封禁时间设置:可控时长,让被封禁的IP有“改过自新”的机会。

技术选型:Nginx + Lua + Redis

整个方案看起来就像一顿营养均衡的程序员套餐:Nginx负责拦截,Lua负责逻辑,Redis负责存储。具体来说:
  • Nginx:作为反向代理,用它的性能处理高并发。
  • Lua:通过OpenResty框架,可以灵活编写动态逻辑。
  • Redis:存储IP黑名单,高效快速,支持过期时间。
如果还在用iptables封IP,那就像用Excel做演示文稿,虽然能用,但不优雅,效率也感人。

环境配置

  1. 操作系统:CentOS 7 或 Ubuntu,其他主流发行版也行。
  2. Redis:建议使用 5.0.5 或更高版本。
  3. Nginx:推荐 Nginx-OpenResty,它让Lua配置更加方便。
示例环境安装(CentOS为例):
  1. # 安装OpenResty
  2. sudo yum install -y yum-utils
  3. sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
  4. sudo yum install -y openresty
  5. # 安装Redis
  6. sudo yum install -y epel-release
  7. sudo yum install -y redis
  8. sudo systemctl start redis
  9. sudo systemctl enable redis

配置实现

下面是一个实际配置的完整流程:

Nginx 配置

在Nginx配置文件的<font style="color:rgb(30, 107, 184);">server</font>块中,添加Lua脚本的位置:
  1. http {
  2. lua_shared_dict ip_blacklist 10m; # 定义共享字典存储黑名单
  3. server {
  4. listen 80;
  5. location / {
  6. access_by_lua_file /etc/nginx/lua/block_ip.lua;
  7. proxy_pass http://backend;
  8. }
  9. }
  10. }

Lua 脚本

这段脚本会连接Redis并检查访问频率,逻辑大致如下:
  1. 获取用户IP。
  2. 检查IP是否在黑名单中。
  3. 如果IP访问频率超标,加入黑名单。
  4. 黑名单的封禁时间由Redis设置。
  1. -- 引入Redis
  2. local redis = require "resty.redis"
  3. -- 定义Redis连接函数
  4. local function connect_redis()
  5. local red = redis:new()
  6. red:set_timeout(1000) -- 1秒超时
  7. local ok, err = red:connect("127.0.0.1", 6379)
  8. if not ok then
  9. ngx.log(ngx.ERR, "Failed to connect to Redis: ", err)
  10. return nil
  11. end
  12. return red
  13. end
  14. -- 获取客户端IP
  15. local client_ip = ngx.var.remote_addr
  16. -- Redis存储逻辑
  17. local red = connect_redis()
  18. if not red then
  19. ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  20. end
  21. -- 检查是否在黑名单
  22. local is_blocked = red:get("block:" .. client_ip)
  23. if is_blocked == "1" then
  24. ngx.exit(ngx.HTTP_FORBIDDEN) -- 返回403
  25. end
  26. -- 记录访问次数
  27. local count, err = red:incr("count:" .. client_ip)
  28. if not count then
  29. ngx.log(ngx.ERR, "Failed to increment count: ", err)
  30. ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  31. end
  32. -- 设置访问次数过期时间
  33. if count == 1 then
  34. red:expire("count:" .. client_ip, 10) -- 10秒过期
  35. end
  36. -- 如果超过阈值,加入黑名单
  37. if count > 100 then
  38. red:set("block:" .. client_ip, "1")
  39. red:expire("block:" .. client_ip, 60) -- 封禁1分钟
  40. ngx.exit(ngx.HTTP_FORBIDDEN)
  41. end

工作原理小解析

  1. IP 获取与检测:脚本中<font style="color:rgb(30, 107, 184);">ngx.var.remote_addr</font>直接获取客户端IP。
  2. Redis 计数:每次请求都会增加计数器,如果超出阈值,则将IP加入黑名单。
  3. 黑名单封禁:Redis的<font style="color:rgb(30, 107, 184);">expire</font>功能自动处理封禁时效,不需要我们操心。

优势分析

  1. 轻量级:Nginx和Lua结合,性能开销极小。
  2. 动态性强:IP黑名单动态更新,无需重启服务。
  3. 可扩展:只需修改脚本逻辑,就可以实现更复杂的规则,比如白名单、访问频率统计等。

应用场景扩展

除了拦截爬虫和恶意用户,这套方案还能应用在:
  • 防DDoS攻击:结合Nginx的限速模块。
  • 防止数据滥用:比如对API接口进行访问频率限制。
  • 异常检测:进一步扩展,可以接入日志分析系统。
有次朋友部署类似方案时,发现黑名单全是公司的内网IP。原因呢?因为所有请求都走了负载均衡,Nginx获取到的是负载均衡器的IP。 😅 小提示:这种情况可以通过<font style="color:rgb(30, 107, 184);">X-Forwarded-For</font>头获取真实IP。 最后,动态封禁IP虽然不能防住“所有坏人”,但就像“锁车门不能防小偷,但能让他去偷别人的车”,还是很有必要的!