java通过拦截器校验进行参数校验,在接口调用前进行校验,解决jar包中class文件无法修改,但不想修改源码的场景
1.定义参数拦截接口ParamsCheckInter.java
package cn.togeek.config; public interface ParamsCheckInter<T> { T doCheck(Object... args); }
2.定义参数校验实现逻辑 ScContractDecomposeWayInterHandler.java
package cn.togeek.config; import cn.togeek.entity.ScContractDecomposeWay; import cn.togeek.exception.ClientError; import org.springframework.util.Assert; import org.springframework.util.StringUtils; public class ScContractDecomposeWayInterHandler implements ParamsCheckInter<Boolean> { @Override public Boolean doCheck(Object... args) { ScContractDecomposeWay way = (ScContractDecomposeWay) args[0]; String info = way.getInfo(); Assert.isTrue(StringUtils.hasLength(info),()->{throw new ClientError("分解方式不能为空");}); return way.isInfoOk(); } }
3.重写过滤器,避免request对象读取流后,controller层参数失效,原因是流只能读取一次
3.1 request包装类RequestWrapper.java
package cn.togeek.config; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; /** * @author wangyan * @description 获取请求参数并处理 * @date 2019/7/18 18:38 */ public class RequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RequestWrapper(HttpServletRequest request) { super(request); String sessionStream = getBodyString(request); body = sessionStream.getBytes(Charset.forName("UTF-8")); } public String getBodyString(){ return new String(body,Charset.forName("UTF-8")); } /** * 获取请求Body * * @param request * @return */ public String getBodyString(final ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = cloneInputStream(request.getInputStream()); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } /** * Description: 复制输入流</br> * * @param inputStream * @return</br> */ public InputStream cloneInputStream(ServletInputStream inputStream) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; try { while ((len = inputStream.read(buffer)) > -1) { byteArrayOutputStream.write(buffer, 0, len); } byteArrayOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); return byteArrayInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }
3.2.重新对request对象进行封装,放入到过滤器链中 RequestParamsFilter.java
package cn.togeek.config; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; /** * * @Author TCL * @Date 2021/4/26 15:30 * @Version 1.0 */ @WebFilter(filterName="bodyReaderFilter",urlPatterns="/*") public class RequestParamsFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // do nothing } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper=null; if(request instanceof HttpServletRequest) { requestWrapper=new RequestWrapper((HttpServletRequest)request); } if(requestWrapper==null) { chain.doFilter(request, response); }else { chain.doFilter(requestWrapper, response); } } }
3.3.拦截器 ParamsCheckIntercepter.java
package cn.togeek.config; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import cn.togeek.util.EmpSpringContextUtil; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; import org.springframework.core.MethodParameter; import org.springframework.core.env.Environment; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * 参数过滤拦截器 */ @Slf4j public class ParamsCheckIntercepter implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(handler instanceof HandlerMethod){ HandlerMethod handlerMethod = (HandlerMethod) handler; String method = request.getMethod(); Environment environment = EmpSpringContextUtil.getBean(Environment.class); String requestUrl = request.getRequestURI(); List<RequestInfos> list = IntercepterList.getList(); Map<String, String[]> originRequestMap = request.getParameterMap(); Map<String,Object> requestMap = new HashMap<>(); for (String key : originRequestMap.keySet()) { String[] values = originRequestMap.get(key); requestMap.put(key,values[0]); } String param; String contextPath = environment.getProperty("server.servlet.context-path"); if(StringUtils.isNotEmpty(contextPath)) { requestUrl = requestUrl.replaceAll(contextPath,""); } for(RequestInfos requestInfo : list) { String url = requestInfo.getUrl(); String infoMethod = requestInfo.getMethod(); String fullClassName = requestInfo.getFullClassName(); if(infoMethod.equalsIgnoreCase(method) && url.equals(requestUrl)) { log.info("ParamsCheckIntercepter#preHandle contextPath:{},requestInfo:{}",contextPath,JSON.toJSONString(requestInfo)); try { String body = new RequestWrapper(request).getBodyString(); MethodParameter[] methodParameters = handlerMethod.getMethodParameters(); int length = methodParameters.length; Object args[] = new Object[length]; if(StringUtils.isNotBlank(body)){ LinkedHashMap<String,String> map = JSONObject.parseObject(body, LinkedHashMap.class); requestMap.putAll(map); } JSONObject info = new JSONObject(requestMap); param = JSON.toJSONString(requestMap); System.out.println("拦截器参数:"+param); for(int i = 0; i < methodParameters.length; i++) { MethodParameter methodParameter = methodParameters[i]; try { args[i] = info.toJavaObject(methodParameter.getParameterType()); } catch(Exception e) { e.printStackTrace(); } } if(StringUtils.isNotEmpty(fullClassName)) { Class<?> aClass = Class.forName(fullClassName); Object bean = aClass.newInstance(); if(bean instanceof ParamsCheckInter) { ParamsCheckInter<Boolean> interBean = (ParamsCheckInter<Boolean>) bean; return interBean.doCheck(args); } } return true; } catch (Exception e) { response.setContentType("text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.write(e.getMessage()); writer.flush(); writer.close(); return false; } } } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
4.过滤器和拦截器注册AdminWebConfig.java
package cn.togeek.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class AdminWebConfig implements WebMvcConfigurer { /** * 配置拦截器 * @param registry 相当于拦截器的注册中心 */ @Override public void addInterceptors(InterceptorRegistry registry) { // 下面这句代码相当于添加一个拦截器 添加的拦截器就是我们刚刚创建的 registry.addInterceptor(new ParamsCheckIntercepter()) // addPathPatterns()配置我们要拦截哪些路径 addPathPatterns("/**")表示拦截所有请求,包括我们的静态资源 .addPathPatterns() // excludePathPatterns()表示我们要放行哪些(表示不用经过拦截器) // excludePathPatterns("/","/login")表示放行“/”与“/login”请求 // 如果有静态资源的时候可以在这个地方放行 .excludePathPatterns("/","/login"); } @Bean public FilterRegistrationBean<RequestParamsFilter> Filters() { FilterRegistrationBean<RequestParamsFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new RequestParamsFilter()); registrationBean.addUrlPatterns("/*"); return registrationBean; } }
5.辅助类RequestInfos.java 对配置进行封装
package cn.togeek.config; import lombok.Data; @Data public class RequestInfos { //请求待拦截的url,不带项目名server.servlet.context-path部分 private String url; //待拦截的方法 private String method; //拦截器实现,需继承ParamsCheckInter private String fullClassName; }
6.application.yml配置文件中配置
config:
intercepters:
list:
- {url: /sc/contract, method: PUT}
- {url: /sc/contract/decomposeway, method: PUT, fullClassName: cn.togeek.config.ScContractDecomposeWayInterHandler}
7.配置类 IntercepterList.java
package cn.togeek.config; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "config.intercepters") public class IntercepterList { private static List<RequestInfos> list; public static List<RequestInfos> getList() { return list; } public void setList(List<RequestInfos> list) { this.list = list; } }