通过 nginx 限制服务的可访问地区,主要利用 GeoIP 数据库识别客户 IP 的归属地,进而对不通归属地的访问请求分流处理。

以限制中国地区访问为例。为了实现目标,我们需要以下“原料”:

  1. GeoLite2-Country 和 GeoLite2-City(可选)数据库
    • GeoLite2-Country 数据库包含了全球范围内的IPv4和IPv6地址及其对应国家的地理位置信息,每个条目至少包含国家码(ISO 3166-1 alpha-2标准)。
    • GeoLite2-City 数据库则更加详细,除了国家信息外,还提供了城市名称、行政区划、经纬度、时区、ISP信息等更细致的地理位置数据。
  2. libmaxminddb-devel 一个开发库及相关的头文件包,它提供了对 MaxMind GeoIP2 数据库格式的读取支持。这个库允许开发者编写程序来解析 MaxMind 提供的 GeoIP2 数据库,从而获取地理位置信息,包括但不限于国家、城市、经纬度、时区等与IP地址关联的数据。
  3. ngx_http_geoip2_module Nginx 的一个第三方模块,与 libmaxminddb 库配合使用,实现在 Nginx 服务器层面根据客户端 IP 地址获取其地理位置信息的功能。

服务处理流程:

客户端请求 -> 路由 -> nginx (ngx_http_geoip2_module 调用 libmaxminddb-devel 功能实现访问本地 Geo 数据库,得到用户 IP 所属的国家码等信息) -> 服务/拦截

一、GeoIP

1.1 安装 libmaxminddb-devel

yum install libmaxminddb-devel -y

1.2 下载 geoip2 模块

本文解压至:/usr/local/ngx_http_geoip2_module/

1.3 下载 GeoLite2 数据库

本文解压至:/usr/share/GeoIP/

二、nginx

2.1 下载 nginx 源码

上传到服务器上解压,进入源码根目录。将 auto 目录下的 configure cp 一份到源码根目录。

2.2 编译 nginx

注意 --conf-path 等路径配置。此处假设服务器已经通过 yum install nginx 之类的命令安装 nginx,相应的配置文件位一般默认位于 /etc/nginx/nginx.conf。要确定原本的 nginx 的配置信息,请运行 nginx -V。若你原本就是自己编译的 nginx,想必我就不用多说什么了。

[root@localhost nginx-release-1.20.1]# ./configure \
    --prefix=/usr/share/nginx \
    --sbin-path=/usr/sbin/nginx \
    --modules-path=/usr/lib64/nginx/modules \
    --conf-path=/etc/nginx/nginx.conf \
    --pid-path=/var/run/nginx/nginx.pid \
    --lock-path=/var/lock/nginx.lock \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --with-http_gzip_static_module \
    --http-client-body-temp-path=/var/lib/nginx/tmp/client_body \
    --http-proxy-temp-path=/var/lib/nginx/tmp/proxy \
    --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi \
    --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi \
    --http-scgi-temp-path=/var/lib/nginx/tmp/scgi \
    --with-http_ssl_module \
    --with-stream \
    --with-http_stub_status_module \
    --add-module=/usr/local/ngx_http_geoip2_module \

其中关键是:

    --add-module=/usr/local/ngx_http_geoip2_module

而后编译,make 即可:

[root@localhost nginx-release-1.20.1]# make

2.3 替换 nginx

首先备份原程序:

cp /usr/sbin/nginx /usr/sbin/nginx-bkp

结束原进程:

pkill nginx

替换程序为新编译的程序(y回车确认):

cp objs/nginx /usr/sbin/

启动 nginx:

systemctl start nginx

2.4 配置 nginx

配置效果为,中国大陆地区访问,响应404。注意替换 GeoLite2 数据库的路径为你实际的路径。

这里 geoip2 我指定了 source 为 $http_x_real_ip country,因为 geoip2 默认使用 $remote_ip 进行查询,而我的服务经过了(很多重)反向代理,默认的 remote_ip 只能记录到 127.0.0.1 或是到达我的首个服务器前的最上一级路由 IP。

更多示例用法请参考:https://github.com/leev/ngx_http_geoip2_module?tab=readme-ov-file#example-usage

http {
    ...
    # GeoIP
    geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        $geoip2_data_country_code source=$http_x_real_ip country iso_code;
        $geoip2_data_country_name source=$http_x_real_ip country names en;
    }
    map $geoip2_data_country_code $is_CN {
        default no;
        CN yes;
    }
    ...
    server {
       ...
        if ($is_CN = yes) {
        	return 404;
        }
       ...
    }
    ...
}

三、更进一步

直接 404 有点不太友好了。

为 nginx 的 configure 加上以下参数使得 nginx 支持 sub_filter / subs_filter(可选)功能,以方便地使用静态页面展示用户所在地区和站点提示。

Github Release - ngx_http_substitutions_filter_module-0.6.4

    --with-http_sub_module \
    --add-module=/usr/local/ngx_http_substitutions_filter_module-0.6.4

在 geoip2 块中增加地区名映射:

         $geoip2_data_country_name source=$http_x_real_ip country names en;

修改 server 块内容

    server {
        ...
        sub_filter '##COUNTRY##' '$geoip2_data_country_name';
        if ($is_CN = yes) {
        	rewrite ^ /restricted.html break;
        }
        ...
    }

小猴寄 restricted.html 页面供参考:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>区域受限提示</title>
    <style>
        body {
            background-color: #212529;
            color: #fff;
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: flex-start;
            min-height: 100vh;
            padding-top: 20vh;
        }
        
        .container {
            text-align: center;
            padding: 40px;
            max-width: 600px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            border-radius: 10px;
            background-color: #343a40;
        }
        
        h1 {
            font-size: 2em;
            margin-bottom: 20px;
        }

        p {
            font-size: 1.2em;
            line-height: 1.6;
        }

        @media (max-width: 767px) {
            .container {
                padding: 20px;
            }
            
            h1 {
                font-size: 1.5em;
            }
            
            p {
                font-size: 1em;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>区域受限提示</h1>
        <p>受法律法规限制,本站内容暂不对您所在地区(##COUNTRY##)开放。</p>
        <p>由此带来的不便,敬请谅解!</p>
        <p style="text-align: right;">——小猴寄</p>
    </div>
</body>
</html>

重载配置

nginx -s reload

四、效果

restrict-visiting-by-nginx-1.png