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