客户端发送请求到GF时通过gorouter代理转发,转发时gorouter会修改header中的remoteAddr和remoteHost的值,所以在GF中获得的remoteAddr和remoteHost的值为gorouter的remoteAddr和remoteHost的值,与标准式样不一致。

使用gorouter转发请求时,它会将请求IP存储在头X-Forwarded-For中,所以可以通过对X-Forwarded-For头的解析获得客户端IP。
X-Forwarded-For:简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP。这一HTTP头中X-Forwarded-For的格式如下:
X-Forwarded-For: client1, proxy1, proxy2
其中的IP通过一个逗号+空格把多个IP地址区分开, 最左边(client1)是最原始客户端的IP地址, 代理服务器每成功收到一个请求,就把请求来源IP地址添加到右边。这是在RFC7239规范中说明的,gorouter也遵守该规范。

设置Request的remotAddr和remoteHost属性有两个方案:
【方案1】
因为Request请求在通过代理时会将发送请求的IP存入到X-Forwarded-For头中且每次都会添加到末尾,所以取出X-Forwarded-For头的第一个IP地址,那么它就是客户端的IP。具体实现如下:
1.    判断X-Forwarded-For头是否存在且不为空。
2.    如果为空则不设置Request的remotAddr和remoteHost属性。
3.    如果X-Forwarded-For头存在且不为空,则取其第一个IP地址,并赋值给Request的remoteAddr和remoteHost属性。

示例:

X-Forwarded-For:Client_IP, proxy1, CF_IP, 192.168.0.10(192.168.0.10是内部地址)
Client_IP是X-Forwarded-For头中第一个IP,所以判断它就是客户端IP,将其赋值给Request的remoteAddr和remoteHost属性。

代码:

 private String getFirstIpOfXFF() {
        String remoteIp = null;
        StringBuilder concatRemoteIpHeaderValue = new StringBuilder();
        for (Iterator<String> headerIterator = getHeaders(remoteIpHeader)
                .iterator(); headerIterator.hasNext();) {
            if (concatRemoteIpHeaderValue.length() > 0) {
                concatRemoteIpHeaderValue.append(", ");
            }
            concatRemoteIpHeaderValue.append(headerIterator.next());
        }
        String[] remoteIpHeaderValue = commaDelimitedListToStringArray(concatRemoteIpHeaderValue
                .toString());
        if (remoteIpHeaderValue.length != 0) {
            remoteIp = remoteIpHeaderValue[0].trim();
        }
        return remoteIp;
    }


【方案2】
Tomcat的实现方式
因为Request请求在通过代理时会将发送请求的IP存入到X-Forwarded-For头中且每次都会添加到末尾,所以去除末尾属于代理服务器的IP,那么剩下的最后一个就是客户端的IP。具体实现如下:
1、
配置文件

<Valve
   className="org.apache.catalina.valves.RemoteIpValve"  
   internalProxies="192\.168\.0\.10, 192\.168\.0\.11"  
   remoteIpHeader="x-forwarded-for"  
   remoteIpProxiesHeader="x-forwarded-by"  
   trustedProxies="192\.168\.0\.13, 192\.168\.0\.12"/>

2、
如果X-Forwarded-For头存在且不为空,从其中读取IP列表,按从右向左的顺序扫描各个IP,规则如下:
1)如果IP列表中当前的IP与internalProxies中的IP匹配,该IP被删去,处理下个IP。
2)如果IP列表中当前的IP与truestedProxies中的IP匹配,该IP被添加在proxiesHeaderValue中,处理下个IP。
如果IP列表中当前的IP与truestedProxies中的IP不匹配,该IP就被置为客户端IP。
3)将IP列表中剩余IP添加到newRemoteIpHeaderValue,扫描结束。
3、
如果扫描结束还没有找到符合规则的客户端IP,则将最后扫描的IP设置为客户端IP。
4、
将上述扫描中产生的客户端IP赋值给Request的remoteAddr和remoteHost属性。
将上述扫描中产生的proxiesHeaderValue中的IP设置到remoteIpProxiesHeader头。
将上述循环中产生的newRemoteIpHeaderValue中的IP设置到remoteIpHeader头。

