nginx · 2024-01-22 0

nginx 的 $remote_addr、$proxy_add_x_forwarded_for、$host 使用,获得真实用户 ip

一、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://localhost:8080/;
}

1.$remote_addr

proxy_set_header X-Real-IP $remote_addr;

$remote_addr 的值相当于 web 端使用 request.getRemoteAddr()

2.$proxy_add_x_forwarded_for

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

意思是增加一个 $proxy_add_x_forwarded_for 到 X-Forwarded-For 里去,注意是增加,而不是覆盖,当搭建两台 nginx 在不同的 ip 上,并且都使用了这段配置,那你会发现在 web 服务器端通过 request.getAttribute("X-Forwarded-For") 获得的将会是客户端 ip 和第一台 nginx 的 ip

$proxy_add_x_forwarded_for 变量包含客户端请求头中的 X-Forwarded-For 与 $remote_addr 两部分,他们之间用逗号分开

3.$host

proxy_set_header Host $host;

请求行中的 Host,如果有 Host 请求头,则用其值替换掉请求行中的主机名,如果请求中没有 Host 行和 Host 请求头,则等于请求匹配的 server 名称(处理请求 server 的 server_name 指令的值),值为小写,不包含端口

二、$remote_addr 和 $proxy_add_x_forwarded_for 示例

1.请求链路

有请求链路如下:

client (172.17.0.6) -> nginx_3 (172.17.0.5) -> nginx_2 (172.17.0.4) -> nginx_1 (172.17.0.3) -> server (172.17.0.2)

2.java 代码

@RestController
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "this is index";
    }

    @GetMapping("/test")
    public String test(HttpServletRequest request) {
        StringBuilder builder = new StringBuilder();

        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            String value = request.getHeader(name);
            builder.append(String.format("%s: %s", name, value));
            builder.append("\n");
        }
        builder.append("request.getRemoteAddr: " + request.getRemoteAddr());
        System.out.println(builder);

        return builder.toString();
    }
}

3.nginx 配置

nginx_1 (172.17.0.3) 配置:

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://172.17.0.2:8080/;
}

nginx2 (172.17.0.4) 配置:

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://172.17.0.3:80/;
}

nginx3 (172.17.0.5) 配置:

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://172.17.0.4:80/;
}

4.执行 (经多层 nginx)

在 client (172.17.0.6) 执行:

root@9f2e34ea4d11:/# curl http://172.17.0.5/test
host: 172.17.0.5
x-real-ip: 172.17.0.4
x-forwarded-for: 172.17.0.6, 172.17.0.5, 172.17.0.4
connection: close
user-agent: curl/7.68.0
accept: */*
request.getRemoteAddr: 172.17.0.3

root@44d615a2f5b2:/# curl -H 'host: abc.com' http://172.17.0.5/test
host: abc.com
x-real-ip: 172.17.0.4
x-forwarded-for: 172.17.0.6, 172.17.0.5, 172.17.0.4
connection: close
user-agent: curl/7.68.0
accept: */*
request.getRemoteAddr: 172.17.0.3

x-forwarded-for 记录每层代理的 $remote_addr 的值

  1. nginx_3 (172.17.0.5) 获得请求的 $remote_addr (相当于执行 request.getRemoteAddr) 的值为 172.17.0.6,请求头的 x-real-ip 为 172.17.0.6,x-forwarded-for 值为 172.17.0.6;
  2. nginx_2 (172.17.0.4) 获得请求的 $remote_addr 的值为 172.17.0.5,设置请求头的 x-real-ip 为 172.17.0.5,x-forwarded-for 值为 172.17.0.6, 172.17.0.5;
  3. nginx_1 (172.17.0.3) 获得请求的 $remote_addr 的值为 172.17.0.4,设置请求头的 x-real-ip 为 172.17.0.4,x-forwarded-for 值为 172.17.0.6, 172.17.0.5, 172.17.0.4;
  4. web 获取请求头 x-real-ip 为 172.17.0.4,x-forwarded-for 为 172.17.0.6, 172.17.0.5, 172.17.0.4

5.执行 (经一层 nginx)

在 client (172.17.0.6) 执行:

root@9f2e34ea4d11:/# curl http://172.17.0.3/test
host: 172.17.0.3
x-real-ip: 172.17.0.6
x-forwarded-for: 172.17.0.6
connection: close
user-agent: curl/7.68.0
accept: */*
request.getRemoteAddr: 172.17.0.3

6.执行 (不经 nginx)

在 client (172.17.0.6) 执行:

root@9f2e34ea4d11:/# curl http://172.17.0.2:8080/test
host: 172.17.0.2:8080
user-agent: curl/7.68.0
accept: */*
request.getRemoteAddr: 172.17.0.6

综上:

  1. 如果多层 nginx 代理,x-forwarded-for 的第一个值为客户端 ip,x-real-ip 为最接近服务端口的 ip (注意:分析可知,如果只是最接近客户端的 nginx 配置 x-real-ip,其他 nginx 不配置 x-real-ip,此时 x-real-ip 也是客户端 ip),request.getRemoteAddr 为最接近服务端的 nginx 的 ip
  2. 如果一层 nginx 代理,x-real-ip、x-forwarded-for 为客户端 ip,request.getRemoteAddr 为 nginx 的 ip
  3. 如果不经 nginx 代理,request.getRemoteAddr 为客户端 的 ip

三、$host 示例

1.nginx 配置

nginx3 (172.17.0.5) 配置:

# 如果没有显式声明 default_server 则第一个 server 会被隐式的设为 default_server
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    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://172.17.0.4:80/;
    }
}

server {
    listen       80;
    listen  [::]:80;
    server_name  aa.com;

    location / {
        return 200 'This is aa.com';
    }
}

server {
    listen       80;
    listen  [::]:80;
    server_name  bb.com;

    location / {
    return 200 'This is bb.com';
    }
}

# 显示的定义一个 default_server
# server {
#     listen       80;
#     listen  [::]:80;
#     server_name  default_server;
# 
#     location / {
#   return 200 'This is default_server';
#     }
# }

2. client 配置

在 client (172.17.0.6) 配置 /etc/hosts

172.17.0.5      aa.com
172.17.0.5      bb.com

3.执行

在 client (172.17.0.6) 执行:

root@9f2e34ea4d11:/# curl http://aa.com
This is aa.com

root@9f2e34ea4d11:/# curl http://bb.com
This is bb.com

root@9f2e34ea4d11:/# curl -H 'host: bb.com' http://aa.com
This is bb.com

综上:

请求行中的 Host,如果有 Host 请求头,则用其值替换掉请求行中的主机名,如果请求中没有 Host 行和 Host 请求头,则等于请求匹配的 server 名称(处理请求 server 的 server_name 指令的值),值为小写,不包含端口