Как перехватить исключение RequestRejectedException весной?

я вижу тонны of RequestRejectedException записи в моем журнале Tomcat (пример вставлен ниже). Они начали появляться в моем файле журнала после незначительного обновления версии (Spring Security 4.2.4, IIRC) несколько месяцев назад, поэтому это явно новая функция безопасности весной, которая включена по умолчанию. Аналогичная проблема сообщил, что, но мой вопрос касается конкретно, как перехватить эти исключения в контроллере. Для этого есть задокументированная ошибка безопасности Spring проблема (укажите способ обработки RequestRejectedException). Однако, они не нацелены на решение этой проблемы до весны 5.1.

я понимаю почему этих исключений, а я не хочу отключить эту функцию безопасности.

я хочу получить некоторый контроль над этой функцией, чтобы:

  1. я знаю, что не блокирую законных пользователей с моего сайта.
  2. я вижу какие запросы вызывают это (они SQL-инъекциям?)
  3. я могу настроить ответ сервера. Брандмауэр Spring Security сбрасывает полную трассировку стека на веб-клиент (раскрытие информации) вместе с 500 Internal Server Error (что это дико неправильно, это должно быть 400 Bad Request).

я хочу найти способ зарегистрировать запрошенный URL-адрес, но также подавить трассировку стека специально для этих исключений, потому что они загрязняют мои файлы журнала не давая мне никакой полезной информации. Оптимально, я хотел бы перехватить эти исключения и обработать их на уровне приложения, а не сообщать о них в журнале Tomcat.

например, это одна из тысяч этих записей журнала, которые появляются каждый день в 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-это форма встроенного отказа в обслуживании, и она потратила часы моего времени с апреля. Я не говорю, что это не важная функция, просто реализация по умолчанию полностью испорчена, и я хочу найти способ получить некоторый контроль над ней, как разработчик и как системный администратор.

я использую пользовательский контроллер ошибок для перехвата многих других типов исключений (в том числе IOException) весной. Однако,RequestRejectedException кажется, проваливается по какой-то причине.

это соответствующая часть моего 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)
...

однако, это не перехват RequestRejectedException (как указано отсутствием "ошибки сервера" в первом примере журнала выше.)

как я могу перехватывать RequestRejectedException в контроллере ошибка?

3 ответов


я реализовал подкласс StrictHttpFirewall это регистрирует информацию запроса на консоль и выдает новое исключение с подавленной трассировкой стека. Это частично решает мою проблему (по крайней мере, теперь я вижу плохие запросы).

если вы просто хотите, чтобы увидеть отклоненные запросы без трассировки стека, это ответ, который вы ищете.

если вы хотите обрабатывать эти исключения в контроллере, пожалуйста, обратитесь к принято отвечать для полного (но немного более сложное) решение.


LoggingHttpFirewall.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);
    }
}

WebSecurityConfig.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;
    }
}

результаты

после развертывания этого решения в производство я быстро обнаружил, что поведение по умолчанию StrictHttpFirewall блокировал Google от индексации моего сайта!

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

как только я обнаружил это, я быстро развернул новую версию (включенную в мой другой ответ), что ищет ;jsessionid= и разрешает эти запросы. Там вполне могут быть другие запросы, которые должны пройти через ну, а теперь у меня есть способ их обнаружить.


оказывается, что хотя HttpFirewall и StrictHttpFirewall содержат несколько ошибок дизайна (задокументированных в коде ниже), едва ли возможно избежать Spring Security Один Истинный Брандмауэр и туннель HttpFirewall информация через атрибут запрос HandlerInterceptor который может передать эти помеченные запросы в реальные (постоянный) брандмауэр, не жертвуя оригинальной бизнес-логикой, которая отмечала их в первую очередь. Метод, описанный здесь, должен быть довольно будущее доказательство, так как оно соответствует простому контракту от HttpFirewall интерфейс, а остальное просто ядро Spring Framework и Java Servlet API.

это, по сути, более сложная, но более полная альтернатива мой предыдущий ответ. В этом ответе я реализовал новый подкласс StrictHttpFirewall который перехватывает и регистрирует отклоненные запросы на определенном уровне ведения журнала, но также добавляет атрибут к HTTP-запросу, который помечает его для нижестоящих фильтров (или контроллеры) для обработки. Кроме того, это AnnotatingHttpFirewall обеспечивает inspect() метод, который позволяет подклассам добавлять пользовательские правила для блокировки запросов.

это решение разделено на две части: (1) Весна Безопасности и (2) Spring Framework (Ядро), потому что это разрыв, что вызвало эту проблему, в первую очередь, и это показывает, как преодолеть это.

для справки, это испытано на Весне 4.3.17 и безопасности весны 4.2.6. Там могут быть существенные изменения при выпуске Spring 5.1.


Часть 1: Безопасность Весны

это половина решения, которое выполняет ведение журнала и пометку в 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.

@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 или HandlerInterceptor. Я иду по пути HandlerInterceptor потому что он, кажется, дает большую гибкость и работает непосредственно в рамках 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 (т. е. после AnnotatingHttpFirewall помечены запросы, которые должны быть отклонены. Этот перехватчик обнаруживает эти флаги (атрибуты) по запросу и вызывает пользовательское исключение, которое может обрабатывать наш контроллер ошибок.

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

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

FirewallController.java

контроллер с отображением по умолчанию, который выдает NoHandlerFoundException. Это обходит стратегию курицы и яйца в DispatcherServlet.noHandlerFound, учитывая, что метод всегда найти отображение, так что FirewallInterceptor.preHandle всегда вызывается. Это дает RequestRejectedByFirewallException приоритет NoHandlerFoundException.

почему это необходимо:

как уже упоминалось здесь, когда NoHandlerFoundException выдает DispatcherServlet (т. е., когда запрошенный URL-адрес не имеет соответствующего сопоставления), нет способа обработать исключения, созданные из вышеуказанного брандмауэра (NoHandlerFoundException выбрасывается перед вызовом preHandle()), поэтому эти запросы будут попадать в ваше представление 404 (что не является желаемым поведением в моем случае - вы увидите много "нет сопоставления для HTTP-запроса с URI..." сообщения.) Это может быть исправлено путем перемещения проверки для специального заголовка в noHandlerFound метод. К сожалению, нет способа сделать это без написания нового сервлета диспетчера с нуля, а затем вы можете также выбросить всю весеннюю структуру. Невозможно продлить DispatcherServlet из-за сочетания защищенных, частных и конечных методов и того факта, что его свойства недоступны (без геттеров или сеттеров). Также невозможно обернуть класс, потому что нет общего интерфейса это можно осуществить. Сопоставление по умолчанию в этом классе предоставляет элегантный способ обойти всю эту логику.

важный нюанс: приведенное ниже сопоставление запросов предотвратит разрешение статических ресурсов, поскольку оно имеет приоритет над всеми зарегистрированными ResourceHandlers. Я все еще ищу обходной путь для этого, но одной из возможностей может быть попробовать один из методов обработки статических ресурсов, предложенных в этот ответ.

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.

Он также может быть обработан простым фильтром, который приведет к ответу на ошибку 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);
        }
    }
}