在企业实际运用中,真实业务服务器往往都不是直接对外提供服务的,前面大多都会添加反代服务器以及防火墙,

但这样以来核心的业务服务器如何获取到用户的真实IP往往是运维考虑的一个核心问题,今天我给大家分享一下

企业真实企业真实环境下核心web服务器如何获取用户的真实IP。由于线上大多使用硬件防火墙,我们这里

使用CentOS自带的iptables来进行模拟,也方便大家阅读本文进行实践。


环境展示:

虚拟化平台: Vmware Workstation

操作系统: CentOS 7.5

防火墙: CentOS系统的iptables做DNAT+SNAT

反代服务器: nginx的七层反代模块

web服务器: nginx或者httpd


此次的模拟过程我们用四台虚拟机来实现:

webserver  一台单独的虚机  192.168.10.254

proxyserver  一台单独的虚机  192.168.10.100

proxyserver2  一台单独的虚机  192.168.10.50

firewall  一台单独的虚机  内网IP:192.168.10.8 外网IP:10.0.0.10


四台服务器同时禁用和停止SELinux和默认的firewalld服务,实际企业环境中,防火墙以内的服务器也很少会启用此两项。


webserver安装nginx和php-fpm

proxyserver安装nginx

firewall默认就可以使用iptables,如果要想使用更完备的iptables功能,可以安装iptables-services


webserver的nginx主配置文件

grep -v '[[:space:]]*#' /etc/nginx/nginx.conf

user  nginx;

worker_processes  auto;


error_log  /data/log/nginx/error.log warn;

pid        /var/run/nginx.pid;


events {

    worker_connections  1024;

}


