简体   繁体   中英

HttpServletResponse .sendError returns empty response body when missing CSRF token

I have CSRF enabled in the config, and when I try to make a POST request to an authenticated endpoint without a CSRF token in header or cookie (the very first request), Spring returns an error response with status code but no response body (header CONTENT-LENGTH is 0). I have this problem in one of my projects, but I tried to reproduce the behavior with a simpler setup.

HelloController.java

@RestController
public class HelloController {

  @PostMapping("/hello/")
  public String hello() {
    return "Hello World";
  }
}

SecurityConfig.java

@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  private final CustomAccessDeniedHandler customAccessDeniedHandler;

  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
      .anyRequest().authenticated();

    http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
  }
}

CustomAccessDeniedHandler.java

@Component
@Slf4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
  @Override public void handle(HttpServletRequest request, HttpServletResponse response,
                               AccessDeniedException accessDeniedException) throws IOException, ServletException {
    String errorMessage = accessDeniedException.getMessage();
    log.error("Access Denied - {}", errorMessage);
    response.sendError(HttpStatus.FORBIDDEN.value(), errorMessage);
  }
}

I'm aware that with this setup, CSRF is enabled by default. I also customized the AccessDeniedHandler because I want Spring to spit out more specific error message when it's working as intended. When I use curl -i -X POST http://localhost:8080/hello/ , Spring returns HTTP/1.1 403 with empty response. However, if I remove the line http.authorizeRequests().anyRequest().authenticated(); in SecurityConfig and start the same request above, there's an error message like normal behavior.

{
  "timestamp":"2021-07-10T23:06:28.172+00:00",
  "status":403,
  "error":"Forbidden",
  "message":"Could not verify the provided CSRF token because your session was not found.",
  "path":"/hello/"
}

I have searched for similar questions but no one has answered why response.sendError() doesn't work when .anyRequest().authenticated() is present with the condition of missing CSRF token. The workaround seems to be using response.setStatus() and response.getWriter().write() to build the error message from scratch. Is it an expected behavior or am I missing something?

Update debug log

