From 6986cf3ef3bd8c513539564b98b2960bce2875d4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 19 Jun 2019 16:32:44 -0400 Subject: [PATCH] Allow configuration of csrf through nested builder Issue: gh-5557 --- .../annotation/web/builders/HttpSecurity.java | 29 +++++++ ...onfigurerIgnoringRequestMatchersTests.java | 66 ++++++++++++++- .../web/configurers/CsrfConfigurerTests.java | 81 +++++++++++++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 341515f5c3..bad4a7fbb4 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/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: + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 *     protected void configure(HttpSecurity http) throws Exception {
+	 *         http
+	 *             .csrf(csrf -> csrf.disable());
+	 *     }
+	 * }
+	 * 
+ * + * @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> 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 diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java index 39af9745cb..56d99f0610 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerIgnoringRequestMatchersTests.java +++ b/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") diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java index e66fd1a075..be2aad0815 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java +++ b/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);