NGINX 屏蔽部分地区访问,但不完全屏蔽
NGINX 模块
首先我们需要确保你的 NGINX 安装了 GeoIP2 模块,如何安装不再描述。
如果你编译 GeoIP2 模块时出现了找不到 libmaxminddb 的错误,那么需要安装 libmaxminddb-devel 包(RHEL 系列)
安装 libmaxminddb 包后你也能在 shell 中查询,比如:
mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 8.8.8.8
GeoIP 库自动更新
我们一般都用 Maxmind 的免费 GeoIP 库 GeoLite2。
你可能想要让你的 IP 库自动更新,可以使用 Maxmind 自家的 geoipupdate。
官方文档:https://dev.maxmind.com/geoip/updating-databases
可以安装 Golang 包后直接执行
go install github.com/maxmind/geoipupdate/v6/cmd/geoipupdate@latest
cp /root/go/bin/geoipupdate /usr/local/bin
安装后注册 Maxmind.com 账户,申请免费密钥。
然后在登录状态下访问 https://www.maxmind.com/en/accounts/current/license-key/GeoIP.conf 下载预填充配置文件
将该配置文件移动到 /usr/local/etc/GeoIP.conf
然后把 YOUR_LICENSE_KEY_HERE
改为你申请的密钥。
配置完后只要执行 geoipupdate
,IP 数据库就会自动下载到 /usr/local/share/GeoIP
。
如果下载错误,可以执行 geoipupdate -v
查看详情。
然后就可以用 corntab 自动更新了,比如在 /etc/cron.d
下新建一个 geoipupdate 写入:
29 2 * * 1,3 /usr/local/bin/geoipupdate
即在每周一和每周四的晚上 9 点 50 分执行
NGINX 设置
GeoIP2 设置
http {
geoip2 /usr/local/share/GeoIP/GeoLite2-Country.mmdb {
auto_reload 1d;
#每隔一天自动重载一次 IP 数据库
$geoip2_data_country_iso_code country iso_code;
#从 GeoIP2 数据库中提取"country iso_code"字段的值,并将其存储在名为"geoip2_data_country_iso_code"的变量中。
}
}
默认情况下会使用 $remote_addr 中的地址来进行判定,如果你有 CDN 和直连 的话:
http {
map $http_x_forwarded_for $real_client_ip {
# 如果 http_x_forwarded_for 存在,则使用其第一个值(可能包含多个 IP 地址)然后赋值到 real_client_ip 变量。
"~^(?P<first_ip>[^,]+)" $first_ip;
# 否则,使用 Nginx 的 remote_addr 变量赋值到 real_client_ip 变量。
default $remote_addr;
}
geoip2 /usr/local/share/GeoIP/GeoLite2-Country.mmdb {
auto_reload 1d;
#每隔一天自动重载一次 IP 数据库
$geoip2_data_country_iso_code_xforward default=unknown source=$real_client_ip country iso_code;
#如果判断失败,则默认返回 default,来源 IP 设置为 $real_client_ip 变量
#然后从 GeoIP2 数据库中提取"country iso_code"字段的值,并将其存储在名为"geoip2_data_country_iso_code_xforward"的变量中。
}
}
国家判断设置
比如想屏蔽 KP(朝鲜) 和 PK(巴基斯坦)
http {
map "$geoip2_data_country_iso_code_xforward" $block_access {
"KP" deny; #如果 geoip2_data_country_iso_code_xforward 的值是 KP(朝鲜) 则给 block_access 变量赋值为 deny
"PK" deny;#如果 geoip2_data_country_iso_code_xforward 的值是 PK(巴基斯坦) 则给 block_access 变量赋值为 deny
default allow; #默认给 block_access 变量赋值为 allow
}
}
location 设置
如果你之前是直接用 / 匹配所有的,那么直接在原来的 location 前面加上判断语句即可。
server {
location / {
if ($block_access = deny) {
#如果 block_access 变量是 deny 则返回 451
return 451;
}
#原来的 location 内容
}
}
不完全屏蔽
http {
map $http_x_forwarded_for $real_client_ip {
# 如果 http_x_forwarded_for 存在,则使用其第一个值(可能包含多个 IP 地址)然后赋值到 real_client_ip 变量。
"~^(?P<first_ip>[^,]+)" $first_ip;
# 否则,使用 Nginx 的 remote_addr 变量赋值到 real_client_ip 变量。
default $remote_addr;
}
geoip2 /usr/local/share/GeoIP/GeoLite2-Country.mmdb {
auto_reload 1d;
#每隔一天自动重载一次 IP 数据库
$geoip2_data_country_iso_code_xforward default=unknown source=$real_client_ip country iso_code;
#如果判断失败,则默认返回 default,来源 IP 设置为 $real_client_ip 变量
#然后从 GeoIP2 数据库中提取"country iso_code"字段的值,并将其存储在名为"geoip2_data_country_iso_code_xforward"的变量中。
}
#NGINX 不支持 2 个 IF,使用 map 实现
map $http_cookie $allow_cookie {
#如果 cookie 里面有 access_granted=true 那么就把 allow_cookie 变量设置为 true
default false;
"~*access_granted=true" true;
}
map "$geoip2_data_country_iso_code_xforward" $block_coutry {
"KP" deny; #如果 geoip2_data_country_iso_code_xforward 的值是 KP(朝鲜) 则给 block_access 变量赋值为 deny
"PK" deny; #如果 geoip2_data_country_iso_code_xforward 的值是 PK(巴基斯坦) 则给 block_access 变量赋值为 deny
default allow; #默认给 block_access 变量赋值为 allow
}
map "$block_coutry:$allow_cookie" $block_access {
"deny:false" deny; #如果 IP 是屏蔽国家的而且没有 access_granted=true 的 cookie,那么就拒绝访问
"deny:true" allow; #如果 IP 是屏蔽国家但有 access_granted=true 的 cookie,那么允许访问
default allow;
}
}
location 部分
额外加一个,返回 451 时转跳到 blocked.html。
server {
location / {
if ($block_access = deny) {
#如果 block_access 变量是 deny 则返回 451
return 451;
}
#原来的 location 内容
}
error_page 451 /blocked.html;
location = /blocked.html {
internal;
}
blocked.html 设置 Cookie 示例,没设置自动刷新,手动刷新即可到正常页面。
<html>
<head>
<meta charset="UTF-8" />
<title>451</title>
</head>
<body>
<div class="access_button" onclick="grantAccess()">
<a href="#">获取访问权限</a>
</div>
<script>
function grantAccess() {
document.cookie = "access_granted=true; path=/";
}
</script>
</body>
</html>
无评论