SpringBoot自定义拦截器实现IP白名单功能
转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/8993331.html
首先,相关功能已经上线了,且先让我先祷告一番:
阿门~ (-__-)
额,正文开始前我先说两句吧,能完成这个功能十分感谢csdn网友的一篇帖子的帮助,在此深表以感谢!
这位朋友的源贴也很不错,如觉得我写的不好,可以移步这里:https://blog.csdn.net/u011244202/article/details/54895038
先,我简要的说下这样做的原因,公司现在的主体架构是thinkphp的,由于php的开发人员走的已经差不多了再加上php岗位一时半会也很难补上,另外工头也是写java的...,算是元婴种种吧,现在所有的主体功能代码都慢慢的往java方向迁移,同时,请注意,java的开发人员目前来看只有我一个,遂架构迁移只能以springboot模块功能的方式逐步走,前端的架构刚开始走,又不能上线,只能将开发出来的模块给php后端调用,两种语言的后端数据交互又不能太复杂(就是不能加Auth),否则会加重php与java数据交互的成本,遂,就有了在springboot端做ip过滤。
额,我仔细分析了下,实现此功能目前大致可有三种方式:
A>因为spring框架中每一个controller就是一个拦截器,遂,可以在每个拦截器里面加ip过滤,显而易见的问题是=>代码会过于冗余,不利于维护
B>可以在springboot提供的拦截器里面做,这样。。。,可能是比较合适的,但似乎也会存在代码冗余的问题
C>可以在各个模块的顶部使用拦截器组件,比如Zuul,。。。问题是我们现在的框架还没完善到这一步
其实,如框架比较完善的情况下,以上方式都不太好,最好的是将前端分离,直接调用java,登陆校验等使用OAuth2来做签名认证。
欸~,可能由于个人能力有限,我就说说使用springboot自带的拦截器做的功能吧。
放代码:
1 package com.github.carvechris.security.common.util; 2 3 import javax.servlet.http.HttpServletRequest; 4 5 /** 6 * CREATE BY funnyZpC ON 2018/5/3 7 **/ 8 public class IPUtils { 9 /** 10 * 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址, 11 * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值 12 * 13 * @return ip 14 */ 15 public static String getRealIP(HttpServletRequest request) { 16 String ip = request.getHeader("x-forwarded-for"); 17 if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { 18 // 多次反向代理后会有多个ip值,第一个ip才是真实ip 19 if( ip.indexOf(",")!=-1 ){ 20 ip = ip.split(",")[0]; 21 } 22 } 23 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 24 ip = request.getHeader("Proxy-Client-IP"); 25 System.out.println("Proxy-Client-IP ip: " + ip); 26 } 27 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 28 ip = request.getHeader("WL-Proxy-Client-IP"); 29 System.out.println("WL-Proxy-Client-IP ip: " + ip); 30 } 31 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 32 ip = request.getHeader("HTTP_CLIENT_IP"); 33 System.out.println("HTTP_CLIENT_IP ip: " + ip); 34 } 35 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 36 ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 37 System.out.println("HTTP_X_FORWARDED_FOR ip: " + ip); 38 } 39 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 40 ip = request.getHeader("X-Real-IP"); 41 System.out.println("X-Real-IP ip: " + ip); 42 } 43 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 44 ip = request.getRemoteAddr(); 45 System.out.println("getRemoteAddr ip: " + ip); 46 } 47 return ip; 48 } 49 }
由于每次校验ip的时候是从请求头里面获取的(HttpSevletRequest),请求头里面的ip多种多样,比如在使用了nginx反向代理的时候ip就很丰富了,遂我就写了个IPUtils单独做这个事情。
,接下来就将这个Utils用起来,开始配置拦截器:
1 package com.github.carvechris.security.bankFlow.config; 2 3 import com.github.carvechris.security.bankFlow.entity.ZwIpFilter; 4 import com.github.carvechris.security.bankFlow.mapper.ZwIpFilterMapper; 5 import com.github.carvechris.security.common.util.IPUtils; 6 import org.apache.commons.lang3.StringUtils; 7 import org.apache.log4j.Logger; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.web.servlet.HandlerInterceptor; 10 import org.springframework.web.servlet.ModelAndView; 11 12 import javax.servlet.http.HttpServletRequest; 13 import javax.servlet.http.HttpServletResponse; 14 import java.util.List; 15 16 /** 17 * CREATE BY funnyZpC ON 2018/5/3 18 **/ 19 20 public class IPInterceptor implements HandlerInterceptor { 21 private static final Logger LOG= Logger.getLogger(IPInterceptor.class.getName()); 22 23 24 @Autowired 25 private ZwIpFilterMapper ipFilterMapper; 26 27 private ZwIpFilter ipFilter; 28 29 @Override 30 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 31 //过滤ip,若用户在白名单内,则放行 32 String ipAddress=IPUtils.getRealIP(request); 33 LOG.info("USER IP ADDRESS IS =>"+ipAddress); 34 if(!StringUtils.isNotBlank(ipAddress)) 35 return false; 36 ipFilter=new ZwIpFilter(); 37 ipFilter.setModule("sino-bankflow");//模块 38 ipFilter.setIp(ipAddress);//ip地址 39 ipFilter.setMark(0);//白名单 40 List<ZwIpFilter> ips=ipFilterMapper.select(ipFilter); 41 if(ips.isEmpty()){ 42 response.getWriter().append("<h1 style=\"text-align:center;\">Not allowed!</h1>"); 43 return false; 44 } 45 return true; 46 } 47 48 49 @Override 50 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 51 52 } 53 54 55 @Override 56 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 57 58 } 59 }
拦截器不是随随便便写的,需要使用SpringBoot提供的拦截器(HandlerInterceptor)模板来做,拦截器是什么=>"切面编程",另外需要说明的是这三个方法:
preHandle=>请求处理前拦截,处理通过返回true,否则返回false不进行处理
postHandle=>请求处理后拦截(页面渲染前),处理通过返回true,否则返回false
afterCompletion=>请求处理后拦截,(同上)
好了,既然已经清楚了,也就是在请求处理前拦截过滤IP,对于上面代码需要说明的是>由于使用的是Mybatis的方式实现DB操作,故注入ZwIpFilterMapper,将ip黑白名单放在数据库,可随时修改使用,额,我把ZwIpFilter这个对象的表结构给下吧。
拦截器定义OK了,但是并不能实现拦截,现在已经做好的只是按照springboot拦截器HandlerInterceptor定义好了拦截器的实现模板,遂,现在要做的是将拦截器放入spring上下文中,以实现启动即可拦截,思路是,先将实现的拦截器功能定义为一个Bean,然后将这个Bean放入到拦截器注册中心(InterceptorRegistry),同时再添加一个拦截前缀即可。我的实现代码和网友的方式大致无二,大家可根据自己的喜好进行调整以符合业务要求:
1 package com.github.carvechris.security.bankFlow.config; 2 3 /*import com.github.carvechris.security.auth.client.interceptor.ServiceAuthRestInterceptor; 4 import com.github.carvechris.security.auth.client.interceptor.UserAuthRestInterceptor; 5 */ 6 7 import com.github.carvechris.security.common.handler.GlobalExceptionHandler; 8 import org.springframework.context.annotation.Bean; 9 import org.springframework.context.annotation.Configuration; 10 import org.springframework.context.annotation.Primary; 11 import org.springframework.web.servlet.HandlerInterceptor; 12 import org.springframework.web.servlet.config.annotation.CorsRegistry; 13 import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 14 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 15 16 /*import org.springframework.web.servlet.config.annotation.InterceptorRegistry;*/ 17 18 /** 19 * Created by funnyZpC on 2017/9/8. 20 */ 21 @Configuration("admimWebConfig") 22 @Primary 23 public class WebConfiguration extends WebMvcConfigurerAdapter { 24 @Bean 25 GlobalExceptionHandler getGlobalExceptionHandler() { 26 return new GlobalExceptionHandler(); 27 } 28 29 //将自定义的拦截器定义为一个bean 30 @Bean 31 public HandlerInterceptor getMyInterceptor(){ 32 return new IPInterceptor(); 33 } 34 35 @Override 36 public void addInterceptors(InterceptorRegistry registry){ 37 // 多个拦截器组成一个拦截器链 38 // addPathPatterns 用于添加拦截规则, 这里假设拦截 /** 后面的全部链接 39 // excludePathPatterns 用户排除拦截 40 registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**"); 41 super.addInterceptors(registry); 42 } 43 44 }
最后,根据写的功能测试下,先我将原有的ip定义为127.0.0.3
试试:
显示不允许进入,说明功能已经成功了,现在我们来看看从控制台打印的ip信息:
打印的ip是127.0.0.1,这是本机IP,OK,现在还不能看出功能是否完全ok,我就测试下同局域网的手机请求看看,需要根据服务端ip做请求,服务端ip是:
故,我的请求地址应该是:http://11.11.11.239:8080/,看下控制台和页面输出:
由于我没有将手机的ip添加至数据库,遂显示“Not allowed!"说明功能正常ヽ(ˋ▽ˊ)ノ。
其实,功能已经在生产服务器调试了,事实上问题还多着呢,比如我们现有的java应用全部放在docker里面,获取的ip全部是docker的内网ip,至于这个问题不通没法测NGINX代理环境下的ip情况,以及阿里云的负载均衡下的ip代理问题,欸~,革命仍未成功,同志仍需努力呀。。。
END,现在是2018-05-12 19:41:11,晚饭时间,晚饭后我还有已有一贴要写
funnyzpc@gmail.com