背景:如果公司的网络扩扑图从统一的nginx转发过来的,需要在NG的配置文件里面配置:把用户实际的IP注入到请求头部的X-Forwarded-For ,然后应用程序去截取X-Forwarded-For中的内容来获取用户的实际IP。
判断是否内网的规定来自:
如果不用此方案的后果:用户的IP会被伪造,会存在很多衍生的漏洞
伪代码:
1、从请求的head中读取X-Forwarded-For ,然后按照逗号拆分为数组
2、循环数据组(从0开始循环),读取第一个不是内网IP开头的IP即可。(所谓的内网IP是指10、172、192开头的某些IP)
3、如果X-Forwarded-For 没有值,则从请求头里面的X-Real-IP来获取
java示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if(ip!=null&&!"".equals(ip) && !"unKnown".equalsIgnoreCase(ip)){
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if(index != -1){
String[] arrIp=ip.split(",");
for(String subIp:arrIp){
if(!checkInIp(subIp)){//找到第一个不是内部IP的进行返回
return subIp;
}
}
}else{
return ip;
}
}
ip = request.getHeader("X-Real-IP");
if(ip!=null&&!"".equals(ip)&& !"unKnown".equalsIgnoreCase(ip)){
return ip;
}
ip = request.getHeader("PROXY_FORWARDED_FOR");
if(ip!=null&&!"".equals(ip) && !"unKnown".equalsIgnoreCase(ip)){
//做下兼容
int index = ip.indexOf(",");
if(index != -1){
String[] arrIp=ip.split(",");
for(String subIp:arrIp){
if(!checkInIp(subIp)){//找到第一个不是内部IP的进行返回
return subIp;
}
}
}else{
return ip;
}
}
return request.getRemoteAddr();
}
static String reg = "((192\\.168|172\\.([1][6-9]|[2]\\d|3[01]))"
+ "(\\.([2][0-4]\\d|[2][5][0-5]|[01]?\\d?\\d)){2}|"
+ "^(\\D)*10(\\.([2][0-4]\\d|[2][5][0-5]|[01]?\\d?\\d)){3})";
static Pattern p = Pattern.compile(reg); // Instances of this class are immutable and are safe for use by multiple concurrent threads
/**
* 判断是否为内网IP
* 10.0.0.0 - 10.255.255.255
* 172.16.0.0 - 172.31.255.255
* 192.168.0.0 - 192.168.255.255
* @param ip
* @return
* 注意:假如 ip 是 null,如返回 false 会误导使用者,因此抛出 NullPointerException
*/
public static boolean checkInIp(String ip) {
Matcher matcher = p.matcher(ip);
return matcher.find();
}