Nginx sni proxy, get real IP and build

一、编译Nginx

  1. 获取Nginx源码
  2. 解压并安装
  3. 安装gcc等编译工具
  4. 编译安装并添加下面的模块
    • stream
    • stream_ssl_preread_module
    • stream_ssl_module
    • http_ssl_module
    • http_realip_module
    • stream_realip_module
1
2
3
./configure --prefix=/usr/local/nginx --sbin-path=/usr/local/sbin/nginx --conf-path=/usr/local/nginx/nginx.conf --with-stream --with-stream_ssl_preread_module --with-stream_ssl_module --with-http_ssl_module --with-http_realip_module --with-stream_realip_module
make
make install

二、修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#user  nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;

# 流量转发核心配置
stream {
# 这里就是 SNI 识别,将域名映射成一个配置名
map $ssl_preread_server_name $backend_name {
xxx.com web;
api.xxx.com api;
gateway.xxx.com tls;
# 域名都不匹配情况下的默认值
default web;
}

# web
upstream web {
server 127.0.0.1:8445;
}

# api
upstream api {
server 127.0.0.1:8444;
}

# tls
upstream tls {
server 127.0.0.1:8443;
}

# 监听 443 并开启 ssl_preread
# server 443 开启 proxy_protocol 后,分流后的所有服务也必须开启 proxy_protocol,否则会报错
server {
listen 443 reuseport;
proxy_pass $backend_name;
ssl_preread on;
proxy_protocol on;
}

# 对于不支持 proxy_protocol 的服务,需要关闭 proxy_protocol
server {
listen 8443 proxy_protocol so_keepalive=on;
proxy_protocol off; #把 proxy_protocol 关掉,因为不支持!这是关键
proxy_connect_timeout 300s;
proxy_timeout 300s;
proxy_pass 127.0.0.1:8446; #forward to tls server
}
}

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

log_format httplog '$remote_addr|$http_x_real_ip|$http_x_forwarded_for|$proxy_protocol_addr';
#access_log logs/access.log main;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

server {
listen 80 proxy_protocol;
server_name xxx.com;

# 强制跳转 https
location / {
return 301 https://$host$request_uri;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

server {
listen 127.0.0.1:8445 ssl proxy_protocol; # 这里需要开启 proxy_protocol
server_name xxx.com;

ssl_certificate /etc/nginx/xxx.com_cert.pem;
ssl_certificate_key /etc/nginx/xxx.com_key.pem;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

location / {
root /usr/share/nginx/html/;
index index.html;
error_page 404 /404.html;

# 用于去除 URL 末尾的html扩展名,访问不存在的页面则跳转404页面
if ($request_uri ~ ^/index(\?|$)) {
return 302 https://$host;
}
if ($request_uri ~ ^/index\.html(\?|$)) {
return 302 https://$host;
}
if ($request_uri ~ ^/(.*)\.html(\?|$)) {
return 302 https://$host/$1;
}
try_files $uri $uri.html $uri/ =404;
}

}

server {
listen 127.0.0.1:8444 ssl proxy_protocol; # 这里需要开启 proxy_protocol
set_real_ip_from 127.0.0.1;
real_ip_recursive on;
real_ip_header proxy_protocol;
server_name api.xxx.com;

ssl_certificate /etc/nginx/xxx.com_cert.pem;
ssl_certificate_key /etc/nginx/xxx.com_key.pem;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header X-Real-IP $remote_addr; #把处理后的$remote_addr赋值给http请求头X-Real-IP, 便于下一级服务获取真实ip
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
}
}

}

三、后台服务器获取IP地址

  1. 添加依赖到 pom.xml 中

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.7</version>
    </dependency>
  2. 获取IP地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @GetMapping("/ipCheck")
    public ResponseEntity<IPCheckResponse<String>> forwardRequest(HttpServletRequest request) {
    // 获取客户端IP地址
    String ip = request.getHeader("X-Real-IP");
    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    // 获取代理IP地址
    ip = request.getHeader("x-forwarded-for");
    }
    return ResponseEntity.ok(new IPCheckResponse<>("200", "Check Success", ip));
    }
  3. 测试

    1
    curl https://xxx.com/ipCheck
  4. 返回数据

    1
    2
    3
    4
    5
    {
    "code": "200",
    "message": "Check Success",
    "data": "{\n \"ip\": \"100.100.100.100\",\n \"hostname\": \"host-by.xxx.com\",\n \"city\": \"Los Angeles\",\n \"region\": \"California\",\n \"country\": \"US\",\n \"loc\": \"38.0559,-128.2666\",\n \"org\": \"AS888 XXX Cloud Services\",\n \"postal\": \"00000\",\n \"timezone\": \"America/Los_Angeles\"\n}"
    }