如何在 Spring 中拦截 RequestRejectedException? [英] How to intercept a RequestRejectedException in Spring?

查看:14
本文介绍了如何在 Spring 中拦截 RequestRejectedException?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的 Tomcat 日志中看到大量RequestRejectedException 条目(示例粘贴在下面).这些在几个月前的次要版本升级(Spring Security 4.2.4,IIRC)后开始出现在我的日志文件中,所以这显然是 Spring 中默认启用的新安全功能.一个类似的问题是此处报告,但我的问题具体涉及如何在控制器中拦截这些异常.有针对此问题记录的 Spring Security 错误(提供处理 RequestRejectedException 的方法).但是,直到 Spring 5.1,他们才针对此问题进行修复.

I am seeing a ton of RequestRejectedException entries in my Tomcat log (sample pasted below). These started appearing in my log file after a minor version upgrade (Spring Security 4.2.4, IIRC) a few months ago, so this is clearly a new security feature in Spring that is enabled by default. A similar issue is reported here, but my question involves specifically how to intercept these exceptions in a controller. There is a Spring Security bug documented for this problem (Provide a way to handle RequestRejectedException). However, they aren't targeting a fix for this problem until Spring 5.1.

我了解为什么会抛出这些异常,并且我不想禁用此安全功能.

我想对该功能进行一些控制,以便:

I want to gain some control over this feature so that:

  1. 我知道我没有阻止合法用户访问我的网站.
  2. 我可以看到触发此操作的请求(它们是 SQL 注入攻击吗?)
  3. 我可以调整服务器响应.Spring Security 防火墙将完整的堆栈跟踪转储到 Web 客户端(信息泄露),以及一个 500 Internal Server Error(这是非常不正确的,这应该是一个 400 Bad Request代码>).
  1. I know I'm not blocking legitimate users from my site.
  2. I can see what requests are triggering this (are they SQL Injection attacks?)
  3. I can adjust the server response. The Spring Security firewall dumps a complete stack trace to the web client (information disclosure), along with a 500 Internal Server Error (which is wildly incorrect, this should be a 400 Bad Request).

我想找到一种方法来记录所请求的 URL,同时也禁止专门针对这些异常的堆栈跟踪,因为它们在没有给我任何有用信息的情况下污染了我的日志文件.理想情况下,我想拦截这些异常并在我的应用程序层处理它们,而不是在 Tomcat 日志中报告它们.

I want to find a way to log the URL that was requested, but also suppress the stack trace specifically for these exceptions because they are polluting my log files without giving me any helpful information. Optimally, I'd like to intercept these exceptions and handle them in my application layer instead of reporting them in the Tomcat log at all.

例如,这是每天出现在我的 catalina.out 中的数千个日志条目之一:

For example, this is one of thousands of these log entries that appear every day in my 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)

我在两天内看到了 3,200 多个这样的文件,并且它很快成为我的 catalina.out 日志文件的最大贡献者,以至于它阻止我看到其他合法的问题.从本质上讲,这个新的 Spring Security 功能是一种内置的拒绝服务形式,自 4 月以来它已经浪费了我几个小时的时间.我并不是说它不是一个重要的特性,只是默认的实现是完全拙劣的,我想找到一种方法来控制它,无论是作为开发人员还是作为系统管理员.

I'm seeing over 3,200 of these in a two day period, and it has quickly become the single largest contributor to my catalina.out log file, to the point that it prevents me from seeing other, legitimate problems. Essentially, this new Spring Security feature is a form of built-in Denial-of-Service, and it has wasted hours of my time since April. I am not saying that it is not an important feature, simply that the default implementation is completely botched, and I want to find a way gain some control over it, both as a developer and as a systems administrator.

我使用自定义错误控制器来拦截 Spring 中的许多其他异常类型(包括 IOException).但是,RequestRejectedException 似乎由于某种原因失败了.

I use a custom Error Controller for intercepting many other Exception types (including IOException) in Spring. However, RequestRejectedException seems to be falling through for some reason.

这是我的 ErrorController.java 的相关部分,用于说明我要完成的任务:

