복붙노트

[SPRING] Spring에서 RequestRejectedException을 인터셉트하는 방법은 무엇입니까?

SPRING

Spring에서 RequestRejectedException을 인터셉트하는 방법은 무엇입니까?

Tomcat 로그에 RequestRejectedException 항목이 대량으로 표시됩니다 (아래 붙여 넣기 샘플). 몇 달 전에 마이너 버전 업그레이드 (스프링 보안 4.2.4, IIRC)를 한 후 로그 파일에 나타나기 시작 했으므로 이것이 기본적으로 활성화 된 스프링의 새로운 보안 기능입니다. 비슷한 문제가 여기에보고되지만 내 질문에 컨트롤러에서 이러한 예외를 가로채는 방법이 구체적으로 포함됩니다. 이 문제에 대한 문서화 된 Spring 보안 버그가 있습니다 (RequestRejectedException 처리 방법 제공). 그러나 Spring 5.1까지는이 문제에 대한 수정을 목표로하지 않습니다.

이러한 예외가 발생하는 이유를 이해하고 있으며이 보안 기능을 사용하지 않으려합니다.

이 기능을 제어하려면 다음과 같이하십시오.

요청 된 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)

나는 2 일 동안이 중 3,200 개가 넘는 것을보고 있으며, 신속하게 내 catalina.out 로그 파일에 가장 큰 하나의 기여자가되어 다른 합법적 인 문제를 볼 수 없게됩니다. 본질적으로,이 새로운 스프링 보안 기능은 내장형 DoS (Denial-of-Service)의 한 형태이며, 4 월 이후로 몇 시간을 낭비했습니다. 중요한 기능이 아니라 단순히 기본 구현이 완전히 엉망이라고 말하는 것이 아니라 개발자와 시스템 관리자 모두에게 제어권을 얻는 방법을 찾고 싶습니다.

나는 Spring에서 IOException을 포함한 많은 다른 Exception 타입을 인터셉트하기 위해 커스텀 Error Controller를 사용한다. 그러나 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을 가로 챌 수있는 방법은 무엇입니까?

