使用WAF之后相当于多一层代理,后方的nginx或者服务直接获取到的IP其实是WAF的回源IP,而不是终端用户的真是IP,那么如何获得真实IP呢?其实WAF会把真实IP放到请求头里,通用Header为:X-Forwarded-For,目前看阿里云、华为云都会放到这个header。经发现阿里云还会放到Header:X-Real-IP,而Cloudflare会放到Header:CF-Connecting-IP。

可以在nginx侧打印日志进行测试,注意nginx日志的变量命名稍微有些不同,header要加上http_开头,横杆要改为下划线。例如:$http_x_forwarded_for、 $http_x_real_ip。完整例子:

1
2
3
log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$http_x_real_ip"';

JAVA获取真实IP

结合hutool工具类,具体实现代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.ServletUtil;

public class IpAddressUtil {

    private static final Log log = Log.get();

    private static final String LOCAL_IP = "127.0.0.1";

    private static final String LOCAL_REMOTE_HOST = "0:0:0:0:0:0:0:1";

    /**
     * 获取客户端ip
     */
    public static String getIp(HttpServletRequest request) {
        if (ObjectUtil.isEmpty(request)) {
            return LOCAL_IP;
        } else {
            String remoteHost = ServletUtil.getClientIP(request);
            return LOCAL_REMOTE_HOST.equals(remoteHost) ? LOCAL_IP : remoteHost;
        }
    }
    
}

经过分析hutool的获取IP的实现代码是通过以下一系列Header来获取:

1
2
3
4
5
6
7
8
public static String getClientIP(HttpServletRequest request, String... otherHeaderNames) {
        String[] headers = new String[]{"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
        if (ArrayUtil.isNotEmpty(otherHeaderNames)) {
            headers = (String[])ArrayUtil.addAll(new String[][]{headers, otherHeaderNames});
        }

        return getClientIPByHeader(request, headers);
}

Controller快速获取客户端IP的调试代码

1
2
3
4
5
6
7
8
9
    @GetMapping("/getClientIp")
    public String testClientIp(HttpServletRequest request) {
        String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
        StringBuilder sb = new StringBuilder();
        for (String header : headers) {
            sb.append(header).append(":").append(ServletUtil.getClientIPByHeader(request, header)).append("\n");
        }
        return sb.toString();
    }

使用ngx_http_realip_module模块

以Cloudflare为例:

在http节点下配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
	# 启用 ngx_http_realip_module 模块
    real_ip_header CF-Connecting-IP;
    #real_ip_header X-Forwarded-For;

    # 设置 Cloudflare 的 IP 地址段
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 131.0.72.0/22;
...

上述配置中 Cloudflare IP 地址列表需要进行定期更新,以包含最新的 IP 地址段。可以从 https://www.cloudflare.com/ips/ 获取最新的 Cloudflare IP 地址列表。

文本地址为:

https://www.cloudflare.com/ips-v4/

https://www.cloudflare.com/ips-v6/

这样真实IP会存储到remote_addr中。

nginx快速获取客户端ip调试配置

1
2
3
4
5
location /getip
{
    default_type "text/plain;charset=utf-8";
        return 200 "$remote_addr";
}