This is the relevant part of my ErrorController.java, to give an idea of what I'm trying to accomplish:

@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:

This error controller works for many exceptions. For example, it successfully intercepted this 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)
...

然而,这并没有拦截RequestRejectedException(如上面第一个日志样本中缺少服务器错误"所示).

However, this is not intercepting RequestRejectedException (as indicated by the lack of "Server Error" in the first log sample above).

如何在错误控制器中拦截RequestRejectedException?

How can I intercept RequestRejectedException in an error controller?

推荐答案

事实证明,虽然 HttpFirewallStrictHttpFirewall 包含几个设计错误(记录在下面的代码中),几乎不可能逃脱 Spring Security 的 One True Firewall 并通过请求属性将 HttpFirewall 信息隧道传输到可以传递这些信息的 HandlerInterceptor将请求标记到真实(持久)防火墙,而不会牺牲最初标记它们的原始业务逻辑.此处记录的方法应该是面向未来的,因为它符合来自 HttpFirewall 接口的简单合同,其余部分只是核心 Spring 框架和 Java Servlet API.

It turns out that although HttpFirewall and StrictHttpFirewall contain several design errors (documented in the code below), it is just barely possible to escape Spring Security's One True Firewall and tunnel the HttpFirewall information via a request attribute to a HandlerInterceptor that can pass these flagged requests to a real (persistent) firewall without sacrificing the original business logic that flagged them in the first place. The method documented here should be fairly future-proof, as it conforms to a simple contract from the HttpFirewall interface, and the rest is simply the core Spring Framework and Java Servlet API.

这本质上是我之前的回答的更复杂但更完整的替代方案.在这个答案中,我实现了一个新的 StrictHttpFirewall 子类,它在特定的日志记录级别拦截和记录被拒绝的请求,但也向 HTTP 请求添加了一个属性,将其标记为下游过滤器(或控制器)处理.此外,这个 AnnotatingHttpFirewall 提供了一个 inspect() 方法,允许子类添加自定义规则来阻止请求.

This is essentially a more complicated but more complete alternative to my earlier answer. In this answer, I implemented a new subclass of StrictHttpFirewall that intercepts and logs rejected requests at a specific logging level, but also adds an attribute to the HTTP request that flags it for downstream filters (or controllers) to handle. Also, this AnnotatingHttpFirewall provides an inspect() method that allows subclasses to add custom rules for blocking requests.

这个解决方案分为两部分:(1) Spring Security 和 (2) Spring Framework (Core),因为这是导致这个问题的分歧首先,这显示了如何桥接它.

This solution is split into two parts: (1) Spring Security and (2) Spring Framework (Core), because that is the divide that caused this problem in the first place, and this shows how to bridge it.

作为参考,这是在 Spring 4.3.17 和 Spring Security 4.2.6 上测试的.Spring 5.1 发布时可能会有重大变化.

For reference, this is tested on Spring 4.3.17 and Spring Security 4.2.6. There may be significant changes when Spring 5.1 is released.

第 1 部分:Spring 安全性

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

This is the half of the solution that performs the logging and flagging within Spring Security.

AnnotatingHttpFirewall.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.
    }
}

<小时>

WebSecurityConfig.java

WebSecurityConfig中,将HTTP防火墙设置为AnnotatingHttpFirewall.

In WebSecurityConfig, set the HTTP firewall to the AnnotatingHttpFirewall.

@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 部分:Spring 框架

可以想象,该解决方案的第二部分可以实现为 ServletFilterHandlerInterceptor.我将采用 HandlerInterceptor 的路径,因为它似乎提供了最大的灵活性并且直接在 Spring 框架内工作.

The second part of this solution could conceivably be implemented as a ServletFilter or HandlerInterceptor. I'm going the path of a HandlerInterceptor because it seems to give the most flexibility and works directly within the Spring Framework.

RequestBlockedException.java

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

This custom exception can be handled by an Error Controller. This may be extended to add any request headers, parameters or properties available from the raw request (even the full request itself) that may be pertinent to application business logic (e.g., a persistent firewall).

