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,晚飯時間,晚飯後我還有已有一貼要寫