http {

    include       /etc/nginx/mime.types;

    default_type  application/octet-stream;


    log_format  main  '$remote_addr "$http_x_forwarded_for" "$proxy_add_x_forwarded_for" '

                      '$remote_user [$time_local] "$request" $status $body_bytes_sent '

                      '$upstream_addr $request_time $upstream_response_time '

                      '"$http_referer" "$http_user_agent" ';


    access_log  /data/log/nginx/access.log  main;


    sendfile        on;

    keepalive_timeout  65;


    include /etc/nginx/conf.d/*.conf;

}


webserver的nginx虚拟主机配置文件

grep -v '[[:space:]]*#' /etc/nginx/conf.d/default.conf

server {

    listen       80;

    server_name  www.test.com;


    root   /data/website;

    index  index.php index.html index.htm;

   

    access_log  /data/log/nginx/test.access.log  main;

    error_log  /data/log/nginx/test.error.log  warn;


    location ~ \.php$ {

        fastcgi_pass   127.0.0.1:9000;

        fastcgi_index  index.php;

        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;

        include        fastcgi_params;

    }

}


测试页面php代码如下:

/data/website/index.php 

<?php


  foreach ($_SERVER as $key=>$value) {

    echo $key.'='.$value.'<br />';

  }


?>


使用宿主机的浏览器访问核心业务服务器192.168.10.254,页面输出如下:

USER=apache

HOME=/usr/share/httpd

FCGI_ROLE=RESPONDER

SCRIPT_FILENAME=/data/website/index.php

QUERY_STRING=

REQUEST_METHOD=GET

CONTENT_TYPE=

CONTENT_LENGTH=

SCRIPT_NAME=/index.php

REQUEST_URI=/

DOCUMENT_URI=/index.php

DOCUMENT_ROOT=/data/website

SERVER_PROTOCOL=HTTP/1.1

REQUEST_SCHEME=http

GATEWAY_INTERFACE=CGI/1.1

SERVER_SOFTWARE=nginx/1.14.0

REMOTE_ADDR=192.168.10.1

REMOTE_PORT=57829

SERVER_ADDR=192.168.10.254

SERVER_PORT=80

SERVER_NAME=www.test.com

REDIRECT_STATUS=200

HTTP_HOST=192.168.10.254

HTTP_CONNECTION=keep-alive

HTTP_CACHE_CONTROL=max-age=0

HTTP_UPGRADE_INSECURE_REQUESTS=1

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

HTTP_ACCEPT_ENCODING=gzip, deflate

HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9

PHP_SELF=/index.php

REQUEST_TIME_FLOAT=1539053341.08

REQUEST_TIME=1539053341


webserver的nginx日志内容如下

cat /data/log/nginx/test.access.log

192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:10:54:34 +0800] "GET / HTTP/1.1" 200 1146 127.0.0.1:9000 0.001 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


由于宿主机的ip都是本网段的第一个ip,所以我们ip是192.168.10.1是真实的客户端ip。


为了方便查看HTTP_X_FORWARDED_FOR的变化,我们直接使用fastcgi_param参数向应用服务器传递一个HTTP_X_FORWARDED_FOR变量

    location ~ \.php$ {

        #root   /data/website;

        fastcgi_pass   127.0.0.1:9000;

        fastcgi_index  index.php;

        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;

        fastcgi_param  HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;  #添加了此行

        include        fastcgi_params;

    }


此时再次刷新访问页面192.168.10.254,输出内容如下:

USER=apache

HOME=/usr/share/httpd

FCGI_ROLE=RESPONDER

SCRIPT_FILENAME=/data/website/index.php

HTTP_X_FORWARDED_FOR=192.168.10.1

QUERY_STRING=

REQUEST_METHOD=GET

CONTENT_TYPE=

CONTENT_LENGTH=

SCRIPT_NAME=/index.php

REQUEST_URI=/

DOCUMENT_URI=/index.php

DOCUMENT_ROOT=/data/website

SERVER_PROTOCOL=HTTP/1.1

REQUEST_SCHEME=http

GATEWAY_INTERFACE=CGI/1.1

SERVER_SOFTWARE=nginx/1.14.0

REMOTE_ADDR=192.168.10.1

REMOTE_PORT=59095

SERVER_ADDR=192.168.10.254

SERVER_PORT=80

SERVER_NAME=www.test.com

REDIRECT_STATUS=200

HTTP_HOST=192.168.10.254

HTTP_CONNECTION=keep-alive

HTTP_CACHE_CONTROL=max-age=0

HTTP_UPGRADE_INSECURE_REQUESTS=1

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

HTTP_ACCEPT_ENCODING=gzip, deflate

HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9

PHP_SELF=/index.php

REQUEST_TIME_FLOAT=1539054347.9258

REQUEST_TIME=1539054347


HTTP_X_FORWARDED_FOR=192.168.10.1,可以明确的看到fastcgi向后端应用服务器传递的HTTP_X_FORWARDED_FOR的值


webserver的nginx日志内容如下

192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:11:05:47 +0800] "GET / HTTP/1.1" 200 1185 127.0.0.1:9000 0.001 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


接下来我们给webserver前端添加第一层反向代理


proxyserver的nginx主配如下:

user  nginx;

worker_processes  auto;


error_log  /data/log/nginx/error.log warn;

pid        /var/run/nginx.pid;


events {

    worker_connections  1024;

}


http {

    include       /etc/nginx/mime.types;

    default_type  application/octet-stream;


    log_format  main  '$remote_addr "$http_x_forwarded_for" "$proxy_add_x_forwarded_for" '

                      '$remote_user [$time_local] "$request" $status $body_bytes_sent '

                      '$upstream_addr $request_time $upstream_response_time '

                      '"$http_referer" "$http_user_agent" ';


    access_log  /data/log/nginx/access.log  main;


    sendfile        on;


    keepalive_timeout  65;


    include /etc/nginx/conf.d/*.conf;

}


proxyserver的nginx小配如下:

upstream backend_server {

    server 192.168.10.254;

}


server {

    listen       80;

    server_name  proxy.test.com;


    access_log  /data/log/nginx/proxy.access.log  main;

    error_log  /data/log/nginx/proxy.error.log  warn;


    location / {

        proxy_pass http://backend_server;

    }

}


此时使用宿主机的浏览器访问proxyserver的ip 192.168.10.100,输出内容如下:


USER=apache

HOME=/usr/share/httpd

FCGI_ROLE=RESPONDER

SCRIPT_FILENAME=/data/website/index.php

HTTP_X_FORWARDED_FOR=192.168.10.100

QUERY_STRING=

REQUEST_METHOD=GET

CONTENT_TYPE=

CONTENT_LENGTH=

SCRIPT_NAME=/index.php

REQUEST_URI=/

DOCUMENT_URI=/index.php

DOCUMENT_ROOT=/data/website

SERVER_PROTOCOL=HTTP/1.0

REQUEST_SCHEME=http

GATEWAY_INTERFACE=CGI/1.1

SERVER_SOFTWARE=nginx/1.14.0

REMOTE_ADDR=192.168.10.100

REMOTE_PORT=44866

SERVER_ADDR=192.168.10.254

SERVER_PORT=80

SERVER_NAME=www.test.com

REDIRECT_STATUS=200

HTTP_HOST=backend_server

HTTP_CONNECTION=close

HTTP_UPGRADE_INSECURE_REQUESTS=1

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

HTTP_ACCEPT_ENCODING=gzip, deflate

HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9

PHP_SELF=/index.php

REQUEST_TIME_FLOAT=1539055499.4538

REQUEST_TIME=1539055499


此时可以看到proxyserver响应的页面中HTTP_X_FORWARDED_FOR=192.168.10.100,由原来记录的用户的真实IP变成了反代proxyserver自己的IP。REMOTE_ADDR=192.168.10.100由原来需要访问核心业务服务器变成了访问proxyserver的IP,从而起到了隐藏核心业务服务器的作用。


proxyserver的nginx日志输出如下:

192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:11:25:03 +0800] "GET / HTTP/1.1" 200 1150 192.168.10.254:80 0.002 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


webserver的nginx日志输出如下:

192.168.10.100 "-" "192.168.10.100" - [09/Oct/2018:11:24:59 +0800] "GET / HTTP/1.0" 200 1138 127.0.0.1:9000 0.001 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


在这里能够很清晰的看到前端proxyserver能够拿到用户的真实访问IP,但核心业务服务器webserver看到所有的访问是来自前端反代服务器的,

这样以来,用户的真实IP就获取不到了。这是无法企业的需求的,那么接下就要给大家分享在前端有反向代理服务器时如何获取用户的真实IP。


修改proxyserver的nginx小配文件

    location / {

        proxy_set_header   Host             $host;         #增加内容

        proxy_set_header   X-Real-IP        $remote_addr;  #增加内容

        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;  #增加内容

        proxy_pass http://backend_server;

    }


reload nginx后重新访问192.168.10.100,页面输出内容如下:

USER=apache

HOME=/usr/share/httpd

FCGI_ROLE=RESPONDER

SCRIPT_FILENAME=/data/website/index.php

HTTP_X_FORWARDED_FOR=192.168.10.1, 192.168.10.100

QUERY_STRING=

REQUEST_METHOD=GET

CONTENT_TYPE=

CONTENT_LENGTH=

SCRIPT_NAME=/index.php

REQUEST_URI=/

DOCUMENT_URI=/index.php

DOCUMENT_ROOT=/data/website

SERVER_PROTOCOL=HTTP/1.0

REQUEST_SCHEME=http

GATEWAY_INTERFACE=CGI/1.1

SERVER_SOFTWARE=nginx/1.14.0

REMOTE_ADDR=192.168.10.100

REMOTE_PORT=44870

SERVER_ADDR=192.168.10.254

SERVER_PORT=80

SERVER_NAME=www.test.com

REDIRECT_STATUS=200

HTTP_HOST=192.168.10.100

HTTP_X_REAL_IP=192.168.10.1

HTTP_CONNECTION=close

HTTP_CACHE_CONTROL=max-age=0

HTTP_UPGRADE_INSECURE_REQUESTS=1

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

HTTP_ACCEPT_ENCODING=gzip, deflate

HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9

PHP_SELF=/index.php

REQUEST_TIME_FLOAT=1539057291.9947

REQUEST_TIME=1539057291


HTTP_X_FORWARDED_FOR=192.168.10.1, 192.168.10.100,可以看到HTTP_X_FORWARDED_FOR变量中保存了所有经过的服务器,这里我们只经过了一层反向服务器,如果有多层的话,会依次追加至真实客户端IP的后面。


此时我们再来查看一下proxyserver的nginx访问日志

192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:11:54:55 +0800] "GET / HTTP/1.1" 200 1231 192.168.10.254:80 0.001 0.003 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


可以看出和之前一样


webserver的nginx访问日志

192.168.10.100 "192.168.10.1" "192.168.10.1, 192.168.10.100" - [09/Oct/2018:11:54:51 +0800] "GET / HTTP/1.0" 200 1219 127.0.0.1:9000 0.000 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


但核心业务服务器日志的HTTP_X_FORWARDED_FOR字段的值就是用户端的真实IP "192.168.10.1"。但这只是针对单层反向代码的情况,如果核心业务服务器前端有多层反向代理时,HTTP_X_FORWARDED_FOR字段可能不上一个IP了,是会同时携带最接近用户真实IP层面的几层IP信息。


为了能够让大家看到效果,我们继续给proxyserver 192.168.10.100的前端再添加一层反代服务器,使用192.168.10.50这台服务器也安装nginx,并配置成为proxyserver2,做为192.168.10.100的前端反向代理。


proxyserver2的nginx主配置文件

user  nginx;

worker_processes  auto;


error_log  /data/log/nginx/error.log warn;

pid        /var/run/nginx.pid;


events {

    worker_connections  1024;

}


http {

    include       /etc/nginx/mime.types;

    default_type  application/octet-stream;


    log_format  main  '$remote_addr "$http_x_forwarded_for" "$proxy_add_x_forwarded_for" '

                      '$remote_user [$time_local] "$request" $status $body_bytes_sent '

                      '$upstream_addr $request_time $upstream_response_time '

                      '"$http_referer" "$http_user_agent" ';


    access_log  /data/log/nginx/access.log  main;


    sendfile        on;


    keepalive_timeout  65;


    include /etc/nginx/conf.d/*.conf;

}


proxyserver2的nginx小配文件

upstream backend_server {

    server 192.168.10.100;

}


server {

    listen       80;

    server_name  proxy.test.com;


    access_log  /data/log/nginx/proxy.access.log  main;

    error_log  /data/log/nginx/proxy.error.log  warn;


    location / {

        proxy_set_header   Host             $host;

        proxy_set_header   X-Real-IP        $remote_addr;

        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

        proxy_pass http://backend_server;

    }

}


此时访问最前端反代服务器proxyserver2的ip,192.168.10.50,输出内容如下:

USER=apache

HOME=/usr/share/httpd

FCGI_ROLE=RESPONDER

SCRIPT_FILENAME=/data/website/index.php

HTTP_X_FORWARDED_FOR=192.168.10.1, 192.168.10.50, 192.168.10.100

QUERY_STRING=

REQUEST_METHOD=GET

CONTENT_TYPE=

CONTENT_LENGTH=

SCRIPT_NAME=/index.php

REQUEST_URI=/

DOCUMENT_URI=/index.php

DOCUMENT_ROOT=/data/website

SERVER_PROTOCOL=HTTP/1.0

REQUEST_SCHEME=http

GATEWAY_INTERFACE=CGI/1.1

SERVER_SOFTWARE=nginx/1.14.0

REMOTE_ADDR=192.168.10.100

REMOTE_PORT=44880

SERVER_ADDR=192.168.10.254

SERVER_PORT=80

SERVER_NAME=www.test.com

REDIRECT_STATUS=200

HTTP_HOST=192.168.10.50

HTTP_X_REAL_IP=192.168.10.50

HTTP_CONNECTION=close

HTTP_CACHE_CONTROL=max-age=0

HTTP_UPGRADE_INSECURE_REQUESTS=1

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

HTTP_ACCEPT_ENCODING=gzip, deflate

HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9

PHP_SELF=/index.php

REQUEST_TIME_FLOAT=1539059555.4869

REQUEST_TIME=1539059555


proxyserver2服务器nginx的访问日志

192.168.10.1 "-" "192.168.10.1" - [09/Oct/2018:12:32:39 +0800] "GET / HTTP/1.1" 200 1246 192.168.10.100:80 0.003 0.004 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


由于这一层在最前端,获取到是用户的真实IP。


proxyserver的nginx访问日志

192.168.10.50 "192.168.10.1" "192.168.10.1, 192.168.10.50" - [09/Oct/2018:12:32:39 +0800] "GET / HTTP/1.0" 200 1234 192.168.10.254:80 0.001 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36" 


能能够看到用户的真实ip是被传递给了HTTP_X_FORWARDED_FOR变量的,但也是源于proxyserver2的反向代理层面添加了如下三行配置:

        proxy_set_header   Host             $host;

        proxy_set_header   X-Real-IP        $remote_addr;

        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

否则proxyserver层是获取不到用户端的真实IP的


再看一下核心层webserver服务器nginx访问日志


192.168.10.100 "192.168.10.1, 192.168.10.50" "192.168.10.1, 192.168.10.50, 192.168.10.100" - [09/Oct/2018:12:32:35 +0800] "GET / HTTP/1.0" 200 1234 127.0.0.1:9000 0.001 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


此时可以看到核心业务服务器HTTP_X_FORWARDED_FOR变量的值就不止一个IP了,包含了用户的真实IP和离用户最近一层的反向代理服务器的IP。

在这种多层反向代理的环境,如果要想从HTTP_X_FORWARDED_FOR变量中取到用户真实IP,就得写代码从HTTP_X_FORWARDED_FOR变量中取第一个,号前的内容了。如果想让REMOTE_ADDR字段直接保存用户的真实IP,还需要进一步的配置。


在这里通过笔者的实践得出了两种配置思路:

第一种,只在核心业务器webserver上配置(前端反代不用关心)

需要在server段中添加如下配置

    set_real_ip_from 192.168.10.50;

    set_real_ip_from 192.168.10.100;

    real_ip_header  X-Forwarded-For;

    real_ip_recursive  on;

也即把所有外层反代服务器的ip地址全部使用set_real_ip_from罗列出来,并指定用户真实IP从X-Forwarded-For字段中取值,

并且启用真实IP递归解析(也即从X-Forwarded-For字段的最右侧开始,排除方式取出第一个,号之前IP)


第二种,每层反代理都配置set_real_ip_from 上层反代IP;(上文中的其配置都可省略)

此种思路配置较为复杂,但每层反代都可以使用REMOTE_ADDR字段保存用户的真实IP,

如果反代服务器不是自己的资源时具有较多的不可操作性。


首先,我们看一下第一种方案配置后的日志记录


访问最外层反代proxyserver2的ip,页面输出如下

USER=apache

HOME=/usr/share/httpd

FCGI_ROLE=RESPONDER

SCRIPT_FILENAME=/data/website/index.php

HTTP_X_FORWARDED_FOR=192.168.10.1, 192.168.10.50, 192.168.10.1

QUERY_STRING=

REQUEST_METHOD=GET

CONTENT_TYPE=

CONTENT_LENGTH=

SCRIPT_NAME=/index.php

REQUEST_URI=/

DOCUMENT_URI=/index.php

DOCUMENT_ROOT=/data/website

SERVER_PROTOCOL=HTTP/1.0

REQUEST_SCHEME=http

GATEWAY_INTERFACE=CGI/1.1

SERVER_SOFTWARE=nginx/1.14.0

REMOTE_ADDR=192.168.10.1

REMOTE_PORT=

SERVER_ADDR=192.168.10.254

SERVER_PORT=80

SERVER_NAME=www.test.com

REDIRECT_STATUS=200

HTTP_HOST=192.168.10.50

HTTP_X_REAL_IP=192.168.10.50

HTTP_CONNECTION=close

HTTP_CACHE_CONTROL=max-age=0

HTTP_UPGRADE_INSECURE_REQUESTS=1

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

HTTP_ACCEPT_ENCODING=gzip, deflate

HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9

PHP_SELF=/index.php

REQUEST_TIME_FLOAT=1539090819.6079

REQUEST_TIME=1539090819


proxyserver2的nginx日志如下:

192.168.10.1 "-" "192.168.10.1" - [10/Oct/2018:19:36:53 +0800] "GET / HTTP/1.1" 200 1237 192.168.10.100:80 0.307 0.308 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36" 


最外层记录了用户的真实访问IP,并向PROXY_ADD_X_FORWARDED_FOR变量中插入用户的真实IP


proxyserver的nginx日志如下:

192.168.10.50 "192.168.10.1" "192.168.10.1, 192.168.10.50" - [09/Oct/2018:21:13:07 +0800] "GET / HTTP/1.0" 200 1225 192.168.10.254:80 0.307 0.307 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


中间层没有使用set_real_ip_from指令,所以REMOTE_ADDR字段还是存放的上层反代服务器的访问IP,但HTTP_X_FORWARDED_FOR变量的第一段已经有了用户的真实访问IP,如果这里想获取用户的真实访问IP就得通过代码来实现。


webserver的nginx日志如下:

192.168.10.1 "192.168.10.1, 192.168.10.50" "192.168.10.1, 192.168.10.50, 192.168.10.1" - [09/Oct/2018:21:13:39 +0800] "GET / HTTP/1.0" 200 1225 127.0.0.1:9000 0.306 0.306 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


核心层的nginx配置中添加过了REAL_IP的相关配置,所以REMOTE_ADDR字段直接保存了用户的真实访问IP,X-Forwarded-For字段保存了用户的真实IP和离用户最近的上层代理,proxy_add_x_forwarded_for变量中的最后一段也是用户的真实IP,这个有待于研究。


至此,我们核心业务服务器在多层反向代理的环境下,已经可以获取用户的真实访问IP了。如果想模拟在最外层有防火墙的情况,请继续往下。


此时我们启用firewall这台虚机,需要另外添加一张外网网卡,内网使用eth0,配置IP:192.168.10.8,外网使用eth1,配置IP:10.0.0.10,

如果要想做为网络防火墙使用,必须打开内核转发参数,也即net.ipv4.ip_forward = 1


添加相应外网到内网的映射规则,也即DNAT规则

iptables -t nat -I PREROUTING -p tcp -d 10.0.0.10 --dport 80 -j DNAT --to-destination 192.168.10.50:80

此条规则是将所有访问防火墙的外网接口10.0.0.10的80端口的数据修改目标地址及端口后转发到内网服务器192.168.10.50的80端口,

而我们内网服务器192.168.10.50也不是真实web服务器,而是多层反向代理的最外层反代服务器。所以这整个链路的层次还是比较多的。


仅有DNAT规则,只能将外网用户的请求转发到内网的反代服务器,所以还需要SNAT规则将反代服务器的响应数据修改源地及端口后转发给用户

iptables -t nat -I POSTROUTING -p tcp -s 192.168.10.50 --sport 80 -j SNAT --to-source 10.0.0.10:80


跟着做的朋友可能会发现现在DNAT和SNAT都做好了,但为什么还是访问10.0.0.10还是不正常了,原因在于内网的反代服务器响应数据时找不到通向10.0网络的路由,所以需要在内网服务器上配置防火墙192.168.10.8为自己的网关。


需要修改proxyserver2的网络配置

GATEWAY=192.168.10.8


此时使用宿主机的浏览器访问10.0.0.10,页面输出内容如下:

USER=apache

HOME=/usr/share/httpd

FCGI_ROLE=RESPONDER

SCRIPT_FILENAME=/data/website/index.php

HTTP_X_FORWARDED_FOR=10.0.0.1, 192.168.10.50, 10.0.0.1

QUERY_STRING=

REQUEST_METHOD=GET

CONTENT_TYPE=

CONTENT_LENGTH=

SCRIPT_NAME=/index.php

REQUEST_URI=/

DOCUMENT_URI=/index.php

DOCUMENT_ROOT=/data/website

SERVER_PROTOCOL=HTTP/1.0

REQUEST_SCHEME=http

GATEWAY_INTERFACE=CGI/1.1

SERVER_SOFTWARE=nginx/1.14.0

REMOTE_ADDR=10.0.0.1

REMOTE_PORT=

SERVER_ADDR=192.168.10.254

SERVER_PORT=80

SERVER_NAME=www.test.com

REDIRECT_STATUS=200

HTTP_HOST=10.0.0.10

HTTP_X_REAL_IP=192.168.10.50

HTTP_CONNECTION=close

HTTP_CACHE_CONTROL=max-age=0

HTTP_UPGRADE_INSECURE_REQUESTS=1

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36

HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

HTTP_ACCEPT_ENCODING=gzip, deflate

HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.9

PHP_SELF=/index.php

REQUEST_TIME_FLOAT=1539111297.9179

REQUEST_TIME=1539111297


proxyserver2的nginx日志如下:

10.0.0.1 "-" "10.0.0.1" - [11/Oct/2018:01:18:11 +0800] "GET / HTTP/1.1" 200 1221 192.168.10.100:80 0.004 0.005 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36


proxyserver的nginx日志如下:

192.168.10.50 "10.0.0.1" "10.0.0.1, 192.168.10.50" - [10/Oct/2018:02:54:26 +0800] "GET / HTTP/1.0" 200 1209 192.168.10.254:80 0.002 0.003 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


webserver的nginx日志如下:

10.0.0.1 "10.0.0.1, 192.168.10.50" "10.0.0.1, 192.168.10.50, 10.0.0.1" - [10/Oct/2018:02:54:57 +0800] "GET / HTTP/1.0" 200 1209 127.0.0.1:9000 0.001 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


接下来我们再为大家展示一下层层代理都配置后得到的日志效果


proxyserver2是最外层的反向代理,所以不需要使用set_real_ip指定上级反代的真实IP

proxyserver2的nginx日志如下:

10.0.0.1 "-" "10.0.0.1" - [11/Oct/2018:02:05:00 +0800] "GET / HTTP/1.1" 200 1211 192.168.10.100:80 0.004 0.005 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


proxyserver是中间反代服务器,需要使用set_real_ip指定上级反代的真实IP

需要在nginx小配文件中增加如下配置

set_real_ip_from 192.168.10.50;


proxyserver的nginx日志如下:

10.0.0.1 "10.0.0.1" "10.0.0.1, 10.0.0.1" - [10/Oct/2018:03:41:14 +0800] "GET / HTTP/1.0" 200 1199 192.168.10.254:80 0.002 0.001 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


webserver的nginx也需要使用set_real_ip指定上级反代的真实IP

set_real_ip_from 192.168.10.100;


webserver的nginx日志如下:

10.0.0.1 "10.0.0.1, 10.0.0.1" "10.0.0.1, 10.0.0.1, 10.0.0.1" - [10/Oct/2018:03:41:46 +0800] "GET / HTTP/1.0" 200 1199 127.0.0.1:9000 0.001 0.000 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36"


从核心业务服务器日志的proxy_add_x_forwarded_for的变量值来看,每经过一次处理,都会把用户的真实IP解析出来并向

proxy_add_x_forwarded_for值。


至此,我们的环境是最前端防火墙,防火墙过后穿越两层反向代理,用户请求才到达核心业务服务器,即便是如此复杂的网络环境,依然能够保证我们的核心业务服务器能够获取到用户的真实IP。