1. 项目背景与核心需求
当网站启用CDN服务后,所有客户端请求都会先经过CDN节点,这使得Nginx日志中记录的IP地址全部变成了CDN服务器的IP。这种场景下,传统的deny指令会直接屏蔽CDN节点IP,导致所有流量被阻断。我们需要一种机制,能够穿透CDN获取真实的客户端IP,并基于真实IP实施精准封禁。
典型的业务场景包括:
- 防御CC攻击时精准封禁攻击源
- 针对恶意爬虫实施IP黑名单
- 限制特定地区的访问权限
- 防止暴力破解等安全威胁
2. 核心技术原理解析
2.1 HTTP头部传递机制
CDN服务商普遍采用X-Forwarded-For(XFF)头部传递原始IP,其格式为:
X-Forwarded-For: 客户端IP, 代理服务器1 IP, 代理服务器2 IP...例如经过多层代理的情况:
X-Forwarded-For: 203.0.113.45, 198.51.100.22, 192.0.2.672.2 Nginx变量处理逻辑
Nginx内置变量说明:
$remote_addr:直接TCP连接的客户端IP(即CDN节点IP)$http_x_forwarded_for:完整的XFF头信息$realip_remote_addr:realip模块解析后的真实IP
3. 完整配置方案实现
3.1 基础环境准备
确认Nginx编译时包含--with-http_realip_module:
nginx -V 2>&1 | grep -o with-http_realip_module3.2 核心配置代码
http { # 定义可信CDN IP段(必须配置) set_real_ip_from 203.0.113.0/24; set_real_ip_from 198.51.100.0/24; # 使用从右向左的解析方式 real_ip_recursive on; real_ip_header X-Forwarded-For; # IP黑名单存储路径 geo $blocked_ip { default 0; include /etc/nginx/conf.d/ip_blacklist.conf; } server { location / { if ($blocked_ip) { return 403; # 或使用更激进的措施 # return 444; } # 正常业务配置... } } }3.3 黑名单管理方案
动态更新黑名单的脚本示例:
#!/bin/bash # 添加IP到黑名单 echo "1.2.3.4 1;" >> /etc/nginx/conf.d/ip_blacklist.conf nginx -t && nginx -s reload推荐使用自动化工具管理:
# 使用fail2ban自动封禁 fail2ban-regex /var/log/nginx/access.log '^<HOST>.*" 403 '4. 高级防护策略
4.1 多维度防御配置
map $http_user_agent $bad_bot { default 0; "~*(AhrefsBot|SemrushBot|MJ12bot)" 1; } map $http_referer $bad_referer { default 0; "~*(spam-site.com|fake-traffic.org)" 1; } server { if ($bad_bot) { return 403; } if ($bad_referer) { return 403; } }4.2 速率限制配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; location /api/ { limit_req zone=api_limit burst=20 nodelay; # 业务逻辑... }5. 实战问题排查指南
5.1 常见错误案例
案例1:错误配置导致403
# 错误示范:未设置可信IP段 real_ip_header X-Forwarded-For;案例2:IP获取不准确
# 需要添加递归解析 real_ip_recursive on;5.2 调试技巧
查看真实IP获取情况:
location /ipinfo { add_header Content-Type text/plain; return 200 "Remote: $remote_addr\nRealIP: $realip_remote_addr\nXFF: $http_x_forwarded_for"; }日志格式建议:
log_format main '$remote_addr - $realip_remote_addr [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"';6. 性能优化建议
- 使用
geoip2模块进行高效IP匹配:
load_module modules/ngx_http_geoip2_module.so; http { geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb { $geoip2_data_country_code country iso_code; } map $geoip2_data_country_code $allowed_country { default no; CN yes; US yes; } }- 黑名单IP使用内存存储:
geo $blocked_ip { ranges; default 0; 192.168.1.0-192.168.1.255 1; }- 定期清理无效规则:
# 清理超过30天的封禁IP find /etc/nginx/conf.d/ -name "ip_blacklist.conf" -mtime +30 -exec sed -i '/old_ip/d' {} \;7. 企业级扩展方案
7.1 动态黑名单API
location /api/blacklist { auth_request /auth; allow 10.0.0.0/8; deny all; content_by_lua_block { local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) -- 1 sec local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.say("failed to connect: ", err) return end local res, err = red:smembers("ip:blacklist") if not res then ngx.say("failed to get blacklist: ", err) return end ngx.say(table.concat(res, "\n")) } }7.2 结合WAF联动
location / { access_by_lua_file /etc/nginx/lua/waf.lua; # 正常业务配置... }waf.lua示例片段:
local function is_ip_banned(ip) -- 调用安全API检查 local http = require "resty.http" local httpc = http.new() local res, err = httpc:request_uri( "http://security-api/check", {method = "GET", query = {ip = ip}} ) if res and res.status == 200 then return res.body == "true" end return false end8. 安全加固建议
- 防止IP欺骗:
# 限制XFF头部长度 map $http_x_forwarded_for $xff_valid { default $http_x_forwarded_for; "~*^([^,]{1,15})(,[^,]{1,15}){0,4}$" $1; "" $remote_addr; }- 关键操作二次验证:
location /admin { satisfy any; allow 192.168.1.100; deny all; auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; }- 定期审计配置:
# 检查异常封禁 awk '$9 == 403 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr