spring boot 配置拦截器过滤器记录请求参数,以及处理swagger无法redirect问题

  • 用于记录前端发送请求的body参数和query参数,当报错无法将json转为java对象的时候,通过查看日志能快速定位问题。

创建ParamsRequestWrapper类

  • HttpServletRequest的输入流只能读取一次不能重复读取,如果直接在拦截器中读取request的body参数,会导致controller中无法获取的错误,所有需要增加HttpServletRequest类,用于将HttpServletRequest中的body参数存储,方便多次读取。
/**
 * @author klan
 **/
public class ParamsRequestWrapper extends HttpServletRequestWrapper {
    private static final Logger LOGGER = LoggerFactory.getLogger(ParamsRequestWrapper.class);
    /**
     * 存储body数据
     */
    private final byte[] body;

    public ParamsRequestWrapper(HttpServletRequest request) {
        super(request);
        // 将body数据存储起来
        String bodyStr = getBodyString(request);
        body = bodyStr.getBytes(Charset.defaultCharset());
    }

    /**
     * Save the body data.
     *
     * @param request The request.
     * @return The body.
     */
    public String getBodyString(final ServletRequest request) {
        try {
            return inputStreamToString(request.getInputStream());
        } catch (IOException e) {
            LOGGER.error("Error information:", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Get body.
     *
     * @return The body.
     */
    public String getBodyString() {
        final InputStream inputStream = new ByteArrayInputStream(body);
        return inputStreamToString(inputStream);
    }

    /**
     * 将inputStream里的数据读取出来并转换成字符串.
     *
     * @param inputStream The input stream.
     * @return String.
     */
    private String inputStreamToString(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(inputStream, Charset.defaultCharset()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            LOGGER.error("Error information:", e);
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

    /**
     * The get reader.
     *
     * @return The buffered reader.
     */
    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    /**
     * 获取input stream,解决ServletInputStream只能读取一次的问题.
     *
     * @return The servlet input stream.
     */
    @Override
    public ServletInputStream getInputStream() {
        // 每次获取都取保存在body常量中的数据.
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public int read() {
                return inputStream.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }

}

增加过滤器Filter

  • 在过滤器中将HttpServletRequest包装成为ParamsRequestWrapper,方便多次读取。
/**
 * The core filter.
 *
 * @author klan
 */
@Component
public class CoreFilter implements Filter {
    private static final Logger LOGGER = LoggerFactory.getLogger(ParameterInterceptor.class);

    /**
     * Filter初始化.
     *
     * @param filterConfig The filter config.
     */
    @Override
    public void init(FilterConfig filterConfig) {
        LOGGER.info("CoreFilter init...");
    }

    /**
     * 实现过滤功能.
     *
     * @param request  The request.
     * @param response The response.
     * @param chain    The chain.
     * @throws IOException      The io exception.
     * @throws ServletException The servlet exception.
     */
    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        // 将HttpServletRequest包装成wrapper对象,后续可重复读取.
        ServletRequest requestWrapper = new ParamsRequestWrapper((HttpServletRequest) request);
        chain.doFilter(requestWrapper, response);
    }

    /**
     * 用于Filter 销毁前,完成某些资源的回收.
     */
    @Override
    public void destroy() {
        LOGGER.info("CoreFilter destroy...");
    }
}

增加拦截器Interceptor

  • 增加拦截器用于记录日志,在拦截器中获取到的HttpServletRequest已经是包装过之后的ParamsRequestWrapper
/**
 * The params interceptor.
 *
 * @author klan
 */
public class ParameterInterceptor implements HandlerInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ParameterInterceptor.class);
    private static final String LOCALHOST = "127.0.0.1";
    private static final String LOCALHOST_REMOTE = "0:0:0:0:0:0:0:1";
    private static final String UNKNOWN = "unknown";
    private static final String X_FORWARDED_FOR = "x-forwarded-for";
    private static final String PROXY_CLIENT_IP = "Proxy-Client-IP";
    private static final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";
    private static final String HTTP_CLIENT_IP = "HTTP_CLIENT_IP";
    private static final String HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR";
    private static final String SPLIT_WORD = ",";

    /**
     * 在业务处理器处理请求之前被调用.
     *
     * @param request  The request.
     * @param response The response.
     * @param handler  The handler.
     * @return false表示流程中断.
     */
    @Override
    public boolean preHandle(@NonNull HttpServletRequest request,
                             @NonNull HttpServletResponse response,
                             @NonNull Object handler) {
        String bodyParam = new ParamsRequestWrapper(request).getBodyString();
        String requestURI = request.getRequestURI();
        String requestParameter = JSON.toJSONString(request.getParameterMap());
        String methodType = request.getMethod();
        String ipAddress = getIpAddress(request);
        String remoteAddress = request.getRemoteAddr();
        LOGGER.info("[ParameterInterceptor]->[preHandle],ipAddress:{},remoteAddress:{},uri:{},methodType:{},bodyParam:{},requestParam:{}",
                ipAddress, remoteAddress, requestURI, methodType, bodyParam, requestParameter);
        return true;
    }

    /**
     * 后处理回调方法,实现处理器的后处理(但在渲染视图之前).
     *
     * @param request      The request.
     * @param response     The response.
     * @param handler      The handler.
     * @param modelAndView The model and view.
     */
    @Override
    public void postHandle(@NonNull HttpServletRequest request,
                           @NonNull HttpServletResponse response,
                           @NonNull Object handler, ModelAndView modelAndView) {
        LOGGER.info("[ParameterInterceptor]->[postHandle]");
    }

    /**
     * 整个请求处理完毕回调方法,即在视图渲染完毕时回调整个请求处理完毕回调方法.
     *
     * @param request  The request.
     * @param response The response.
     * @param handler  The handler.
     * @param ex       The exception.
     */
    @Override
    public void afterCompletion(@NonNull HttpServletRequest request,
                                @NonNull HttpServletResponse response,
                                @NonNull Object handler, Exception ex) {
        LOGGER.info("[ParameterInterceptor]->[afterCompletion]");
    }

    /**
     * Get real request ip address.
     *
     * @param request The request.
     * @return The ip address.
     */
    private String getIpAddress(HttpServletRequest request) {
        // 只有在通过了HTTP代理或者负载均衡服务器时才会添加该项.
        String ip = request.getHeader(X_FORWARDED_FOR);
        if (isIPAddressNotEmpty(ip)) {
            // 用apache http做代理时一般会加上Proxy-Client-IP请求头.
            ip = request.getHeader(PROXY_CLIENT_IP);
        }
        if (isIPAddressNotEmpty(ip)) {
            // 用apache http做代理时web logic插件加上的头.
            ip = request.getHeader(WL_PROXY_CLIENT_IP);
        }
        if (isIPAddressNotEmpty(ip)) {
            // 是代理服务器发送的HTTP头.
            ip = request.getHeader(HTTP_CLIENT_IP);
        }
        if (isIPAddressNotEmpty(ip)) {
            // 简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP.
            ip = request.getHeader(HTTP_X_FORWARDED_FOR);
        }
        if (isIPAddressNotEmpty(ip)) {
            // 未代理直接获取remoteAddr.
            ip = request.getRemoteAddr();
        }
        // localhost情况.
        if (LOCALHOST_REMOTE.equals(ip)) {
            ip = LOCALHOST;
        }
        // 真实ip为第一个非unknown的有效IP字符串.
        if (ip.split(SPLIT_WORD).length > 1) {
            ip = ip.split(SPLIT_WORD)[0];
        }
        return ip;
    }

    /**
     * 判断获取的ip不为空.
     *
     * @param ip The ip address.
     * @return if ip address is empty return false.
     */
    private boolean isIPAddressNotEmpty(String ip) {
        return (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip));
    }
}

增加配置Configuration

  • 将拦截器注入到容器中
/**
 * The core interceptor config.
 *
 * @author klan
 */
@Configuration
public class CoreInterceptorConfig extends WebMvcConfigurationSupport {
    @Bean
    public ParameterInterceptor parameterInterceptor() {
        // 把自定义拦截器注入到spring容器里.
        return new ParameterInterceptor();
    }

    /**
     * 解决redirect:无法识别问题.
     *
     * @return The internal resource view resolver.
     */
    @Bean
    public InternalResourceViewResolver defaultViewResolver() {
        return new InternalResourceViewResolver();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加拦截的path,忽略swagger页面.
        registry.addInterceptor(parameterInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/");
        super.addInterceptors(registry);
    }

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 忽略swagger,解决无法访问swagger页面问题.
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

遇到的问题

  • 在添加拦截器要拦截的url之后导致项目的swagger无法访问,需要在集成了WebMvcConfigurationSupport的configuration配置类中添加静态资源配置和忽略的path。
@Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加拦截的path,忽略swagger页面.
        registry.addInterceptor(parameterInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/");
        super.addInterceptors(registry);
    }

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 忽略swagger,解决无法访问swagger页面问题.
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
  • swagger页面可以正常访问了但是访问项目根目录会报错:redirect找不到的错误,在网上搜索后发现是缺少试图解析器,所以在配置类中将默认的试图解析器指定为了InternalResourceViewResolver
/**
     * 解决redirect:无法识别问题.
     *
     * @return The internal resource view resolver.
     */
    @Bean
    public InternalResourceViewResolver defaultViewResolver() {
        return new InternalResourceViewResolver();
    }

原因是在WebMvcAutoConfiguration类源码中有一个注解@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}),只有在没有WebMvcConfigurationSupport这个bean的时候才会触发WebMvcAutoConfiguration这个配置类的自动配置,在WebMvcAutoConfiguration中对defaultViewResolver这个bean进行了初始化,默认为InternalResourceViewResolver,而我们的CoreInterceptorConfig配置类继承了WebMvcConfigurationSupport,所以就不会触发WebMvcAutoConfiguration的自动配置,导致项目中没有defaultViewResolver这个bean,无法处理:redirect跳转,如下:

// 在WebMvcAutoConfiguration这个配置类中对defaultViewResolver这个bean进行了如下初始化
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix(this.mvcProperties.getView().getPrefix());
    resolver.setSuffix(this.mvcProperties.getView().getSuffix());
    return resolver;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容