/**
 * 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 Security 过滤器运行之后调用这个拦截器(即,在 AnnotatingHttpFirewall 标记了应该被拒绝的请求之后.这个拦截器在请求上检测这些标志(属性)并引发自定义异常我们的错误控制器可以处理.

This interceptor is invoked after the Spring Security filters have run (i.e., after AnnotatingHttpFirewall has flagged requests that should be rejected. This interceptor detects those flags (attributes) on the request and raises a custom exception that our Error Controller can handle.

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 添加到注册表中.

In WebConfig, add the FirewallInterceptor to the registry.

@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;
    }
}

<小时>

ErrorController.java

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

This specifically handles the custom exception above, and produces a clean error page for the client while logging all of the relevant information and invoking any special business logic for a custom application firewall.

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";
    }
}

<小时>

FirewallController.java

带有默认映射的控制器,该映射会引发 NoHandlerFoundException.这绕过了 DispatcherServlet.noHandlerFound,允许该方法总是找到一个映射,以便 FirewallInterceptor.preHandle 总是调用.这使 RequestRejectedByFirewallException 优先于 NoHandlerFoundException.

A controller with a default mapping that throws a NoHandlerFoundException. This circumvents the chicken-and-egg strategy in DispatcherServlet.noHandlerFound, allowing that method to always find a mapping so that FirewallInterceptor.preHandle is always invoked. This gives RequestRejectedByFirewallException priority over NoHandlerFoundException.

为什么这是必要的:

此处所述,当 DispatcherServlet 抛出 NoHandlerFoundException 时code>(即当请求的URL没有对应的映射时),无法处理上述防火墙产生的异常(NoHandlerFoundException在调用preHandle()),因此这些请求将落入您的 404 视图(在我的情况下这不是所需的行为 - 您会看到很多不为带有 URI 的 HTTP 请求找到映射..."消息).这可以通过将特殊标头的检查移动到 noHandlerFound 方法中来解决.不幸的是,没有从头开始编写一个新的 Dispatcher Servlet 就没有办法做到这一点,然后你还不如把整个 Spring Framework 扔掉.由于protected、private 和final 方法的混合,以及它的属性不可访问(没有getter 或setter)这一事实,不可能扩展DispatcherServlet.封装类也是不可能的,因为没有可以实现的通用接口.此类中的默认映射提供了一种绕过所有这些逻辑的优雅方式.

As mentioned here, when a NoHandlerFoundException is thrown from DispatcherServlet (i.e., when a requested URL has no corresponding mapping), there is no way to handle the exceptions generated from the above firewall (NoHandlerFoundException is thrown prior to invoking preHandle()), so those requests will fall through to your 404 view (which is not the desired behavior in my case - you will see a lot of "No mapping found for HTTP request with URI..." messages). This could be fixed by moving the check for the special header into the noHandlerFound method. Unfortunately, there is no way to do this without writing a new Dispatcher Servlet from scratch, and then you may as well throw out the entire Spring Framework. It is impossible to extend DispatcherServlet due to the mix of protected, private and final methods, and the fact that its properties are inaccessible (no getters or setters). It is also impossible to wrap the class because there is no common interface that can be implemented. The default mapping in this class provides an elegant way to circumvent all of that logic.

重要警告:下面的 RequestMapping 将阻止解析静态资源,因为它优先于所有已注册的 ResourceHandler.我仍在为此寻找解决方法,但一种可能性可能是尝试使用 此答案中建议的一种处理静态资源的方法.

Important caveat: The RequestMapping below will prevent resolution of static resources because it takes precedence over all registered ResourceHandlers. I am still looking for a workaround for this, but one possibility might be to try one of the methods for handling static resources suggested in 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(核心)ErrorController).现在,您可以完全控制日志记录,以及可以根据需要进行调整的可扩展应用防火墙.

When both parts of this are working, you'll see the following two warnings logged (the first one is in Spring Security, the second one is the Spring Framework (Core) ErrorController). Now you have full control over logging, and an extensible application firewall that you can adjust however you need.

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.

这篇关于如何在 Spring 中拦截 RequestRejectedException?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