Bladeren bron

Allow configuration of csrf through nested builder

Issue: gh-5557
Eleftheria Stein 6 jaren geleden
bovenliggende
commit
6986cf3ef3

+ 29 - 0
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -771,6 +771,35 @@ public final class HttpSecurity extends
 		return getOrApply(new CsrfConfigurer<>(context));
 	}
 
+	/**
+	 * Adds CSRF support. This is activated by default when using
+	 * {@link WebSecurityConfigurerAdapter}'s default constructor. You can disable it
+	 * using:
+	 *
+	 * <pre>
+	 * &#064;Configuration
+	 * &#064;EnableWebSecurity
+	 * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	&#064;Override
+	 *     protected void configure(HttpSecurity http) throws Exception {
+	 *         http
+	 *             .csrf(csrf -> csrf.disable());
+	 *     }
+	 * }
+	 * </pre>
+	 *
+	 * @param csrfCustomizer the {@link Customizer} to provide more options for
+	 * the {@link CsrfConfigurer}
+	 * @return the {@link HttpSecurity} for further customizations
+	 * @throws Exception
+	 */
+	public HttpSecurity csrf(Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer) throws Exception {
+		ApplicationContext context = getContext();
+		csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context)));
+		return HttpSecurity.this;
+	}
+
 	/**
 	 * Provides logout support. This is automatically applied when using
 	 * {@link WebSecurityConfigurerAdapter}. The default is that accessing the URL

+ 65 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -75,6 +75,36 @@ public class CsrfConfigurerIgnoringRequestMatchersTests {
 		}
 	}
 
+	@Test
+	public void requestWhenIgnoringRequestMatchersInLambdaThenAugmentedByConfiguredRequestMatcher()
+			throws Exception {
+		this.spring.register(IgnoringRequestInLambdaMatchers.class, BasicController.class).autowire();
+
+		this.mvc.perform(get("/path"))
+				.andExpect(status().isForbidden());
+
+		this.mvc.perform(post("/path"))
+				.andExpect(status().isOk());
+	}
+
+	@EnableWebSecurity
+	static class IgnoringRequestInLambdaMatchers extends WebSecurityConfigurerAdapter {
+		RequestMatcher requestMatcher =
+				request -> HttpMethod.POST.name().equals(request.getMethod());
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.csrf(csrf ->
+					csrf
+						.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/path"))
+						.ignoringRequestMatchers(this.requestMatcher)
+				);
+			// @formatter:on
+		}
+	}
+
 	@Test
 	public void requestWhenIgnoringRequestMatcherThenUnionsWithConfiguredIgnoringAntMatchers()
 			throws Exception {
@@ -107,6 +137,40 @@ public class CsrfConfigurerIgnoringRequestMatchersTests {
 		}
 	}
 
+	@Test
+	public void requestWhenIgnoringRequestMatcherInLambdaThenUnionsWithConfiguredIgnoringAntMatchers()
+			throws Exception {
+
+		this.spring.register(IgnoringPathsAndMatchersInLambdaConfig.class, BasicController.class).autowire();
+
+		this.mvc.perform(put("/csrf"))
+				.andExpect(status().isForbidden());
+
+		this.mvc.perform(post("/csrf"))
+				.andExpect(status().isOk());
+
+		this.mvc.perform(put("/no-csrf"))
+				.andExpect(status().isOk());
+	}
+
+	@EnableWebSecurity
+	static class IgnoringPathsAndMatchersInLambdaConfig extends WebSecurityConfigurerAdapter {
+		RequestMatcher requestMatcher =
+				request -> HttpMethod.POST.name().equals(request.getMethod());
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.csrf(csrf ->
+					csrf
+						.ignoringAntMatchers("/no-csrf")
+						.ignoringRequestMatchers(this.requestMatcher)
+				);
+			// @formatter:on
+		}
+	}
+
 	@RestController
 	public static class BasicController {
 		@RequestMapping("/path")

+ 81 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java

@@ -210,6 +210,26 @@ public class CsrfConfigurerTests {
 		}
 	}
 
+	@Test
+	public void postWhenCsrfDisabledInLambdaThenRespondsWithOk() throws Exception {
+		this.spring.register(DisableCsrfInLambdaConfig.class, BasicController.class).autowire();
+
+		this.mvc.perform(post("/"))
+				.andExpect(status().isOk());
+	}
+
+	@EnableWebSecurity
+	static class DisableCsrfInLambdaConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.csrf(AbstractHttpConfigurer::disable);
+			// @formatter:on
+		}
+	}
+
 	// SEC-2498
 	@Test
 	public void loginWhenCsrfDisabledThenRedirectsToPreviousPostRequest() throws Exception {
@@ -386,6 +406,40 @@ public class CsrfConfigurerTests {
 		}
 	}
 
+	@Test
+	public void requireCsrfProtectionMatcherInLambdaWhenRequestDoesNotMatchThenRespondsWithOk() throws Exception {
+		RequireCsrfProtectionMatcherInLambdaConfig.MATCHER = mock(RequestMatcher.class);
+		this.spring.register(RequireCsrfProtectionMatcherInLambdaConfig.class, BasicController.class).autowire();
+		when(RequireCsrfProtectionMatcherInLambdaConfig.MATCHER.matches(any()))
+				.thenReturn(false);
+
+		this.mvc.perform(get("/"))
+				.andExpect(status().isOk());
+	}
+
+	@Test
+	public void requireCsrfProtectionMatcherInLambdaWhenRequestMatchesThenRespondsWithForbidden() throws Exception {
+		RequireCsrfProtectionMatcherInLambdaConfig.MATCHER = mock(RequestMatcher.class);
+		when(RequireCsrfProtectionMatcherInLambdaConfig.MATCHER.matches(any())).thenReturn(true);
+		this.spring.register(RequireCsrfProtectionMatcherInLambdaConfig.class, BasicController.class).autowire();
+
+		this.mvc.perform(get("/"))
+				.andExpect(status().isForbidden());
+	}
+
+	@EnableWebSecurity
+	static class RequireCsrfProtectionMatcherInLambdaConfig extends WebSecurityConfigurerAdapter {
+		static RequestMatcher MATCHER;
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.csrf(csrf -> csrf.requireCsrfProtectionMatcher(MATCHER));
+			// @formatter:on
+		}
+	}
+
 	@Test
 	public void getWhenCustomCsrfTokenRepositoryThenRepositoryIsUsed() throws Exception {
 		CsrfTokenRepositoryConfig.REPO = mock(CsrfTokenRepository.class);
@@ -454,6 +508,33 @@ public class CsrfConfigurerTests {
 		}
 	}
 
+	@Test
+	public void getWhenCustomCsrfTokenRepositoryInLambdaThenRepositoryIsUsed() throws Exception {
+		CsrfTokenRepositoryInLambdaConfig.REPO = mock(CsrfTokenRepository.class);
+		when(CsrfTokenRepositoryInLambdaConfig.REPO.loadToken(any()))
+				.thenReturn(new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token"));
+		this.spring.register(CsrfTokenRepositoryInLambdaConfig.class, BasicController.class).autowire();
+
+		this.mvc.perform(get("/"))
+				.andExpect(status().isOk());
+		verify(CsrfTokenRepositoryInLambdaConfig.REPO).loadToken(any(HttpServletRequest.class));
+	}
+
+	@EnableWebSecurity
+	static class CsrfTokenRepositoryInLambdaConfig extends WebSecurityConfigurerAdapter {
+		static CsrfTokenRepository REPO;
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.formLogin()
+					.and()
+				.csrf(csrf -> csrf.csrfTokenRepository(REPO));
+			// @formatter:on
+		}
+	}
+
 	@Test
 	public void getWhenCustomAccessDeniedHandlerThenHandlerIsUsed() throws Exception {
 		AccessDeniedHandlerConfig.DENIED_HANDLER = mock(AccessDeniedHandler.class);