해결법

  1. ==============================

    1.StrictHttpFirewall의 하위 클래스를 구현하여 요청 정보를 콘솔에 기록하고 억제 된 스택 추적으로 새로운 예외를 throw합니다. 이것은 부분적으로 내 문제를 해결합니다 (적어도 지금은 나쁜 요청을 볼 수 있음).

    StrictHttpFirewall의 하위 클래스를 구현하여 요청 정보를 콘솔에 기록하고 억제 된 스택 추적으로 새로운 예외를 throw합니다. 이것은 부분적으로 내 문제를 해결합니다 (적어도 지금은 나쁜 요청을 볼 수 있음).

    스택 추적없이 거부 된 요청을보고 싶다면 원하는 답변을 찾아야합니다.

    컨트롤러에서 이러한 예외를 처리하려면 완성 된 (그러나 약간 더 복잡한) 솔루션에 대한 대답을 참조하십시오.

    LoggingHttpFirewall.java

    이 클래스는 StrictHttpFirewall을 확장하여 RequestRejectedException을 catch하고 요청 및 억제 된 스택 추적의 메타 데이터로 새로운 예외를 throw합니다.

    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 =를 찾고 이러한 요청을 허용하는 새 버전 (다른 대답에 포함됨)을 신속하게 배포했습니다. 통과해야하는 다른 요청이있을 수도 있으며, 이제는이를 감지 할 수있는 방법이 있습니다.

  2. ==============================

    2.HttpFirewall과 StrictHttpFirewall은 몇 가지 디자인 오류 (아래 코드에 설명되어 있음)를 포함하고 있지만, Spring Security의 One True Firewall을 탈출하고 요청 속성을 통해 HttpFirewall 정보를이 플래그 된 요청을 전달할 수있는 HandlerInterceptor로 터널링하는 것은 거의 불가능합니다 원래의 비즈니스 논리를 희생시키지 않으면 서 진정한 (영구적 인) 방화벽으로 전환 할 수 있습니다. 여기에 문서화 된 방법은 HttpFirewall 인터페이스의 간단한 계약을 따르고 있으므로 나머지는 Spring Framework 및 Java Servlet API의 핵심 요소이므로 상당히 미래 보장적이어야합니다.

    HttpFirewall과 StrictHttpFirewall은 몇 가지 디자인 오류 (아래 코드에 설명되어 있음)를 포함하고 있지만, Spring Security의 One True Firewall을 탈출하고 요청 속성을 통해 HttpFirewall 정보를이 플래그 된 요청을 전달할 수있는 HandlerInterceptor로 터널링하는 것은 거의 불가능합니다 원래의 비즈니스 논리를 희생시키지 않으면 서 진정한 (영구적 인) 방화벽으로 전환 할 수 있습니다. 여기에 문서화 된 방법은 HttpFirewall 인터페이스의 간단한 계약을 따르고 있으므로 나머지는 Spring Framework 및 Java Servlet API의 핵심 요소이므로 상당히 미래 보장적이어야합니다.

    이것은 본질적으로 이전의 대답보다 더 복잡하지만 더 완전한 대안입니다. 이 대답에서는 특정 로깅 수준에서 거부 된 요청을 가로 채고 기록하는 StrictHttpFirewall의 새 하위 클래스를 구현했지만 HTTP 요청에 특성을 추가하여 다운 스트림 필터 (또는 컨트롤러)가 처리 할 수 ​​있도록 플래그를 지정합니다. 또한 AnnotatingHttpFirewall은 inspect () 메소드를 제공하여 서브 클래스가 요청을 차단하기위한 사용자 정의 규칙을 추가 할 수 있도록합니다.

    이 솔루션은 (1) 스프링 보안과 (2) 스프링 프레임 워크 (코어)의 두 부분으로 나뉩니다. 첫 번째 부분에서이 문제를 일으킨 부분이므로이 부분을 연결하는 방법을 보여줍니다.

    참고로, 이는 Spring 4.3.17 및 Spring Security 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 Security 필터가 실행 된 후 (즉, 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

    Web Config에서 방화벽 인터셉터를 레지스트리에 추가하십시오.

    @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이 항상 호출되도록합니다. 이것은 NoHandlerFoundException보다 우선 순위가 높은 RequestRejectedByFirewallException을 제공합니다.

    필요한 이유 :

    여기서 언급했듯이, DispatcherServlet에서 NoHandlerFoundException이 발생하면 (요청 된 URL에 해당 매핑이없는 경우) 위의 방화벽에서 생성 된 예외를 처리 할 방법이 없습니다 (preHandle ()을 호출하기 전에 NoHandlerFoundException이 throw됩니다) 그 요청은 404보기로 넘어 가게 될 것입니다 (제 경우에는 원하는 동작이 아닙니다. "URI가있는 HTTP 요청에 대한 매핑이 없습니다 ..."메시지가 나타납니다). 특수 헤더에 대한 검사를 noHandlerFound 메소드로 이동하여이를 수정할 수 있습니다. 안타깝게도 새로운 Dispatcher 서블릿을 처음부터 작성하지 않고는이 작업을 수행 할 수있는 방법이 없으며 전체 Spring Framework를 폐기 할 수도 있습니다. DispatcherServlet은 protected, private 및 final 메서드와 속성에 액세스 할 수 없으므로 확장 할 수 없습니다 (getter 또는 setter가 없음). 구현할 수있는 공통 인터페이스가 없으므로 클래스를 래핑하는 것도 불가능합니다. 이 클래스의 기본 매핑은 모든 논리를 우회하는 우아한 방법을 제공합니다.

    중요한주의 사항 : 아래의 RequestMapping은 등록 된 모든 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;
        }
    }
    

    결과

    이 두 부분이 모두 작동하면 다음 두 경고가 기록됩니다 (첫 번째는 스프링 보안에 있고 두 번째는 스프링 프레임 워크 (핵심) 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.
    
  3. ==============================

    3.간단한 필터로도 처리 할 수 ​​있으며 404 오류 응답으로 연결됩니다.

    간단한 필터로도 처리 할 수 ​​있으며 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);
            }
        }
    }
    
  4. from https://stackoverflow.com/questions/51788764/how-to-intercept-a-requestrejectedexception-in-spring by cc-by-sa and MIT license