目前网上流行的所谓“取真实IP地址”的方法,都有bug,没有考虑到多层透明代理的情况。
多数代码类似:
stringIpAddress=(HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]!=null
&&HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]!=String.Empty)
?HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]
:HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
事实上,上面的代码只<nobr><a target="_blank" style="border-bottom: 1px dotted rgb(0, 0, 0); color: rgb(0, 0, 255); background-color: transparent; text-decoration: underline;" class="iAs">试用</a></nobr>与用户只使用了1层代理,如果用户有2层,3层HTTP_X_FORWARDED_FOR的值是:“本机真实IP,1层代理IP,2层代理IP,.....”,如果这个时候你的数据<nobr><a target="_blank" style="border-bottom: 1px dotted rgb(0, 0, 0); color: rgb(0, 0, 255); background-color: transparent; text-decoration: underline;" class="iAs">中保</a></nobr>存IP字段的长度很小(15个字节),数据库就报错了。
实际应用中,因为使用多层透明代理的情况比较少,所以这种用户并不多。
其他应用情况,现在越来越多的网站使用了代理加速方式,比如新浪、SOHU的新闻都使用Squid做代理方式,利用多台服务器分流。Squid本身类似透明代理,会发送“HTTP_X_FORWARDED_FOR”,HTTP_X_FORWARDED_FOR中包括客户的IP地址,如果此时客户已经使用了一层透明代理,那么程序取的“HTTP_X_FORWARDED_FOR”就包括两个IP地址。(我遇到过3个IP地址的情况,4个的未遇到过)
所以取“真正”IP地址的方式,还应该判断“HTTP_X_FORWARDED_FOR”中是否有“,”<nobr><a target="_blank" style="border-bottom: 1px dotted rgb(0, 0, 0); color: rgb(0, 0, 255); background-color: transparent; text-decoration: underline;" class="iAs">逗号</a></nobr>,或者<nobr><a target="_blank" style="border-bottom: 1px dotted rgb(0, 0, 0); color: rgb(0, 0, 255); background-color: transparent; text-decoration: underline;" class="iAs">长度</a></nobr>是否超长(超过15字节xxx.xxx.xxx.xxx)。
所以代码应该如下:
/**////<summary>
///取得客户端真实IP。如果有代理则取第一个非内网地址
///</summary>
publicstaticstringIPAddress
{
get
{
stringresult=String.Empty;
result=HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if(result!=null&&result!=String.Empty)
{
//可能有代理
if(result.IndexOf(".")==-1)//没有“.”肯定是非IPv4格式
result=null;
else
{
if(result.IndexOf(",")!=-1)
{
//有“,”,估计多个代理。取第一个不是内网的IP。
result=result.Replace("","").Replace("'","");
string[]temparyip=result.Split(",;".ToCharArray());
for(inti=0;i<temparyip.Length;i++)
{
if(Text.IsIPAddress(temparyip[i])
&&temparyip[i].Substring(0,3)!="10."
&&temparyip[i].Substring(0,7)!="192.168"
&&temparyip[i].Substring(0,7)!="172.16.")
{
returntemparyip[i];//找到不是内网的地址
}
}
}
elseif(Text.IsIPAddress(result))//代理即是IP格式
returnresult;
else
result=null;//代理中的内容非IP,取IP
}
}
stringIpAddress=(HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]!=null&&HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]!=String.Empty)?HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]:HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
if(null==result||result==String.Empty)
result=HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
if(result==null||result==String.Empty)
result=HttpContext.Current.Request.UserHostAddress;
returnresult;
}
}
取“HTTP_X_FORWARDED_FOR”的弊端。
HTTP_X_FORWARDED_FOR是HTTP协议中头的一部分,不影响TCP的通讯。也就是说实际上<nobr><a target="_blank" style="border-bottom: 1px dotted rgb(0, 0, 0); color: rgb(0, 0, 255); background-color: transparent; text-decoration: underline;" class="iAs">客户</a></nobr>端可以发送任意内容的HTTP_X_FORWARDED_FOR,以就是伪造IP。最简单的是WEB程序的IP记录,本来是要记录真实IP的,反而被“黑客”欺骗。当你的应用程序记录客户的访问IP、拒绝或允许部分IP的访问、错误日志都会出错,甚至误杀。
因此必要的安全日志应该记录完整的“HTTP_X_FORWARDED_FOR”(至少给数据库中的字段分配3*15+2个<nobr><a target="_blank" style="border-bottom: 1px dotted rgb(0, 0, 0); color: rgb(0, 0, 255); background-color: transparent; text-decoration: underline;" class="iAs">字节</a></nobr>,以记录至少3个IP)和“REMOTE_ADDR”。对HTTP_X_FORWARDED_FOR的IP格式检查也是不可少的。
附:(Text是我自定义的一个类,IsIPAddress是其中的一个判断是否是IP<nobr><a target="_blank" style="border-bottom: 1px dotted rgb(0, 0, 0); color: rgb(0, 0, 255); background-color: transparent; text-decoration: underline;" class="iAs">地址</a></nobr>格式的方法)
#regionboolIsIPAddress(str1)判断是否是IP格式
/**////<summary>
///判断是否是IP地址格式0.0.0.0
///</summary>
///<paramname="str1">待判断的IP地址</param>
///<returns>trueorfalse</returns>
publicstaticboolIsIPAddress(stringstr1)
{
if(str1==null||str1==string.Empty||str1.Length<7||str1.Length>15)returnfalse;
stringregformat=@"^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$";
Regexregex=newRegex(regformat,RegexOptions.IgnoreCase);
returnregex.IsMatch(str1);
}
#endregion