在开发工作中,我们常常需要获取客户端的IP。一般获取客户端的IP地址的方法是:request.getRemoteAddr();但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实IP地址了。

原因:由于在客户端和服务之间增加了中间代理,因此服务器无法直接拿到客户端的IP,服务器端应用也无法直接通过转发请求的地址返回给客户端。

现在图示代理上网和IP的关系:

第一种情况:不通过代理上网,服务器端拿到真实IP

第二种情况:通过代理服务器如:Nginx,Squid等一层代理或多层代理上网,如下图:

需要注意的是X-Forwarded-For和X-Real-IP都不是http的正式协议头,而是squid等反向代理软件最早引入的,之所以resin能拿到,是因为NGINX里一般缺省都会这么配置转发的http请求:

location / {

         proxy_pass       http://yourdomain.com;

         proxy_set_header   Host             $host;

         proxy_set_header   X-Real-IP        $remote_addr;

         proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

}

经过多层代理后,我们会发现X-Forwarded-For是一个可叠加的过程,后面的代理会把前面代理的IP加入X-Forwarded-For,类似于python的列表append的作用.

就是这样

X-Forwarded-For:192.168.1.2, 192.168.1.3, 192.168..n

从X-Forwarded-For的定义来看,ips[0]才是原始客户端ip,如果这个都不是,那拿第二个就更不靠谱了,我们平时检验的时候,可能是直接在内网挂代理去访问的,跟外面网友访问经过的网络路径不一样,后面不停添加的是经过的每一层代理ip才对,下面举例说明;

request.getRemoteAddr() 192.168.239.196

request.getHeader("X-Forwarded-For") 58.63.227.162, 192.168.237.178, 192.168.238.218

request.getHeader("X-Real-IP") 192.168.238.218

所以访问的流程应该是这样,客户端58.63.227.162发出请求,经过192.168.237.178, 192.168.238.218两层转发,到了192.168.239.196这台NGINX上,NGINX就把X-Real-IP头设成了自己看到的remote_addr,也就是直接发给到他的192.168.238.218,这时候resin收到这个包,对resin来说直接发给他的remote_addr就是NGINX的ip,也就是192.168.239.196,那么resin里面的request.getRemoteAddr()就是192.168.239.196,那么在resin里拿最原始的ip逻辑(也就是拿能够知道的最外层的ip)应该是这样:

  1. 如果XFF不为空,拿XFF的左边第一个
  2. 如果XFF为空,拿XRI
  3. 如果XRI为空,只能拿request.getRemoteAddr(),也就是只能拿到最直接发给他的机器ip了

也可以发现 ,如果只有一层代理,X-Forwarded-For和X-Real-IP这两个头的值就是一样的

其他都不可考究,参考Java代码如下:

第一种代码:

      /**
* 从Request对象中获得客户端IP,处理了HTTP代理服务器和Nginx的反向代理截取了ip
* @param request
* @return ip
*/
public static String getLocalIp(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
String forwarded = request.getHeader("X-Forwarded-For");
String realIp = request.getHeader("X-Real-IP"); String ip = null;
if (realIp == null) {
if (forwarded == null) {
ip = remoteAddr;
} else {
ip = remoteAddr + "/" + forwarded.split(",")[0];
}
} else {
if (realIp.equals(forwarded)) {
ip = realIp;
} else {
if(forwarded != null){
forwarded = forwarded.split(",")[0];
}
ip = realIp + "/" + forwarded;
}
}
return ip;
}

第二种代码:

 1      public static String getIp(HttpServletRequest request) {
2 String remoteAddr = request.getRemoteAddr();
3 String forwarded = request.getHeader("X-Forwarded-For");
4 String realIp = request.getHeader("X-Real-IP");
5
6 String ip = null;
7 if (realIp == null) {
8 if (forwarded == null) {
9 ip = remoteAddr;
10 } else {
11 ip = remoteAddr + "/" + forwarded;
12 }
13 } else {
14 if (realIp.equals(forwarded)) {
15 ip = realIp;
16 } else {
17 ip = realIp + "/" + forwarded.replaceAll(", " + realIp, "");
18 }
19 }
20 return ip;
21 }

第三种代码:

 1        public static String getIp2(HttpServletRequest request) {
2 String ip = request.getHeader("X-Forwarded-For");
3 if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
4 //多次反向代理后会有多个ip值,第一个ip才是真实ip
5 int index = ip.indexOf(",");
6 if(index != -1){
7 return ip.substring(0,index);
8 }else{
9 return ip;
10 }
11 }
12 ip = request.getHeader("X-Real-IP");
13 if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
14 return ip;
15 }
16 return request.getRemoteAddr();
17 }

第三种是最合适的,最清晰理解的。

最后,为什么推荐使用X-Forwarded-For而不是X-Real-IP,虽然有时它们的值是一样的

