2018-11-20 · Develop

Java 获取客户端IP地址

在 Java 的 web 项目中获取客户端 IP地址的方法是 request.getRemoteAddr() , 这种方法在大部分情况下是有效的。但是在通过了如 Apache、Squid 等代理软件之后就不能获取到客户端的真实 IP地址了。

由于在客户端和服务之间增加了中间代理,因此服务器无法直接拿到客户端的IP,服务器端应用也无法直接通过转发请求的地址返回给客户端。但是在代理进行转发的过程中,一般都会在转发请求的 HTTP 头信息中添加相应的转发信息,用以跟踪原有的客户端IP地址和原来客户端请求的服务器地址。

我们来简单的验证下

    public void printRequestHeaders(HttpServletRequest request) {
        Enumeration<String>  headerNames = request.getHeaderNames();
        HashMap<String, String> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            headers.put(key, value);
        }
        log.debug(JsonUtils.toJsonPretty(headers));
    }

将所以的 header 进行打印后如下

{
    "referer": "https://www.google.com/",
    "cf-ipcountry": "US",
    "cf-ray": "348c7acba8a02210-EWR",
    "x-forwarded-proto": "https",
    "accept-language": "en-US,en;q=0.8",
    "cookie": "__cfduid=d3c6e5d73aa55b6b42fad9600c94849851490726068; _ga=GA1.2.450731937.1490726069",
    "x-forwarded-for": "100.8.204.40",
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "x-real-ip": "108.162.219.236",
    "x-forwarded-server": "hostingcompass.com",
    "x-forwarded-host": "hostingcompass.com",
    "cf-visitor": "{\"scheme\":\"https\"}",
    "host": "127.0.0.1:8080",
    "upgrade-insecure-requests": "1",
    "connection": "close",
    "cf-connecting-ip": "100.8.204.40",
    "accept-encoding": "gzip",
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
}

大致会有上面的信息。其中 x-forwarded-for 就是 Squid 代理所设置的请求头, x-real-ip 是 Nginx 代理的。下面我们来具体看看比较常用的一些请求头。

常见的代理请求头

这是一个 Squid 开发的字段,只有在通过了HTTP代理或者负载均衡服务器时才会添加该项。
如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址。格式一般为

X-Forwarded-For:client1,proxy1,proxy2....proxyN

这个一般是经过 apache http 服务器的请求才会有,用 apache http 做代理时一般会加上 Proxy-Client-IP 请求头,而 WL-Proxy-Client-IP 是他的 weblogic 插件加上的头。

有些代理服务器会加上此请求头。

nginx 代理一般会加上此请求头。

注意:

  1. 上面的这些都不是标准的 HTTP 请求头,各个代理服务器的请求头都不相同,新的代理服务器也可能使用其他的请求头。
  2. 代理服务器不一定会加上请求头,比如很多匿名代理就是这么干的。
  3. 不同的网络架构,判断请求头的顺序不一定相同。
  4. 请求头都是可以进行伪造的,但是 request.getRemoteAddr() 的方式却很难伪造,所以需要真实度很高的情况下,就请忽略代理的请求头。

获取真实IP

下面就使用 Java 获取真实IP,注意判断顺序和网络架构有关,这种获取方式很容易伪造。

public class IPUtils {

	private static Logger log = LoggerFactory.getLogger(IPUtils.class);

	public static String getIpAddr(HttpServletRequest request) {
    	String ip = null;
        try {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("X-Real-IP");
            }           
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
                ip = InetAddress.getLocalHost().getHostAddress();
            }            
        } catch (Exception e) {
        	log.error("IPUtils ERROR ", e);
        }
        
//        //使用代理,则获取第一个IP地址
//        if(StringUtils.isEmpty(ip) && ip.length() > 15) {
//			if(ip.indexOf(",") > 0) {
//				ip = ip.substring(0, ip.indexOf(","));
//			}
//		}

        return ip;
    }
	
}

参考文档
干货:Java正确获取客户端真实IP方法整理
How to get client Ip Address in Java