代码之家  ›  专栏  ›  技术社区  ›  Parker

如何在Spring中拦截RequestRejectedException?

  •  15
  • Parker  · 技术社区  · 6 年前

    我看到了 一吨 属于 RequestRejectedException Tomcat日志中的条目(下面粘贴了示例)。在几个月前的一次小版本升级(Spring Security 4.2.4,IIRC)之后,它们就开始出现在我的日志文件中,所以这显然是Spring中默认启用的一个新的安全特性。类似的问题是 reported here ,但我的问题具体涉及到如何在控制器中截获这些异常。有一个针对这个问题的Spring安全漏洞的文档( Provide a way to handle RequestRejectedException ). 然而,直到Spring5.1,他们才打算解决这个问题。

    我理解 why these exceptions are being thrown ,我不想 disable this security feature .

    我想控制这个功能,以便:

    1. 我知道我不会阻止合法用户访问我的网站。
    2. 我可以看到是什么请求触发了这一点(它们是SQL注入攻击吗?)
    3. 我可以调整服务器响应。Spring安全防火墙将一个完整的堆栈跟踪转储到web客户端(信息公开),同时 500 Internal Server Error (这是非常不正确的,这应该是 400 Bad Request ).

    我想找到一种方法来记录所请求的URL,但也要针对这些异常抑制堆栈跟踪,因为它们会污染我的日志文件,而不会给我任何有用的信息。最理想的情况是,我希望拦截这些异常并在我的应用程序层中处理它们,而不是在Tomcat日志中报告它们。

    例如,这是每天出现在我的 catalina.out :

    Aug 10, 2018 2:01:36 PM org.apache.catalina.core.StandardWrapperValve invoke
    SEVERE: Servlet.service() for servlet [dispatcher] in context with path [] threw exception
    org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
            at org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlacklistedUrls(StrictHttpFirewall.java:265)
            at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:245)
            at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:193)
            at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
            at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
            at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
            at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
            at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
            at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
            at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
            at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
            at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
            at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
            at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:486)
            at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
            at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
            at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
            at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
            at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
            at java.lang.Thread.run(Thread.java:748)
    

    在两天的时间里,我看到了3200多个,它很快成为我的 卡塔琳娜 日志文件,以至于我看不到其他合法的问题。从本质上讲,这个新的Spring安全特性是一种内置拒绝服务的形式,自4月份以来,它已经浪费了我好几个小时的时间。我并不是说它不是一个重要的特性,只是说默认实现完全是拙劣的,我想找到一种方法来获得对它的一些控制,无论是作为开发人员还是作为系统管理员。

    我使用自定义错误控制器来截取许多其他异常类型(包括 IOException )春天。然而, 请求拒绝异常 似乎是因为某种原因而失败的。

    这是我 ErrorController.java ,来说明我要实现的目标:

    @ControllerAdvice
    public final class ErrorController
    {
        /**
         * Logger.
         */
        private static final Logger LOGGER = Logger.getLogger(ErrorController.class.getName());
    
        /**
         * Generates an Error page by intercepting exceptions generated from HttpFirewall.
         *
         * @param ex A RequestRejectedException exception.
         * @return The tile definition name for the page.
         */
        @ExceptionHandler(RequestRejectedException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public String handleRequestRejectedException(final HttpServletRequest request, final RequestRejectedException ex)
        {
            if (LOGGER.isLoggable(Level.INFO))
            {
                LOGGER.log(Level.INFO, "Request Rejected", ex);
            }
    
            LOGGER.log(Level.WARNING, "Rejected request for [" + request.getRequestURL().toString() + "]. Reason: " + ex.getMessage());
            return "errorPage";
        }
    
        /**
         * Generates a Server Error page.
         *
         * @param ex An exception.
         * @return The tile definition name for the page.
         */
        @ExceptionHandler(Exception.class)
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public String handleException(final Exception ex)
        {
            if (LOGGER.isLoggable(Level.SEVERE))
            {
                LOGGER.log(Level.SEVERE, "Server Error", ex);
            }
    
            return "errorPage";
        }
    }
    

    此错误控制器适用于许多异常。例如,它成功地截获了这个 IllegalStateException :

    Aug 05, 2018 7:50:30 AM com.mycompany.spring.controller.ErrorController handleException
    SEVERE: Server Error
    java.lang.IllegalStateException: Cannot create a session after the response has been committed
            at org.apache.catalina.connector.Request.doGetSession(Request.java:2999)
    ...
    

    但是,这不是拦截 请求拒绝异常 (如上面第一个日志示例中缺少“服务器错误”所示)。

    我如何拦截 请求拒绝异常 在错误控制器中?

    4 回复  |  直到 6 年前
        1
  •  26
  •   lanwen    5 年前

    它也可以由一个简单的过滤器处理,这将导致404错误响应

    @Component
    @Slf4j
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class LogAndSuppressRequestRejectedExceptionFilter extends GenericFilterBean {
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            try {
                chain.doFilter(req, res);
            } catch (RequestRejectedException e) {
                HttpServletRequest request = (HttpServletRequest) req;
                HttpServletResponse response = (HttpServletResponse) res;
    
                log
                    .warn(
                            "request_rejected: remote={}, user_agent={}, request_url={}",
                            request.getRemoteHost(),  
                            request.getHeader(HttpHeaders.USER_AGENT),
                            request.getRequestURL(), 
                            e
                    );
    
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        }
    }
    
        2
  •  10
  •   Parker    6 年前

    我实现了 StrictHttpFirewall 它将请求信息记录到控制台,并抛出一个新的异常和一个被抑制的堆栈跟踪。这部分解决了我的问题(至少我现在可以看到错误的请求)。

    如果您只想看到不带堆栈跟踪的被拒绝请求,这就是您要寻找的答案。

    如果要在控制器中处理这些异常,请参阅 accepted answer 一个完整(但稍微复杂一点)的解决方案。


    日志HttpFirewall.java

    这个类扩展 StrictHttpFirewall 抓住 RequestRejectedException 并抛出一个新的异常,其中包含来自请求的元数据和被抑制的堆栈跟踪。

    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.security.web.firewall.FirewalledRequest;
    import org.springframework.security.web.firewall.RequestRejectedException;
    import org.springframework.security.web.firewall.StrictHttpFirewall;
    
    /**
     * Overrides the StrictHttpFirewall to log some useful information about blocked requests.
     */
    public final class LoggingHttpFirewall extends StrictHttpFirewall
    {
        /**
         * Logger.
         */
        private static final Logger LOGGER = Logger.getLogger(LoggingHttpFirewall.class.getName());
    
        /**
         * Default constructor.
         */
        public LoggingHttpFirewall()
        {
            super();
            return;
        }
    
        /**
         * Provides the request object which will be passed through the filter chain.
         *
         * @returns A FirewalledRequest (required by the HttpFirewall interface) which
         *          inconveniently breaks the general contract of ServletFilter because
         *          we can't upcast this to an HttpServletRequest. This prevents us
         *          from re-wrapping this using an HttpServletRequestWrapper.
         * @throws RequestRejectedException if the request should be rejected immediately.
         */
        @Override
        public FirewalledRequest getFirewalledRequest(final HttpServletRequest request) throws RequestRejectedException
        {
            try
            {
                return super.getFirewalledRequest(request);
            } catch (RequestRejectedException ex) {
                if (LOGGER.isLoggable(Level.WARNING))
                {
                    LOGGER.log(Level.WARNING, "Intercepted RequestBlockedException: Remote Host: " + request.getRemoteHost() + " User Agent: " + request.getHeader("User-Agent") + " Request URL: " + request.getRequestURL().toString());
                }
    
                // Wrap in a new RequestRejectedException with request metadata and a shallower stack trace.
                throw new RequestRejectedException(ex.getMessage() + ".\n Remote Host: " + request.getRemoteHost() + "\n User Agent: " + request.getHeader("User-Agent") + "\n Request URL: " + request.getRequestURL().toString())
                {
                    private static final long serialVersionUID = 1L;
    
                    @Override
                    public synchronized Throwable fillInStackTrace()
                    {
                        return this; // suppress the stack trace.
                    }
                };
            }
        }
    
        /**
         * Provides the response which will be passed through the filter chain.
         * This method isn't extensible because the request may already be committed.
         * Furthermore, this is only invoked for requests that were not blocked, so we can't
         * control the status or response for blocked requests here.
         *
         * @param response The original HttpServletResponse.
         * @return the original response or a replacement/wrapper.
         */
        @Override
        public HttpServletResponse getFirewalledResponse(final HttpServletResponse response)
        {
            // Note: The FirewalledResponse class is not accessible outside the package.
            return super.getFirewalledResponse(response);
        }
    }
    

    WebSecurity配置.java

    WebSecurityConfig ,将HTTP防火墙设置为 LoggingHttpFirewall .

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter
    {
        /**
         * Default constructor.
         */
        public WebSecurityConfig()
        {
            super();
            return;
        }
    
        @Override
        public final void configure(final WebSecurity web) throws Exception
        {
            super.configure(web);
            web.httpFirewall(new LoggingHttpFirewall()); // Set the custom firewall.
            return;
        }
    }
    

    结果

    在将此解决方案部署到生产环境之后,我很快发现 严格的防火墙 阻止谷歌对我的站点进行索引!

    Aug 13, 2018 1:48:56 PM com.mycompany.spring.security.AnnotatingHttpFirewall getFirewalledRequest
    WARNING: Intercepted RequestBlockedException: Remote Host: 66.249.64.223 User Agent: Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) Request URL: https://www.mycompany.com/10.1601/tx.3784;jsessionid=692804549F9AB55F45DBD0AFE2A97FFD
    

    我一发现这个,就迅速部署了一个新版本(包括 my other answer )寻找 ;jsessionid= 允许这些请求通过。很可能还有其他请求也应该通过,现在我有了一种方法来检测这些请求。

        3
  •  6
  •   Parker    5 年前

    结果是尽管 HttpFirewall StrictHttpFirewall 包含几个设计错误(记录在下面的代码中),很难逃脱Spring Security的 一个真正的防火墙 在隧道里 http防火墙 通过请求属性向 HandlerInterceptor 可以将这些标记的请求传递给 真实的 (持久)防火墙,而不牺牲最初标记它们的原始业务逻辑。这里记录的方法应该是相当未来的证明,因为它符合 http防火墙 接口,其余的只是核心Spring框架和Java Servlet API。

    这基本上是一个更复杂但更完整的替代方案 my earlier answer . 在这个答案中,我实现了 严格的防火墙 它在特定的日志级别截取并记录被拒绝的请求,同时还向HTTP请求添加一个属性,将其标记为要由下游筛选器(或控制器)处理。还有,这个 AnnotatingHttpFirewall 提供 inspect() 方法,该方法允许子类为阻止请求添加自定义规则。

    这个解决方案分为两部分:(1) 春季安全 和(2) 弹簧骨架(芯) ,因为这是导致这个问题的第一个原因,这说明了如何弥合它。

    作为参考,在弹簧4.3.17和弹簧安全4.2.6上进行了测试。当Spring5.1发布时,可能会有显著的变化。


    第一部分:弹簧安全

    这是在Spring Security中执行日志记录和标记的解决方案的一半。


    注释httpfirewall.java

    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.security.web.firewall.FirewalledRequest;
    import org.springframework.security.web.firewall.RequestRejectedException;
    import org.springframework.security.web.firewall.StrictHttpFirewall;
    
    /**
     * Overrides the StrictHttpFirewall to log some useful information about blocked requests.
     */
    public class AnnotatingHttpFirewall extends StrictHttpFirewall
    {
        /**
         * The name of the HTTP header representing a request that has been rejected by this firewall.
         */
        public static final String HTTP_HEADER_REQUEST_REJECTED_FLAG = "X-HttpFirewall-RequestRejectedFlag";
    
        /**
         * The name of the HTTP header representing the reason a request has been rejected by this firewall.
         */
        public static final String HTTP_HEADER_REQUEST_REJECTED_REASON = "X-HttpFirewall-RequestRejectedReason";
    
        /**
         * Logger.
         */
        private static final Logger LOGGER = Logger.getLogger(AnnotatingHttpFirewall.class.getName());
    
        /**
         * Default constructor.
         */
        public AnnotatingHttpFirewall()
        {
            super();
            return;
        }
    
        /**
         * Provides the request object which will be passed through the filter chain.
         *
         * @param request The original HttpServletRequest.
         * @returns A FirewalledRequest (required by the HttpFirewall interface) which
         *          inconveniently breaks the general contract of ServletFilter because
         *          we can't upcast this to an HttpServletRequest. This prevents us
         *          from re-wrapping this using an HttpServletRequestWrapper.
         */
        @Override
        public FirewalledRequest getFirewalledRequest(final HttpServletRequest request)
        {
            try
            {
                this.inspect(request); // Perform any additional checks that the naive "StrictHttpFirewall" misses.
                return super.getFirewalledRequest(request);
            } catch (RequestRejectedException ex) {
                final String requestUrl = request.getRequestURL().toString();
    
                // Override some of the default behavior because some requests are
                // legitimate.
                if (requestUrl.contains(";jsessionid="))
                {
                    // Do not block non-cookie serialized sessions. Google's crawler does this often.
                } else {
                    // Log anything that is blocked so we can find these in the catalina.out log.
                    // This will give us any information we need to make
                    // adjustments to these special cases and see potentially
                    // malicious activity.
                    if (LOGGER.isLoggable(Level.WARNING))
                    {
                        LOGGER.log(Level.WARNING, "Intercepted RequestBlockedException: Remote Host: " + request.getRemoteHost() + " User Agent: " + request.getHeader("User-Agent") + " Request URL: " + request.getRequestURL().toString());
                    }
    
                    // Mark this request as rejected.
                    request.setAttribute(HTTP_HEADER_REQUEST_REJECTED, Boolean.TRUE);
                    request.setAttribute(HTTP_HEADER_REQUEST_REJECTED_REASON, ex.getMessage());
                }
    
                // Suppress the RequestBlockedException and pass the request through
                // with the additional attribute.
                return new FirewalledRequest(request)
                {
                    @Override
                    public void reset()
                    {
                        return;
                    }
                };
            }
        }
    
        /**
         * Provides the response which will be passed through the filter chain.
         * This method isn't extensible because the request may already be committed.
         * Furthermore, this is only invoked for requests that were not blocked, so we can't
         * control the status or response for blocked requests here.
         *
         * @param response The original HttpServletResponse.
         * @return the original response or a replacement/wrapper.
         */
        @Override
        public HttpServletResponse getFirewalledResponse(final HttpServletResponse response)
        {
            // Note: The FirewalledResponse class is not accessible outside the package.
            return super.getFirewalledResponse(response);
        }
    
        /**
         * Perform any custom checks on the request.
         * This method may be overridden by a subclass in order to supplement or replace these tests.
         *
         * @param request The original HttpServletRequest.
         * @throws RequestRejectedException if the request should be rejected immediately.
         */
        public void inspect(final HttpServletRequest request) throws RequestRejectedException
        {
            final String requestUri = request.getRequestURI(); // path without parameters
    //        final String requestUrl = request.getRequestURL().toString(); // full path with parameters
    
            if (requestUri.endsWith("/wp-login.php"))
            {
                throw new RequestRejectedException("The request was rejected because it is a vulnerability scan.");
            }
    
            if (requestUri.endsWith(".php"))
            {
                throw new RequestRejectedException("The request was rejected because it is a likely vulnerability scan.");
            }
    
            return; // The request passed all custom tests.
        }
    }
    

    WebSecurity配置.java

    WebSecurityConfig ,将HTTP防火墙设置为 注释httpfirewall .

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter
    {
        /**
         * Default constructor.
         */
        public WebSecurityConfig()
        {
            super();
            return;
        }
    
        @Override
        public final void configure(final WebSecurity web) throws Exception
        {
            super.configure(web);
            web.httpFirewall(new AnnotatingHttpFirewall()); // Set the custom firewall.
            return;
        }
    }
    

    第2部分:弹簧框架

    这个解决方案的第二部分可以想象为 ServletFilter 拦截器 . 我要走一条 拦截器 因为它似乎提供了最大的灵活性,并且直接在Spring框架内工作。


    RequestBlockedException.java

    此自定义异常可由错误控制器处理。这可以扩展为添加原始请求(甚至完整请求本身)中可用的、可能与应用程序业务逻辑(例如,持久防火墙)相关的任何请求头、参数或属性。

    /**
     * A custom exception for situations where a request is blocked or rejected.
     */
    public class RequestBlockedException extends RuntimeException
    {
        private static final long serialVersionUID = 1L;
    
        /**
         * The requested URL.
         */
        private String requestUrl;
    
        /**
         * The remote address of the client making the request.
         */
        private String remoteAddress;
    
        /**
         * A message or reason for blocking the request.
         */
        private String reason;
    
        /**
         * The user agent supplied by the client the request.
         */
        private String userAgent;
    
        /**
         * Creates a new Request Blocked Exception.
         *
         * @param reqUrl The requested URL.
         * @param remoteAddr The remote address of the client making the request.
         * @param userAgent The user agent supplied by the client making the request.
         * @param message A message or reason for blocking the request.
         */
        public RequestBlockedException(final String reqUrl, final String remoteAddr, final String userAgent, final String message)
        {
            this.requestUrl = reqUrl;
            this.remoteAddress = remoteAddr;
            this.userAgent = userAgent;
            this.reason = message;
            return;
        }
    
        /**
         * Gets the requested URL.
         *
         * @return A URL.
         */
        public String getRequestUrl()
        {
            return this.requestUrl;
        }
    
        /**
         * Gets the remote address of the client making the request.
         *
         * @return A remote address.
         */
        public String getRemoteAddress()
        {
            return this.remoteAddress;
        }
    
        /**
         * Gets the user agent supplied by the client making the request.
         *
         * @return  A user agent string.
         */
        public String getUserAgent()
        {
            return this.userAgent;
        }
    
        /**
         * Gets the reason for blocking the request.
         *
         * @return  A message or reason for blocking the request.
         */
        public String getReason()
        {
            return this.reason;
        }
    }
    

    FirewallInterceptor.java软件

    这个拦截器在Spring安全过滤器运行之后调用(即 注释httpfirewall 已标记应拒绝的请求。这个拦截器检测请求上的那些标志(属性),并引发一个我们的错误控制器可以处理的自定义异常。

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    /**
     * Intercepts requests that were flagged as rejected by the firewall.
     */
    public final class FirewallInterceptor implements HandlerInterceptor
    {
        /**
         * Default constructor.
         */
        public FirewallInterceptor()
        {
            return;
        }
    
        @Override
        public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception
        {
            if (Boolean.TRUE.equals(request.getAttribute(AnnotatingHttpFirewall.HTTP_HEADER_REQUEST_REJECTED)))
            {
                // Throw a custom exception that can be handled by a custom error controller.
                final String reason = (String) request.getAttribute(AnnotatingHttpFirewall.HTTP_HEADER_REQUEST_REJECTED_REASON);
                throw new RequestRejectedByFirewallException(request.getRequestURL().toString(), request.getRemoteAddr(), request.getHeader(HttpHeaders.USER_AGENT), reason);
            }
    
            return true; // Allow the request to proceed normally.
        }
    
        @Override
        public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception
        {
            return;
        }
    
        @Override
        public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception
        {
            return;
        }
    }
    

    WebConfig.java

    WebConfig ,添加 FirewallInterceptor 去登记处。

    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter
    {
        /**
         * Among your other methods in this class, make sure you register
         * your Interceptor.
         */
        @Override
        public void addInterceptors(final InterceptorRegistry registry)
        {
            // Register firewall interceptor for all URLs in webapp.
            registry.addInterceptor(new FirewallInterceptor()).addPathPatterns("/**");
            return;
        }
    }
    

    错误控制器.java

    这特别处理上面的自定义异常,并在记录所有相关信息和调用自定义应用程序防火墙的任何特殊业务逻辑时为客户端生成一个干净的错误页。

    import java.io.IOException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import org.springframework.web.servlet.NoHandlerFoundException;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    import RequestBlockedException;
    
    @ControllerAdvice
    public final class ErrorController
    {
        /**
         * Logger.
         */
        private static final Logger LOGGER = Logger.getLogger(ErrorController.class.getName());
    
        /**
         * Generates an Error page by intercepting exceptions generated from AnnotatingHttpFirewall.
         *
         * @param request The original HTTP request.
         * @param ex A RequestBlockedException exception.
         * @return The tile definition name for the page.
         */
        @ExceptionHandler(RequestBlockedException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public String handleRequestBlockedException(final RequestBlockedException ex)
        {
            if (LOGGER.isLoggable(Level.WARNING))
            {
                LOGGER.log(Level.WARNING, "Rejected request from " + ex.getRemoteAddress() + " for [" + ex.getRequestUrl() + "]. Reason: " + ex.getReason());
            }
    
            // Note: Perform any additional business logic or logging here.
    
            return "errorPage"; // Returns a nice error page with the specified status code.
        }
    
        /**
         * Generates a Page Not Found page.
         *
         * @param ex A NoHandlerFound exception.
         * @return The tile definition name for the page.
         */
        @ExceptionHandler(NoHandlerFoundException.class)
        @ResponseStatus(HttpStatus.NOT_FOUND)
        public String handleException(final NoHandlerFoundException ex)
        {
            return "notFoundPage";
        }
    }
    

    防火墙控制器.java

    具有默认映射的控制器 NoHandlerFoundException . 这就规避了 DispatcherServlet.noHandlerFound ,允许该方法 总是 找到一个映射以便 FirewallInterceptor.preHandle 总是被调用。这给了 RequestRejectedByFirewallException 优先于 NoHandlerFoundException异常 .

    为什么这是必要的:

    如前所述 here ,当 NoHandlerFoundException异常 DispatcherServlet (即,当请求的URL没有对应的映射时),无法处理从上述防火墙生成的异常( NoHandlerFoundException异常 在调用之前引发 preHandle() ),因此这些请求将进入404视图(在我的例子中,这不是期望的行为-您将看到许多“找不到带有URI的HTTP请求的映射…”消息)。这可以通过将特殊头的检查移动到 noHandlerFound 方法。不幸的是,如果不从头开始编写一个新的DispatcherServlet,就无法做到这一点,那么您也可以抛出整个Spring框架。扩展是不可能的 调度员Servlet 由于混合了受保护的、私有的和final方法,并且其属性不可访问(没有getter或setter)。也不可能包装类,因为没有可以实现的公共接口。这个类中的默认映射提供了一种绕过所有逻辑的优雅方法。

    重要警告 :下面的请求映射将阻止解析静态资源,因为它优先于所有注册的资源处理程序。我仍在寻找解决方法,但有一种可能是尝试中建议的处理静态资源的方法之一 this answer .

    import org.springframework.http.HttpHeaders;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.NoHandlerFoundException;
    
    @Controller
    public final class FirewallController
    {
        /**
         * The name of the model attribute (or request parameter for advertisement click tracking) that contains the request URL.
         */
        protected static final String REQUEST_URL = "requestUrl";
    
        /**
         * The name of the model attribute that contains the request method.
         */
        protected static final String REQUEST_METHOD = "requestMethod";
    
        /**
         * The name of the model attribute that contains all HTTP headers.
         */
        protected static final String REQUEST_HEADERS = "requestHeaders";
    
        /**
         * Default constructor.
         */
        public FirewallController()
        {
            return;
        }
    
        /**
         * Populates the request URL model attribute from the HTTP request.
         *
         * @param request The HTTP request.
         * @return The request URL.
         */
        @ModelAttribute(REQUEST_URL)
        public final String getRequestURL(final HttpServletRequest request)
        {
            return request.getRequestURL().toString();
        }
    
        /**
         * Populates the request method from the HTTP request.
         *
         * @param request The HTTP request.
         * @return The request method (GET, POST, HEAD, etc.).
         */
        @ModelAttribute(REQUEST_METHOD)
        public final String getRequestMethod(final HttpServletRequest request)
        {
            return request.getMethod();
        }
    
        /**
         * Gets all headers from the HTTP request.
         *
         * @param request The HTTP request.
         * @return The request headers.
         */
        @ModelAttribute(REQUEST_HEADERS)
        public final HttpHeaders getRequestHeaders(final HttpServletRequest request)
        {
            return FirewallController.headers(request);
        }
    
        /**
         * A catch-all default mapping that throws a NoHandlerFoundException.
         * This will be intercepted by the ErrorController, which allows preHandle to work normally.
         *
         * @param requestMethod The request method.
         * @param requestUrl The request URL.
         * @param requestHeaders The request headers.
         * @throws NoHandlerFoundException every time this method is invoked.
         */
        @RequestMapping(value = "/**") // NOTE: This prevents resolution of static resources. Still looking for a workaround for this.
        public void getNotFoundPage(@ModelAttribute(REQUEST_METHOD) final String requestMethod, @ModelAttribute(REQUEST_URL) final String requestUrl, @ModelAttribute(REQUEST_HEADERS) final HttpHeaders requestHeaders) throws NoHandlerFoundException
        {
            throw new NoHandlerFoundException(requestMethod, requestUrl, requestHeaders);
        }
    
        /**
         * Gets all headers from a HTTP request.
         *
         * @param request The HTTP request.
         * @return The request headers.
         */
        public static HttpHeaders headers(final HttpServletRequest request)
        {
            final HttpHeaders headers = new HttpHeaders();
    
            for (Enumeration<?> names = request.getHeaderNames(); names.hasMoreElements();)
            {
                final String headerName = (String) names.nextElement();
    
                for (Enumeration<?> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
                {
                    headers.add(headerName, (String) headerValues.nextElement());
                }
            }
    
            return headers;
        }
    }
    

    结果

    当这两个部分都工作时,您将看到以下两个警告被记录下来(第一个是Spring Security,第二个是Spring Framework(Core) ErrorController ). 现在您可以完全控制日志记录,还可以根据需要调整可扩展的应用程序防火墙。

    Sep 12, 2018 10:24:37 AM com.mycompany.spring.security.AnnotatingHttpFirewall getFirewalledRequest
    WARNING: Intercepted org.springframework.security.web.firewall.RequestRejectedException: Remote Host: 0:0:0:0:0:0:0:1 User Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0 Request URL: http://localhost:8080/webapp-www-mycompany-com/login.php
    Sep 12, 2018 10:24:37 AM com.mycompany.spring.controller.ErrorController handleException
    WARNING: Rejected request from 0:0:0:0:0:0:0:1 for [http://localhost:8080/webapp-www-mycompany-com/login.php]. Reason: The request was rejected because it is a likely vulnerability scan.
    
        4
  •  1
  •   Ahmed Sayed    5 年前

    另一种处理方法是使用 Spring AOP . 我们可以在 FilterChainProxy.doFilter() 方法捕获HttpFirewall抛出的任何RequestRejectedException,并将其转换为400个错误请求

    @Aspect
    @Component
    public class FilterChainProxyAdvice {
    
        @Around("execution(public void org.springframework.security.web.FilterChainProxy.doFilter(..))")
        public void handleRequestRejectedException (ProceedingJoinPoint pjp) throws Throwable {
            try {
                pjp.proceed();
            } catch (RequestRejectedException exception) {
                HttpServletResponse response = (HttpServletResponse) pjp.getArgs()[1]);
                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
            }
        }
    }
    
        5
  •  1
  •   holmis83    4 年前

    一个很简单的方法是 web.xml文件 ;在该文件中指定错误页:

    <error-page>
      <exception-type>org.springframework.security.web.firewall.RequestRejectedException</exception-type>
      <location>/request-rejected</location>
    </error-page>
    

    对于指定的路径(位置),在 @Controller -注释类:

    @RequestMapping(value = "/request-rejected")
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public @ResponseBody String handleRequestRejected(
            @RequestAttribute(RequestDispatcher.ERROR_EXCEPTION) RequestRejectedException ex,
            @RequestAttribute(RequestDispatcher.ERROR_REQUEST_URI) String uri) {
    
        String msg = ex.getMessage();
    
        // optionally log the message and requested URI (slf4j)
        logger.warn("Request with URI [{}] rejected. {}", uri, msg);
    
        return msg;
    }