获取真实ip之所以通用X-Forwarded-For:
1. 他在正向(如squid)反向(如nginx)代理中都是标准用法,而正向代理中是没有x-real-ip相关的标准的,也就是说,如果用户访问你的 nginx反向代理之前,还经过了一层正向代理,你即使在nginx中配置了x-real-ip,取到的也只是正向代理的IP而不是客户端真实IP
2. 大部分nginx反向代理配置文章中都没有推荐加上x-real-ip ,而只有x-forwarded-for,因此更通用的做法自然是取x-forwarded-for
3. 多级代理很少见,只有一级代理的情况下二者是等效的
4. 如果有多级代理,x-forwarded-for效果是大于x-real-ip的,可以记录完整的代理链路

经过Nginx代理后如何区分HTTP请求头中的X-Forwarded-For和X-Real-IP,以及Java示例的更多相关文章

  1. 使用nginx代理后以及配置https后,如何获取真实的ip地址

    使用nginx代理后以及配置https后,如何获取真实的ip地址 Date:2018-8-27 14:15:51 使用nginx, apache等反向代理后,如果想获取请求的真实ip,要在nginx中 ...

  2. go 语言的库文件放在哪里?如何通过nginx代理后还能正确获取远程地址

    /usr/local/Cellar/go/1.5.1/libexec/src/ 他的RemoteAddr 是从哪里获取? func (c *conn) RemoteAddr() Addr { if ! ...

  3. HTTP 请求头中的 Remote_Addr,X-Forwarded-For,X-Real-IP

    REMOTE_ADDR 表示发出请求的远程主机的 IP 地址,remote_addr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时,假设中间 ...

  4. HTTP 请求头中的 X-Forwarded-For(转)

    原文:https://imququ.com/post/x-forwarded-for-header-in-http.html 我一直认为,对于从事 Web 前端开发的同学来说,HTTP 协议以及其他常 ...

  5. Http 请求头中的 Proxy-Connection

    平时用 Chrome 开发者工具抓包时,经常会见到 Proxy-Connection 这个请求头.之前一直没去了解什么情况下会产生它,也没去了解它有什么含义.最近看完<HTTP 权威指南> ...

  6. post请求头中常见content-type(非常重要)

    定义和用法 enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码.默认地,表单数据会编码为 "application/x-www-form-urlencoded". ...

  7. shiro 获取请求头中的 rememberMe

    前言: 上一篇提到了, 将 sessionId 放到请求头中去, 那rememberMe是否也可以放到请求头中去呢. 其实不管是sessionId还是rememberMe, shiro都会默认往coo ...

  8. shiro 获取请求头中的 sessionId

    前言: 在前后端项目中, 前端有可能会要求, 后台返回一个 sessionId 给他, 然后他在请求后台接口时, 把这个sessionId 带给后台, 后台拿到这个sessionId , 就能识别, ...

  9. Ajax 请求头中常见content-type

    四种常见的 POST 提交数据方式 HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范.规范把 HTTP 请求分为三个部分:状态行.请求头.消息主体.协议规定 POST ...

随机推荐

  1. POJ1325Machine Schedule(匈牙利算法)

                                                          Machine Schedule Time Limit: 1000MS   Memory L ...

  2. 第3天-DIV+CSS布局

    盒子模型 margin 设置外边距宽度 有4个值的时候: maigin 10px 5px 15px 20px; (上.右.下.左)有3个值的时候: margin: 10px 5px 15px; (上. ...

  3. Xamarin.Forms使用Slider注意问题

    Xamarin.Forms使用Slider注意问题   Xamarin.Forms中,Slider用来构建滑块控件.其中,Minimum表示最小值,Maximum表示最大值.如果Minimum大等于1 ...

  4. 网站模糊测试爆破工具Wfuzz

    网站模糊测试爆破工具Wfuzz   模糊测试爆破使用模糊测试的方式对HTTP请求中的各个参数同时进行猜测爆破.例如,渗透测试人员可以采用不同的HTTP请求方式来访问由字典生成的网页路径,以判断网页目录 ...

  5. NOIP 2015 跳石头

    题目背景 一年一度的“跳石头”比赛又要开始了! 题目描述 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有 N 块岩石(不 ...

  6. [OpenJudge8471][划分DP]切割回文

    切割回文 总时间限制: 1000ms 内存限制: 65536kB [描述] 阿福最近对回文串产生了非常浓厚的兴趣. 如果一个字符串从左往右看和从右往左看完全相同的话,那么就认为这个串是一个回文串.例如 ...

  7. CentOS7.x 通过mail命令发,使用465端口(smtps协议)发送邮件

    #创建证书mkdir -p /root/.certs/echo -n | openssl s_client -connect smtp.qq.com:465 | sed -ne '/-BEGIN CE ...

  8. android 博客列表

    1.  Hongyang http://blog.csdn.net/lmj623565791/article/details/37970961

  9. JAVA加解密 -- 对称加密算法与非对称加密算法

    对称加密算法:双方必须约定好算法 DES 数据加密标准:由于不断地被破解 自98年起就已经逐渐放弃使用 AES 目前使用最多的加密方式,官方并未公布加密方式已被破解,替代DES 实现和DES非常接近 ...

  10. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-PLC支持哪些PLC语言类型

    PLC的标准化语言,统称为IEC 61131-3,该规范下有五种不同的语言可以创建PLC程序,TwinCAT都支持. IL(指令列表): 每条指令都从一个新行开始并包含一个操作和一个或多个操作数,一条 ...