From b7dc65d97fec0391eac63d2652e946177db14a52 Mon Sep 17 00:00:00 2001 From: Holger Hagen <19706592+holgerhagen@users.noreply.github.com> Date: Wed, 6 Mar 2024 13:33:40 +0100 Subject: [PATCH] Closes #2510 - fixed csrf handling without disabling it --- .../security/BootWebSecurityConfigurer.java | 6 ++- .../boot/security/CsrfCookieFilter.java | 25 +++++++++++ .../security/SpaCsrfTokenRequestHandler.java | 44 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/CsrfCookieFilter.java create mode 100644 rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/SpaCsrfTokenRequestHandler.java diff --git a/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/BootWebSecurityConfigurer.java b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/BootWebSecurityConfigurer.java index 73e4225e5..d98ae8dd9 100644 --- a/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/BootWebSecurityConfigurer.java +++ b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/BootWebSecurityConfigurer.java @@ -75,7 +75,11 @@ public class BootWebSecurityConfigurer { if (enableCsrf) { CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); csrfTokenRepository.setCookiePath("/"); - http.csrf(csrf -> csrf.csrfTokenRepository(csrfTokenRepository)); + http.csrf( + csrf -> + csrf.csrfTokenRepository(csrfTokenRepository) + .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())) + .addFilterAfter(new CsrfCookieFilter(), SpringSecurityToJaasFilter.class); } else { http.csrf(AbstractHttpConfigurer::disable).httpBasic(Customizer.withDefaults()); } diff --git a/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/CsrfCookieFilter.java b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/CsrfCookieFilter.java new file mode 100644 index 000000000..2af0127be --- /dev/null +++ b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/CsrfCookieFilter.java @@ -0,0 +1,25 @@ +package pro.taskana.example.boot.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.web.filter.OncePerRequestFilter; + +final class CsrfCookieFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal( + HttpServletRequest request, + @SuppressWarnings("NullableProblems") HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf"); + // Render the token value to a cookie by causing the deferred token to be loaded + csrfToken.getToken(); + + filterChain.doFilter(request, response); + } +} diff --git a/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/SpaCsrfTokenRequestHandler.java b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/SpaCsrfTokenRequestHandler.java new file mode 100644 index 000000000..2b3303749 --- /dev/null +++ b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/security/SpaCsrfTokenRequestHandler.java @@ -0,0 +1,44 @@ +package pro.taskana.example.boot.security; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.function.Supplier; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; +import org.springframework.security.web.csrf.CsrfTokenRequestHandler; +import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; +import org.springframework.util.StringUtils; + +final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler { + private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler(); + + @Override + public void handle( + HttpServletRequest request, HttpServletResponse response, Supplier csrfToken) { + /* + * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of + * the CsrfToken when it is rendered in the response body. + */ + this.delegate.handle(request, response, csrfToken); + } + + @Override + public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { + /* + * If the request contains a request header, use CsrfTokenRequestAttributeHandler + * to resolve the CsrfToken. This applies when a single-page application includes + * the header value automatically, which was obtained via a cookie containing the + * raw CsrfToken. + */ + if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) { + return super.resolveCsrfTokenValue(request, csrfToken); + } + /* + * In all other cases (e.g. if the request contains a request parameter), use + * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies + * when a server-side rendered form includes the _csrf request parameter as a + * hidden input. + */ + return this.delegate.resolveCsrfTokenValue(request, csrfToken); + } +}