如图:

wKiom1YXsUHCj_X1AAIDrpm7ky0979.jpg

wKioL1YXsVvxvFuyAAEXUC_dGd4923.jpg


示例:
X-Forwarded-For:Client_IP, ELB_IP, CF_IP, 192.168.0.10(192.168.0.10是内部地址)
配置文件

<Valve   
   className="org.apache.catalina.valves.RemoteIpValve"  
   internalProxies="192\.168\.0\.10"  
   remoteIpHeader="x-forwarded-for"  
   remoteIpProxiesHeader="x-forwarded-by"  
   trustedProxies="CF_IP"/>

获取客户端IP过程如下:
1、X-Forwarded-For头不为空,获取IP列表Client_IP, ELB_IP, CF_IP, 192.168.0.10。
2、获取到192.168.0.10,与internalProxies中IP匹配,匹配成功,删除此IP然后获取下一个IP。
3、获取到CF_IP,与internalProxies中IP匹配,匹配失败,与truestedProxies中的IP匹配,匹配成功,添加此IP到proxiesHeaderValue然后获取下一个IP。
4、获取到ELB_IP,与internalProxies中IP匹配,匹配失败,与truestedProxies中的IP匹配,匹配失败,所以ELB_IP就是客户端IP。
5、将ELB_IP赋值给Request的remoteAddr和remoteHost属性。
6、将CF_IP设置到remoteIpProxiesHeader头。
7、将Client_IP设置到remoteIpHeader头。


代码:

private HashMap<String, LinkedList<String>> getMatchedIpOfXFF() {
        String remoteIp = null;
        LinkedList<String> remoteIpValue = new LinkedList<String>();
        LinkedList<String> proxiesHeaderValue = new LinkedList<String>();
        LinkedList<String> newRemoteIpHeaderValue = new LinkedList<String>();
        HashMap<String, LinkedList<String>> headerValueMap = new HashMap<String, LinkedList<String>>();
        StringBuilder concatRemoteIpHeaderValue = new StringBuilder();
        for (Iterator<String> headerIterator = getHeaders(remoteIpHeader)
                .iterator(); headerIterator.hasNext();) {
            if (concatRemoteIpHeaderValue.length() > 0) {
                concatRemoteIpHeaderValue.append(", ");
            }
            concatRemoteIpHeaderValue.append(headerIterator.next());
        }
        String[] remoteIpHeaderValue = commaDelimitedListToStringArray(concatRemoteIpHeaderValue
                .toString());
        int idx;
        for (idx = remoteIpHeaderValue.length - 1; idx >= 0; idx--) {
            String currentRemoteIp = remoteIpHeaderValue[idx];
            remoteIp = currentRemoteIp;
            if (internalProxies.matcher(currentRemoteIp).matches()) {
                // do nothing, ignore internal proxies.
            } else if (trustedProxies != null
                    && trustedProxies.matcher(currentRemoteIp).matches()) {
                // if currentRemoteIp match with trusted proxies,add this ip to
                // proxiesHeader.
                proxiesHeaderValue.addFirst(currentRemoteIp);
            } else {
                idx--; // decrement idx because break statement doesn't do it.
                break;
            }
        }
        // continue to loop on remoteIpHeaderValue to build the new value of the
        // remoteIpHeader.
        for (; idx >= 0; idx--) {
            String currentRemoteIp = remoteIpHeaderValue[idx];
            newRemoteIpHeaderValue.addFirst(currentRemoteIp);
        }
        remoteIpValue.addFirst(remoteIp);
        headerValueMap.put("remoteIpValue", remoteIpValue);
        headerValueMap.put("proxiesHeaderValue", proxiesHeaderValue);
        headerValueMap.put("newRemoteIpHeaderValue", newRemoteIpHeaderValue);
        return headerValueMap;
    }