|
19 | 19 | import java.util.ArrayList;
|
20 | 20 | import java.util.LinkedHashMap;
|
21 | 21 | import java.util.List;
|
| 22 | +import java.util.function.Supplier; |
22 | 23 |
|
23 | 24 | import io.micrometer.observation.ObservationRegistry;
|
24 | 25 | import jakarta.servlet.http.HttpServletRequest;
|
| 26 | +import jakarta.servlet.http.HttpServletResponse; |
25 | 27 |
|
26 | 28 | import org.springframework.context.ApplicationContext;
|
27 | 29 | import org.springframework.security.access.AccessDeniedException;
|
|
34 | 36 | import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
|
35 | 37 | import org.springframework.security.web.access.ObservationMarkingAccessDeniedHandler;
|
36 | 38 | import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
| 39 | +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; |
37 | 40 | import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
|
38 | 41 | import org.springframework.security.web.csrf.CsrfFilter;
|
39 | 42 | import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
| 43 | +import org.springframework.security.web.csrf.CsrfToken; |
40 | 44 | import org.springframework.security.web.csrf.CsrfTokenRepository;
|
| 45 | +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; |
41 | 46 | import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
|
42 | 47 | import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
43 | 48 | import org.springframework.security.web.csrf.MissingCsrfTokenException;
|
| 49 | +import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; |
44 | 50 | import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
|
45 | 51 | import org.springframework.security.web.session.InvalidSessionStrategy;
|
46 | 52 | import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
47 | 53 | import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
48 | 54 | import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
49 | 55 | import org.springframework.security.web.util.matcher.RequestMatcher;
|
50 | 56 | import org.springframework.util.Assert;
|
| 57 | +import org.springframework.util.StringUtils; |
51 | 58 |
|
52 | 59 | /**
|
53 | 60 | * Adds
|
@@ -214,6 +221,21 @@ public CsrfConfigurer<H> sessionAuthenticationStrategy(
|
214 | 221 | return this;
|
215 | 222 | }
|
216 | 223 |
|
| 224 | + /** |
| 225 | + * <p> |
| 226 | + * Sensible CSRF defaults when used in combination with a single page application. |
| 227 | + * Creates a cookie-based token repository and a custom request handler to resolve the |
| 228 | + * actual token value instead of the encoded token. |
| 229 | + * </p> |
| 230 | + * @return the {@link CsrfConfigurer} for further customizations |
| 231 | + * @since 7.0 |
| 232 | + */ |
| 233 | + public CsrfConfigurer<H> spa() { |
| 234 | + this.csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); |
| 235 | + this.requestHandler = new SpaCsrfTokenRequestHandler(); |
| 236 | + return this; |
| 237 | + } |
| 238 | + |
217 | 239 | @SuppressWarnings("unchecked")
|
218 | 240 | @Override
|
219 | 241 | public void configure(H http) {
|
@@ -375,4 +397,42 @@ protected IgnoreCsrfProtectionRegistry chainRequestMatchers(List<RequestMatcher>
|
375 | 397 |
|
376 | 398 | }
|
377 | 399 |
|
| 400 | + private static class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler { |
| 401 | + |
| 402 | + private final CsrfTokenRequestAttributeHandler plain = new CsrfTokenRequestAttributeHandler(); |
| 403 | + |
| 404 | + private final CsrfTokenRequestAttributeHandler xor = new XorCsrfTokenRequestAttributeHandler(); |
| 405 | + |
| 406 | + SpaCsrfTokenRequestHandler() { |
| 407 | + this.xor.setCsrfRequestAttributeName(null); |
| 408 | + } |
| 409 | + |
| 410 | + @Override |
| 411 | + public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) { |
| 412 | + /* |
| 413 | + * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection |
| 414 | + * of the CsrfToken when it is rendered in the response body. |
| 415 | + */ |
| 416 | + this.xor.handle(request, response, csrfToken); |
| 417 | + } |
| 418 | + |
| 419 | + @Override |
| 420 | + public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { |
| 421 | + String headerValue = request.getHeader(csrfToken.getHeaderName()); |
| 422 | + /* |
| 423 | + * If the request contains a request header, use |
| 424 | + * CsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies |
| 425 | + * when a single-page application includes the header value automatically, |
| 426 | + * which was obtained via a cookie containing the raw CsrfToken. |
| 427 | + * |
| 428 | + * In all other cases (e.g. if the request contains a request parameter), use |
| 429 | + * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies |
| 430 | + * when a server-side rendered form includes the _csrf request parameter as a |
| 431 | + * hidden input. |
| 432 | + */ |
| 433 | + return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken); |
| 434 | + } |
| 435 | + |
| 436 | + } |
| 437 | + |
378 | 438 | }
|
0 commit comments