2021-07-10 17:52:23.933 DEBUG 24250 --- [nio-8080-exec-1] org.apache.tomcat.util.http.Parameters   : Set query string encoding to UTF-8
2021-07-10 17:52:23.939 DEBUG 24250 --- [nio-8080-exec-1] o.a.c.authenticator.AuthenticatorBase    : Security checking request POST /hello/
2021-07-10 17:52:23.939 DEBUG 24250 --- [nio-8080-exec-1] org.apache.catalina.realm.RealmBase      :   No applicable constraints defined
2021-07-10 17:52:23.944 DEBUG 24250 --- [nio-8080-exec-1] o.a.c.a.jaspic.AuthConfigFactoryImpl     : Loading persistent provider registrations from [/tmp/tomcat.8080.144327633803357340/conf/jaspic-providers.xml]
2021-07-10 17:52:23.945 DEBUG 24250 --- [nio-8080-exec-1] o.a.c.authenticator.AuthenticatorBase    : Not subject to any constraint
2021-07-10 17:52:23.949  INFO 24250 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-07-10 17:52:23.949  INFO 24250 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-07-10 17:52:23.949 DEBUG 24250 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected StandardServletMultipartResolver
2021-07-10 17:52:23.950 DEBUG 24250 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected AcceptHeaderLocaleResolver
2021-07-10 17:52:23.950 DEBUG 24250 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected FixedThemeResolver
2021-07-10 17:52:23.951 DEBUG 24250 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@5acdab24
2021-07-10 17:52:23.951 DEBUG 24250 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.servlet.support.SessionFlashMapManager@589cd2b7
2021-07-10 17:52:23.952 DEBUG 24250 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2021-07-10 17:52:23.952  INFO 24250 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 3 ms
2021-07-10 17:52:23.967 DEBUG 24250 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing POST /hello/
2021-07-10 17:52:23.974 DEBUG 24250 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2021-07-10 17:52:23.978 DEBUG 24250 --- [nio-8080-exec-1] org.apache.tomcat.util.http.Parameters   : Set encoding to UTF-8
2021-07-10 17:52:23.993 DEBUG 24250 --- [nio-8080-exec-1] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost:8080/hello/
2021-07-10 17:52:23.994 ERROR 24250 --- [nio-8080-exec-1] i.l.s.CustomAccessDeniedHandler          : Access Denied - Could not verify the provided CSRF token because your session was not found.
2021-07-10 17:52:23.997 DEBUG 24250 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2021-07-10 17:52:23.999 DEBUG 24250 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2021-07-10 17:52:24.000 DEBUG 24250 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2021-07-10 17:52:24.000 DEBUG 24250 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost]           : Processing ErrorPage[errorCode=0, location=/error]
2021-07-10 17:52:24.005 DEBUG 24250 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing POST /error
2021-07-10 17:52:24.005 DEBUG 24250 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2021-07-10 17:52:24.008 DEBUG 24250 --- [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2021-07-10 17:52:24.018 DEBUG 24250 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Failed to authorize filter invocation [POST /error] with attributes [authenticated]
2021-07-10 17:52:24.021 DEBUG 24250 --- [nio-8080-exec-1] o.s.s.w.a.Http403ForbiddenEntryPoint     : Pre-authenticated entry point called. Rejecting access
2021-07-10 17:52:24.021 DEBUG 24250 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2021-07-10 17:52:24.021 DEBUG 24250 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2021-07-10 17:52:24.021 DEBUG 24250 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2021-07-10 17:52:24.022 DEBUG 24250 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    :  Disabling the response for further output
2021-07-10 17:52:24.027 DEBUG 24250 --- [nio-8080-exec-1] o.a.coyote.http11.Http11InputBuffer      : Before fill(): parsingHeader: [true], parsingRequestLine: [true], parsingRequestLinePhase: [0], parsingRequestLineStart: [0], byteBuffer.position(): [0], byteBuffer.limit(): [0], end: [85]
2021-07-10 17:52:24.028 DEBUG 24250 --- [nio-8080-exec-1] o.a.tomcat.util.net.SocketWrapperBase    : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@5287597f:org.apache.tomcat.util.net.NioChannel@7d5d9d3:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:38824]], Read from buffer: [0]
2021-07-10 17:52:24.029 DEBUG 24250 --- [nio-8080-exec-1] o.apache.coyote.http11.Http11Processor   : Error parsing HTTP request header

java.io.EOFException: null
    at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1345) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1255) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:799) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:359) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:261) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]

2021-07-10 17:52:24.029 DEBUG 24250 --- [nio-8080-exec-1] o.apache.coyote.http11.Http11Processor   : Error state [CLOSE_CONNECTION_NOW] reported while processing request

java.io.EOFException: null
    at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1345) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1255) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:799) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:359) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:261) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]

2021-07-10 17:52:24.030 DEBUG 24250 --- [nio-8080-exec-1] o.apache.coyote.http11.Http11Processor   : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@5287597f:org.apache.tomcat.util.net.NioChannel@7d5d9d3:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:38824]], Status in: [OPEN_READ], State out: [CLOSED]
2021-07-10 17:52:24.031 DEBUG 24250 --- [nio-8080-exec-1] o.apache.tomcat.util.threads.LimitLatch  : Counting down[http-nio-8080-exec-1] latch=1
2021-07-10 17:52:24.031 DEBUG 24250 --- [nio-8080-exec-1] org.apache.tomcat.util.net.NioEndpoint   : Calling [org.apache.tomcat.util.net.NioEndpoint@68245ebc].closeSocket([org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@5287597f:org.apache.tomcat.util.net.NioChannel@7d5d9d3:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:38824]])

Have a look at the log you've given:

o.s.s.w.a.i.FilterSecurityInterceptor    : Failed to authorize filter invocation [POST /error] with attributes [authenticated]

The .anyRequest().authenticated() is blocking the access to the default /error page. If you configure in such a way that it permits this specific path, it can process as you expected.

You can do this by simply adding this:

.antMatchers("/error").permitAll()

In fact, you can allow all the urls you want if you declare a list of urls you want to allow and set that to the antMatchers parameter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM