【问题标题】:HttpServletResponse .sendError returns empty response body when missing CSRF tokenHttpServletResponse .sendError 在缺少 CSRF 令牌时返回空响应正文
【发布时间】:2021-07-11 00:02:44
【问题描述】:

我在配置中启用了 CSRF,当我尝试向经过身份验证的端点发出 POST 请求时,标头或 cookie 中没有 CSRF 令牌(第一个请求),Spring 返回带有状态码但没有响应的错误响应正文(标题 CONTENT-LENGTH 为 0)。我在我的一个项目中遇到了这个问题,但我尝试使用更简单的设置来重现该行为。

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

我知道使用此设置,CSRF 默认启用。 我还自定义了 AccessDeniedHandler,因为我希望 Spring 在按预期工作时吐出更具体的错误消息。 当我使用curl -i -X POST http://localhost:8080/hello/ 时,Spring 返回 HTTP/1.1 403,响应为空。但是,如果我删除 SecurityConfig 中的 http.authorizeRequests().anyRequest().authenticated(); 行并启动上面的相同请求,则会出现类似正常行为的错误消息。

{
  "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/"
}

我已经搜索过类似的问题,但没有人回答为什么 response.sendError() 在缺少 CSRF 令牌的情况下出现 .anyRequest().authenticated() 时不起作用。解决方法似乎是使用 response.setStatus() 和 response.getWriter().write() 从头开始​​构建错误消息。这是预期的行为还是我遗漏了什么?

更新调试日志

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]])

【问题讨论】:

  • @Toerktumlare,我更新了完整的调试日志。我提供的调试日志是当我使用authenticated() 保护我的端点时,它确实返回了我写的错误。我在没有authenticated() 的情况下再次做了同样的事情,调试中的错误日志会出现相同的情况。
  • @kle97 您的日志显示您的错误页面/error(Spring Boot 的默认设置)不可访问,因此调用了Http403ForbiddenEntryPoint。如果你删除.anyRequest().authenticated() 是允许的并且返回Spring Boot 的默认错误页面。尝试在您的安全配置中允许/error
  • @dur,感谢您的回复,您的建议解决了问题。如果我在我的安全配置中允许/error,有什么顾虑吗?

标签: java spring spring-boot spring-security


【解决方案1】:

看看你给出的日志:

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

.anyRequest().authenticated() 阻止了对默认 /error 页面的访问。如果您以允许此特定路径的方式进行配置,则它可以按您的预期进行处理。

您只需添加以下内容即可:

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

事实上,如果您声明一个您想要允许的 url 列表并将其设置为 antMatchers 参数,您就可以允许所有您想要的 url。

【讨论】:

  • 嗨@devReddit,我用你的建议更新了代码,一切正常。但是,如果我向 /error 发送 GET 请求,我会收到状态为 999 的 HTTP/1.1 500 错误。如果我在我的安全配置中允许 /error,是否有任何安全问题?
  • 只要您没有涉及某些业务逻辑和其他服务参与的此类 api '/error',那应该不是问题。
猜你喜欢
  • 1970-01-01
  • 2012-07-22
  • 2020-02-10
  • 2017-01-08
  • 2019-06-01
  • 1970-01-01
  • 2013-12-02
  • 2021-10-23
  • 1970-01-01
相关资源
最近更新 更多