Interceptor、Filter
通过上方这个图我们可以知道,在 Filter 和 Inteceptor 中间有一层Servlet。而Servlet就是提交request的地方。所以我们要重写HttpServletRequest方法只能在Servlet之前。也就是filter 中。
区别:
-
1.servlet请求,顺序:Filter ——> interceptor。
-
2.Filter的作用是对所有进行过滤,包括接口或者静态资源,interceptor 仅拦截 请求。
-
3.Filter对请求或者资源进行过滤,筛选合适的请求或者资源。interceptor,仅对不符合的请求拦截。
-
4.Filter基于回调函数,我们需要实现的filter接口中doFilter方法就是回调函数,而interceptor则基于 java本身的反射机制,这是两者最本质的区别。
-
5.Filter是依赖于servlet容器的,即只能在servlet容器中执行,很显然没有servlet容器就无法来回调doFilter方法。而interceptor与servlet容器无关。
-
filter:目的就是可以以流的方式多次获取请求参数。
-
Interceptor:对回调接口进行统一的验证签名。
回调接口都需要验证签名,而且签名规则一样,所以想拿到拦截器处理。
如果签名或者ip地址不符合条件,直接就返回了。而具体的接口只要专注业务处理,不需要验证签名了。
下面贴一下在springboot中的使用:
一、Filter
如果请求是GET方法,可以直接通过getParameter(String param)方法读取指定参数,可读取多次;
而POST方法的参数是存储在输入流中,只能读一次,不能多次读取。
有时需要在filter里打印请求参数,因而在filter里读取post请求里的输入流后,会导致具体的controller里拿不到请求参数。
解决方法:
-
1、采用ThreadLocal,在filter里把读取到的post参数存入ThreadLocal里,而controller就可以再从ThreadLocal里把请求参数读取出来。
-
2、使用servlet提供的HttpServletRequestWrapper类,重写相关ServletRequest方法,实现多次读取的能力。
1.1 ThreadLocal方法
public class ThreadCache { // ThreadLocal里只存储了简单的String对象,也可以自己定义对象,存储更加复杂的参数 private static ThreadLocal<String> threadLocal = new ThreadLocal<String>(); public static String getPostRequestParams{ return threadLocal.get(); } public static void setPostRequestParams(String postRequestParams){ threadLocal.set(postRequestParams); } public static void removePostRequestParams(){ threadLocal.remove(); } }一个简单的filter:
import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebFilter(value = {"/test/threadLocal/*"}) public class SimpleFilter implements Filter { private static Logger log = LoggerFactory.getLogger(SimpleFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; try { if ("POST".equals(req.getMethod().toUpperCase())) { // 获取请求参数 byte[] bytes = IOUtils.toByteArray(request.getInputStream()); String params = new String(bytes, req.getCharacterEncoding()); ThreadCache.setPostRequestParams(params); log.info("filer-post请求参数:[params={}]", params); } else { log.info("非post请求"); } chain.doFilter(request, response); } catch (Exception e) { log.error(e.getMessage(), e); } } @Override public void destroy() { } }简单的测试controller
import com.sankuai.xm.ems.auth.filter.ThreadCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/test") public class TestController { private static Logger log = LoggerFactory.getLogger(TestController.class); @RequestMapping(value = "/threadLocal/getPostRequestParams",method = RequestMethod.POST) @ResponseBody public void getPostRequestParams() { String params = ThreadCache.getPostRequestParams(); log.info("controller-post请求参数:[params={}]", params); } }这里我们只保存了post参数,从ThreadLocal中反复读取,而get方法的还需要从request里获取。
1.2 HttpServletRequestWrapper方法
方式一
- 实现一个HttpServletRequestWrapper子类
- 实现另一个filter
方式二
- 实现一个HttpServletRequestWrapper子类
- 实现另一个filter
我们在上面的TestController里加入一个新的处理方法
@RequestMapping(value = "/wrapped/getPostRequestParams",method = RequestMethod.POST) @ResponseBody //public void getPostRequestParams(@RequestBody String params) { public void getPostRequestParams(HttpServletRequest request) throws Exception{ byte[] bytes = IOUtils.toByteArray(request.getInputStream()); String params = new String(bytes, request.getCharacterEncoding()); log.info("controller-post请求参数:[params={}]", params); }这种方法里,我们在SimpleWrappedFilter里一个实现了WrappedHttpServletRequest类,其构造器自动读取了servletRequest里的输入流,并把数据保存了下来,最后又把数据重新写入servletRequest里,使得cotroller可以再次从request里读取到输入参数。
二、Interceptor
PlatformInterceptor.java
import java.util.Map; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import com.alibaba.fastjson.JSONObject; import com.test.test.config.ConfigProperties; import com.test.test.constants.IMsgEnum; import com.test.test.filter.ThreadCache; import com.test.test.resp.BaseResp; import com.test.test.security.SignCheck; import com.test.test.utils.DESTools; import com.test.test.utils.JsonUtil; import com.test.test.utils.LogUtils; import com.test.test.utils.NetworkUtil; import com.test.test.utils.ReflectUtil; /** * ClassName: PlatformInterceptor date: 2015年12月30日 下午2:13:24 Description: 拦截器 * * @author xiaozhan * @version * @since JDK 1.8 */ @Component public class PlatformInterceptor implements HandlerInterceptor { private static final Log logger = LogFactory.getLog(PlatformInterceptor.class); @Autowired private SignCheck signCheck; @Autowired private ConfigProperties configProperties; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info(LogUtils.getRequestLog(request)); // 获取自定义注解 String allowOrigin = null; String servletPath = request.getServletPath(); boolean isDeprecated = false; BaseResp baseResp = new BaseResp(); ServletOutputStream out = response.getOutputStream(); if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Deprecated deprecated = handlerMethod.getMethodAnnotation(Deprecated.class); if (deprecated != null) { isDeprecated = true; } } String method = request.getMethod(); if (!method.equals(RequestMethod.POST.name())) { baseResp.getMsg().setRstcode(IMsgEnum.PARAM_REQUEST_METHOD_FALSE.getMsgCode()); baseResp.getMsg().setRsttext(IMsgEnum.PARAM_REQUEST_METHOD_FALSE.getMsgText()); logger.info("----- " + IMsgEnum.PARAM_REQUEST_METHOD_FALSE.getMsgText() + " -----"); out.write(JSONObject.toJSONString(baseResp).getBytes("UTF-8")); return false; } String clientIp = NetworkUtil.getIpAddress(request); logger.info("------ client Ip is ---》" + clientIp); // 判断是否是ip白名单 if (!signCheck.checkIpAddress(clientIp)) { baseResp.getMsg().setRstcode(IMsgEnum.PARAM_IP_ADDRESS_FALSE.getMsgCode()); baseResp.getMsg().setRsttext(IMsgEnum.PARAM_IP_ADDRESS_FALSE.getMsgText()); logger.info("----- " + IMsgEnum.PARAM_IP_ADDRESS_FALSE.getMsgText() + " -----"); out.write(JSONObject.toJSONString(baseResp).getBytes("UTF-8")); return false; } // 验证签名 String params = ThreadCache.getPostRequestParams(); logger.info("interceptor-post请求参数:[params={}]" + params); Map<String, Object> map = ReflectUtil.getDecodeParamMap(params); String sign = (String) map.get("sign"); if (map.containsKey("sign")) { map.remove("sign"); } // 签名校验 if (!SignCheck.checkSign(map, sign, configProperties.getPrivateKey())) { baseResp.getMsg().setRstcode(IMsgEnum.PARAM_SIGN_FALSE.getMsgCode()); baseResp.getMsg().setRsttext(IMsgEnum.PARAM_SIGN_FALSE.getMsgText()); logger.info("----- " + IMsgEnum.PARAM_SIGN_FALSE.getMsgText() + " -----"); out.write(JSONObject.toJSONString(baseResp).getBytes("UTF-8")); return false; } try { if (isDeprecated) { logger.error(LogUtils.getCommLog(String.format("该接口已停止使用,%s", servletPath))); } } catch (Exception e) { baseResp.getMsg().setRstcode(IMsgEnum.PARAMETER_INVALID.getMsgCode()); baseResp.getMsg().setRsttext(IMsgEnum.PARAMETER_INVALID.getMsgText()); logger.info("----- " + IMsgEnum.PARAMETER_INVALID.getMsgText() + " -----"); out.write(JSONObject.toJSONString(baseResp).getBytes("UTF-8")); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }我这里用到拦截器的作用是对回调接口进行统一的验证签名。 回调接口都需要验证签名,而且签名规则一样,所以想拿到拦截器处理。 如果签名或者ip地址不符合条件,直接就返回了。而具体的接口只要专注业务处理,不需要验证签名了。 分享一下用的工具类:ip和签名校验:
####signCheck.java
import java.util.Arrays; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSONObject; import com.test.test.config.ConfigProperties; import com.test.test.interceptor.PlatformInterceptor; import com.test.test.utils.Signature; import org.springframework.stereotype.Service; /** * 类说明 :校验签名和ip */ @Service(value="signCheck") public class SignCheck{ private static final Log logger = LogFactory.getLog(PlatformInterceptor.class); @Autowired private ConfigProperties configProperties; @Bean public SignCheck getSignCheck(){ return new SignCheck(); } /** * 校验签名 * @Description: * @author: * @throws IllegalAccessException * @time:2018年3月1日 上午10:38:09 */ public static Boolean checkSign(Map params, String sign,String privateKey) throws IllegalAccessException { if(StringUtils.isBlank(sign)) { logger.info("*********************sign is null*********************"); return false; }else { String signAfter = Signature.getSign(params,privateKey); System.out.println("sign:"+sign); System.out.println("signAfter:"+signAfter); if(!sign.equals(signAfter)) { logger.info("*********************sign is not equal signAfter*********************"); return false; } } return true; } /** * 校验ip * @Description: * @author: * @throws IllegalAccessException * @time:2018年3月1日 上午10:38:09 */ public Boolean checkIpAddress(String ip) throws IllegalAccessException { String ipWhite = configProperties.getRequestUrl(); System.out.println(ipWhite); String[] ipWhiteArray = ipWhite.split(","); List<String> ipWhiteList = Arrays.asList(ipWhiteArray); if(!ipWhiteList.contains(ip)) { logger.info("*********************ip is not in ipWhiteList*********************"); return false; } return true; } }NetworkUtil.java
获取ip地址工具类
import java.io.IOException; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; /** * 常用获取客户端信息的工具 * */ public final class NetworkUtil { /** * Logger for this class */ private static Logger logger = Logger.getLogger(NetworkUtil.class); /** * 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址; * * @param request * @return * @throws IOException */ public final static String getIpAddress(HttpServletRequest request) throws IOException { // 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址 String ip = request.getHeader("X-Forwarded-For"); if (logger.isInfoEnabled()) { logger.info("getIpAddress(HttpServletRequest) - X-Forwarded-For - String ip=" + ip); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); if (logger.isInfoEnabled()) { logger.info("getIpAddress(HttpServletRequest) - Proxy-Client-IP - String ip=" + ip); } } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); if (logger.isInfoEnabled()) { logger.info("getIpAddress(HttpServletRequest) - WL-Proxy-Client-IP - String ip=" + ip); } } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); if (logger.isInfoEnabled()) { logger.info("getIpAddress(HttpServletRequest) - HTTP_CLIENT_IP - String ip=" + ip); } } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); if (logger.isInfoEnabled()) { logger.info("getIpAddress(HttpServletRequest) - HTTP_X_FORWARDED_FOR - String ip=" + ip); } } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); if (logger.isInfoEnabled()) { logger.info("getIpAddress(HttpServletRequest) - getRemoteAddr - String ip=" + ip); } } } else if (ip.length() > 15) { String[] ips = ip.split(","); for (int index = 0; index < ips.length; index++) { String strIp = (String) ips[index]; if (!("unknown".equalsIgnoreCase(strIp))) { ip = strIp; break; } } } return ip; } }Signature.java
加密解密工具类
import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import com.alibaba.fastjson.JSONObject; public class Signature { /** * 签名算法 * @param o 要参与签名的数据对象 * @return 签名 * @throws IllegalAccessException *规则:签名,将筛选的参数按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推,形成key=value& * skey的字符串MD5加密; (必填) */ public static String getSign(Object o) throws IllegalAccessException { ArrayList<String> list = new ArrayList<String>(); Class cls = o.getClass(); Field[] fields = cls.getDeclaredFields(); for (Field f : fields) { f.setAccessible(true); if (f.get(o) != null && f.get(o) != "") { list.add(f.getName() + "=" + f.get(o) + "&"); } } int size = list.size(); String [] arrayToSort = list.toArray(new String[size]); Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); StringBuilder sb = new StringBuilder(); for(int i = 0; i < size; i ++) { sb.append(arrayToSort[i]); } String result = sb.toString(); System.out.println(result); result = MD5Util.MD5Encode(result).toUpperCase(); System.out.println(result); return result; } public static String getSign(Map<String,Object> map,String privateKey){ ArrayList<String> list = new ArrayList<String>(); for(Map.Entry<String,Object> entry:map.entrySet()){ if(entry.getValue()!=""){ list.add(entry.getKey() + "=" + entry.getValue() + "&"); } } int size = list.size(); String [] arrayToSort = list.toArray(new String[size]); Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); StringBuilder sb = new StringBuilder(); for(int i = 0; i < size; i ++) { sb.append(arrayToSort[i]); } sb.append(privateKey); String result = sb.toString(); System.out.println(result); result = MD5Util.MD5Encode(result).toUpperCase(); return result; } public static void main(String[] args) { /* * */ Map<String,Object> map=new HashMap(); map.put("uuid","PC0000000056"); String result= getSign(map,"aaaaa!aaa"); System.out.println(result); map.put("sign", result);//sign DESTools desTools = new DESTools(); String s = JSONObject.toJSONString(map); System.out.println(s); String param= desTools.getEncString(s); String str= HttpUtil.doPost("http://localhost:8111/test/test", param); System.out.println(str); } }DESTools.java
import java.security.Key; import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import org.apache.commons.codec.binary.Base64; public class DESTools { public static DESTools instance; public static DESTools getInstace() { if(instance == null) { instance = new DESTools(); } return instance; } Key key; /** * ** */ private static byte[] BOSS_SECRET_KEY = { 0x0b, 0x13, (byte) 0xe7, (byte) 0xb2, 0x51, 0x0d, 0x75, (byte) 0xc2, 0x4e, (byte) 0xdd, (byte) 0x4b, (byte) 0x51, 0x24, 0x36, (byte) 0xa8, (byte) 0x28, 0x0b, 0x13, (byte) 0xe2, (byte) 0xb2, 0x31, 0x0d, 0x75, (byte) 0xc1 }; public DESTools() { setKey(BOSS_SECRET_KEY); } /** * 根据参数生成KEY */ public void setKey(byte[] strKey) { try { DESKeySpec dks = new DESKeySpec(BOSS_SECRET_KEY); SecretKeyFactory keyFactory; keyFactory = SecretKeyFactory.getInstance("DES"); this.key = keyFactory.generateSecret(dks); } catch (Exception e) { throw new RuntimeException( "Error initializing DESTOOLS class. Cause: " + e); } } /** * 加密String明文输入,String密文输出 */ public String getEncString(String strMing) { byte[] byteMi = null; byte[] byteMing = null; String strMi = ""; Base64 base64en = new Base64(); try { byteMing = strMing.getBytes("UTF8"); byteMi = this.getEncCode(byteMing); strMi = base64en.encodeAsString(byteMi); } catch (Exception e) { throw new RuntimeException( "Error initializing DESTOOLS class. Cause: " + e); } finally { base64en = null; byteMing = null; byteMi = null; } return strMi; } /** * 解密 以String密文输入,String明文输出 * @param strMi * @return */ public String getDesString(String strMi) { Base64 base64De = new Base64(); byte[] byteMing = null; byte[] byteMi = null; String strMing = ""; try { byteMi = base64De.decode(strMi); byteMing = this.getDesCode(byteMi); strMing = new String(byteMing, "UTF8"); } catch (Exception e) { throw new RuntimeException("Error initializing DESTOOLS class. Cause: " + e); } finally { base64De = null; byteMing = null; byteMi = null; } return strMing; } /** * 加密以byte[]明文输入,byte[]密文输出 * @param byteS * @return */ private byte[] getEncCode(byte[] byteS) { byte[] byteFina = null; Cipher cipher; try { cipher = Cipher.getInstance("DES"); cipher.init(Cipher.ENCRYPT_MODE, key); byteFina = cipher.doFinal(byteS); } catch (Exception e) { throw new RuntimeException( "Error initializing DESTOOLS class. Cause: " + e); } finally { cipher = null; } return byteFina; } /** * 解密以byte[]密文输入,以byte[]明文输出 * @param byteD * @return */ private byte[] getDesCode(byte[] byteD) { Cipher cipher; byte[] byteFina = null; try { cipher = Cipher.getInstance("DES"); cipher.init(Cipher.DECRYPT_MODE, key); byteFina = cipher.doFinal(byteD); } catch (Exception e) { throw new RuntimeException( "Error initializing DESTOOLS class. Cause: " + e); } finally { cipher = null; } return byteFina; } }ConfigProperties.java
获取yml文件的值
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; @Configuration @Component public class ConfigProperties { @Value("${test.test.privateKey}") private String privateKey; @Value("${test.test.requestUrl}") private String requestUrl; public String getPrivateKey() { return privateKey; } public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } public String getRequestUrl() { return requestUrl; } public void setRequestUrl(String requestUrl) { this.requestUrl = requestUrl; } }yml文件如图
springboot 2.x 过滤器和拦截器 放行某个路径(静态资源)
import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import xxx.xxxx.interceptor.PlatformInterceptor; @EnableWebMvc @Configuration @Component public class WebMvcConfig implements WebMvcConfigurer { @Autowired PlatformInterceptor passportInterceptor; /** * 处理拦截器 拦截路径(暂时无效) */ @Override public void addInterceptors(InterceptorRegistry registry) { // 多个拦截器组成一个拦截器链 registry.addInterceptor(passportInterceptor).addPathPatterns("/**") .excludePathPatterns("notify","/static/**"); WebMvcConfigurer.super.addInterceptors(registry); } /** * 处理过滤器 静态资源放行 */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); WebMvcConfigurer.super.addResourceHandlers(registry); } /** * 利用fastjson替换掉jackson,且解决中文乱码问题 * @param converters */ @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); //处理中文乱码问题 List<MediaType> fastMediaTypes = new ArrayList<>(); fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); fastConverter.setSupportedMediaTypes(fastMediaTypes); fastConverter.setFastJsonConfig(fastJsonConfig); converters.add(fastConverter); WebMvcConfigurer.super.configureMessageConverters(converters); } }参考: https://www.freesion.com/article/1790501924/
https://blog.csdn.net/liuerchong/article/details/110467532
https://www.cnblogs.com/endstart/p/6196807.html