马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
给内网系统、后台管理页、或者某个监测接口做访问控制时,「只允许几个固定 IP 访问」是最直接的需求。Nginx 原生就能做:用 allow/deny 做基础黑白名单,用 geo 模块把客户端 IP 映射成一个变量,再配合 if 做条件判断与跳转,复杂一点还能用 set_by_lua 接入动态名单。本文给出一份可直接抄的完整配置,并说明每段的作用与常见误用。
一、需求与整体思路
目标:主站点正常对所有客户端开放,但若干内部路径(例如管理后台、健康检查专属页、某个需要二次限制的 API)只允许白名单 IP 访问;不在名单里的请求,返回提示页或一个跳转,而不是粗暴 403。
实现分三层:
- geo 模块:把
$remote_addr 映射成一个自定义变量(如 $ip_whitelist),便于后续复用;
- allow / deny:在 location 或 server 段做最基础的放行与拒绝;
- if 判断 + 跳转:命中非白名单时
rewrite 到一个提示页(如 /warn_navigate_page),给出明确反馈。
二、http 块里定义 geo 变量
geo 只能写在 http 上下文里,不能写在 server 或 location 里。写一段最基础的名单:
geo $remote_addr $ip_whitelist {
default 0; # 默认不在白名单
10.0.0.0/8 1; # 内网整段放行
192.168.1.0/24 1;
203.0.113.0/24 1; # 某台办公出口
}
几个要点:
default 0 一定要写,不写的话未匹配的地址变量为空,条件判断容易走错分支;
- geo 支持 CIDR 网段写法,不需要给每个 IP 单独写一行;
- geo 里也可以放
include,把 IP 段抽到独立文件(见后文 include ip.conf 的用法),便于热更新而不必 reload 整份主配置。
三、用 allow / deny 做兜底
最简版本,写在 server 或 location 里:
location /admin/ {
allow 10.0.0.0/8;
allow 203.0.113.0/24;
deny all;
proxy_pass http://127.0.0.1:8080;
}
匹配规则是有序的、遇到第一个命中就停止:deny all 放在 allow 前面就会把所有请求拒掉,这是最常见的配置事故。另外 allow/deny 在 geo(读变量)与方向(写规则)上的语义不同,别混用。
四、把名单抽成独立文件
当白名单很长时,单独维护一个 ip.conf 放在 conf 目录,在 http 块里 include:
http {
include mime.types;
default_type application/octet-stream;
include ip.conf; # 里面全是 geo 的网段规则
}
ip.conf 内容(注意每一行是「网段 + 值」):
default 0;
10.0.0.0/8 1;
172.16.10.0/24 1;
203.0.113.0/24 1;
这样单独改一个文件就能调整名单,配合 nginx -s reload 生效,主配置不用动,也方便纳管到配置中心。
五、用 if 做条件跳转
geo 变量有了,location 里就能判断。写法是取 $ip_whitelist 的值,命中非白名单(值不等于 1)时重定向到提示页:
server {
listen 80;
server_name example.com;
location = /warn_navigate_page {
default_type text/html;
return 200 '<h1>当前网络环境不允许访问</h1>';
}
location / {
if ($ip_whitelist <> "1") {
rewrite ^ /warn_navigate_page redirect;
}
proxy_pass http://backend_upstream;
}
}
这里有两个容易踩的点:
if 在 location 里被戏称为「Nginx 的魔法 if」,能干的事有限,只在 return / rewrite 这类简单动作里用;要写复杂逻辑请交给 real_ip/map/lua;
- 比较字符串要用
= 或 !=,写成 if ($ip_whitelist = 1) 表示在白名单放行,否则走兜底;
rewrite ... redirect 会返回 302,客户端会再请求一次;若不想多一次往返,可改用 return 403 或 return 302 /warn_navigate_page。
六、进阶:用 set_by_lua 接动态名单
当白名单存在数据库、Redis 或者需要按时间段变化时,纯配置文件就力不从心了,可以引入 lua-nginx-module 的 set_by_lua:
http {
lua_package_path "/usr/local/lualib/?.lua;;";
server {
location / {
set_by_lua $whitelist_flag '
local ip = ngx.var.remote_addr
-- 从共享字典或 Redis 里读白名单,这里示例读计数器
local dict = ngx.shared.whitelist_dict
return dict and dict:get(ip) or "0"
';
if ($whitelist_flag = "1") {
proxy_pass http://backend_upstream;
}
rewrite ^ /warn_navigate_page redirect;
}
}
}
生产使用要注意:lua 代码里不要做阻塞式 I/O(直接连 Redis 如果不用 cosocket 会阻塞 worker),通常配合 lua_shared_dict 缓存名单;另外要限制 lua_code_cache on 的边界,避免热更新后旧代码长期驻留。
七、验证与生效
改完先自检语法,再 reload:
nginx -t
nginx -s reload
从本机、白名单 IP、非白名单 IP 三个来源各 curl 一次:
curl -I -H "X-Forwarded-For: 203.0.113.5" http://127.0.0.1/
curl -I -H "X-Forwarded-For: 198.51.100.9" http://127.0.0.1/
另外提醒一句:如果前面还有 CDN 或七层代理,$remote_addr 拿到的会是代理 IP,此时要先用 set_real_ip_from + real_ip_header X-Forwarded-For 把真实客户端 IP 取出来,再交给 geo 判断,否则白名单永远不命中。配完记得 nginx -t 通过再 reload,reload 失败不会中断线上请求,但本次修改未生效。
八、小结
整件事的配置骨架就三行:http 块用 geo 把 $remote_addr 映射成 $ip_whitelist(记得写 default) → allow/deny 做基础兜底(顺序敏感,deny all 放最后) → location 里 if 判断后 rewrite 到提示页。名单长了抽 include ip.conf,需要动态就上 set_by_lua。最容易被忽略的两点是「deny all 的位置」和「有反向代理时真实 IP 没还原」,这两处错了,表现为「白名单全失效」或「白名单全命中」,非常好认。
qidao123.com企服评测-应用市场 编辑提示 |