diff --git a/config/config.gradle b/config/config.gradle
index 86123f1525..57fe44bb78 100644
--- a/config/config.gradle
+++ b/config/config.gradle
@@ -19,6 +19,7 @@ dependencies {
project(':spring-security-ldap'),
project(':spring-security-openid'),
"org.springframework:spring-web:$springVersion",
+ "org.springframework:spring-webmvc:$springVersion",
"org.aspectj:aspectjweaver:$aspectjVersion"
provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
diff --git a/config/pom.xml b/config/pom.xml
index 85569dc3b2..fb9a5c4570 100644
--- a/config/pom.xml
+++ b/config/pom.xml
@@ -133,6 +133,13 @@
compiletrue
+
+ org.springframework
+ spring-webmvc
+ 3.2.3.RELEASE
+ compile
+ true
+ org.apache.tomcattomcat-servlet-api
diff --git a/config/src/main/java/org/springframework/security/config/Elements.java b/config/src/main/java/org/springframework/security/config/Elements.java
index a324f592e7..aef2d8f223 100644
--- a/config/src/main/java/org/springframework/security/config/Elements.java
+++ b/config/src/main/java/org/springframework/security/config/Elements.java
@@ -55,4 +55,5 @@ public abstract class Elements {
public static final String DEBUG = "debug";
public static final String HTTP_FIREWALL = "http-firewall";
public static final String HEADERS = "headers";
+ public static final String CSRF = "csrf";
}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java
index 6b500111a3..16108001eb 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java
@@ -37,6 +37,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
+import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
@@ -69,6 +70,8 @@ final class FilterComparator implements Comparator, Serializable {
order += STEP;
put(HeaderWriterFilter.class, order);
order += STEP;
+ put(CsrfFilter.class, order);
+ order += STEP;
put(LogoutFilter.class, order);
order += STEP;
put(X509AuthenticationFilter.class, order);
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 8a295a464c..1a3407db7c 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
@@ -38,6 +38,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
import org.springframework.security.config.annotation.web.configurers.ChannelSecurityConfigurer;
+import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
@@ -663,6 +664,17 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder());
}
+
+ /**
+ * Adds CSRF support
+ *
+ * @return the {@link ServletApiConfigurer} for further customizations
+ * @throws Exception
+ */
+ public CsrfConfigurer csrf() throws Exception {
+ return getOrApply(new CsrfConfigurer());
+ }
+
/**
* Provides logout support. This is automatically applied when using
* {@link WebSecurityConfigurerAdapter}. The default is that accessing
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/CsrfWebMvcConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/CsrfWebMvcConfiguration.java
new file mode 100644
index 0000000000..bc7775c86a
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/CsrfWebMvcConfiguration.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2013 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.web.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.support.RequestDataValueProcessor;
+
+/**
+ * Used to add a {@link RequestDataValueProcessor} for Spring MVC and Spring
+ * Security CSRF integration. This configuration is added whenever
+ * {@link EnableWebMvc} is added by {@link SpringWebMvcImportSelector} and the
+ * DispatcherServlet is present on the classpath.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+@Configuration
+class CsrfWebMvcConfiguration {
+
+ @Bean
+ public RequestDataValueProcessor requestDataValueProcessor() {
+ return new CsrfRequestDataValueProcessor();
+ }
+}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java
index 39b8fab879..3fdc5e9ae7 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java
@@ -77,7 +77,7 @@ import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value={java.lang.annotation.ElementType.TYPE})
@Documented
-@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class})
+@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class, SpringWebMvcImportSelector.class})
public @interface EnableWebSecurity {
/**
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/SpringWebMvcImportSelector.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/SpringWebMvcImportSelector.java
new file mode 100644
index 0000000000..5cc4073bc0
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/SpringWebMvcImportSelector.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2013 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.web.configuration;
+
+import org.springframework.context.annotation.ImportSelector;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Used by {@link EnableWebSecurity} to conditionaly import
+ * {@link CsrfWebMvcConfiguration} when the DispatcherServlet is present on the
+ * classpath.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+class SpringWebMvcImportSelector implements ImportSelector {
+
+ /* (non-Javadoc)
+ * @see org.springframework.context.annotation.ImportSelector#selectImports(org.springframework.core.type.AnnotationMetadata)
+ */
+ public String[] selectImports(AnnotationMetadata importingClassMetadata) {
+ boolean webmvcPresent = ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", getClass().getClassLoader());
+ return webmvcPresent ? new String[] {"org.springframework.security.config.annotation.web.configuration.CsrfWebMvcConfiguration"} : new String[] {};
+ }
+}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java
index 398b76db0c..23751949c1 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java
@@ -154,6 +154,7 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer
http.setSharedObject(ContentNegotiationStrategy.class, contentNegotiationStrategy);
if(!disableDefaults) {
http
+ .csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java
index 5c6dd82f0b..bb7b21136d 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractHttpConfigurer.java
@@ -30,4 +30,15 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
*/
abstract class AbstractHttpConfigurer> extends SecurityConfigurerAdapter {
+ /**
+ * Disables the {@link AbstractHttpConfigurer} by removing it. After doing
+ * so a fresh version of the configuration can be applied.
+ *
+ * @return the {@link HttpSecurityBuilder} for additional customizations
+ */
+ @SuppressWarnings("unchecked")
+ public B disable() {
+ getBuilder().removeConfigurer(getClass());
+ return getBuilder();
+ }
}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java
index c227c1b558..4a4dc01f57 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AnonymousConfigurer.java
@@ -39,7 +39,7 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi
* @author Rob Winch
* @since 3.2
*/
-public final class AnonymousConfigurer> extends SecurityConfigurerAdapter {
+public final class AnonymousConfigurer> extends AbstractHttpConfigurer {
private String key;
private AuthenticationProvider authenticationProvider;
private AnonymousAuthenticationFilter authenticationFilter;
@@ -53,18 +53,6 @@ public final class AnonymousConfigurer> extends
public AnonymousConfigurer() {
}
- /**
- * Disables anonymous authentication.
- *
- * @return the {@link HttpSecurity} since no further customization of anonymous authentication would be
- * meaningful.
- */
- @SuppressWarnings("unchecked")
- public H disable() {
- getBuilder().removeConfigurer(getClass());
- return getBuilder();
- }
-
/**
* Sets the key to identify tokens created for anonymous authentication. Default is a secure randomly generated
* key.
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java
new file mode 100644
index 0000000000..7879cf7440
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2013 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.web.configurers;
+
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
+import org.springframework.security.web.csrf.CsrfFilter;
+import org.springframework.security.web.csrf.CsrfLogoutHandler;
+import org.springframework.security.web.csrf.CsrfTokenRepository;
+import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
+import org.springframework.security.web.util.RequestMatcher;
+import org.springframework.util.Assert;
+
+/**
+ * Adds CSRF protection for the methods as specified by
+ * {@link #requireCsrfProtectionMatcher(RequestMatcher)}.
+ *
+ *
Security Filters
+ *
+ * The following Filters are populated
+ *
+ *
+ *
{@link CsrfFilter}
+ *
+ *
+ *
Shared Objects Created
+ *
+ * No shared objects are created.
+ *
+ *
Shared Objects Used
+ *
+ *
+ *
+ * {@link ExceptionHandlingConfigurer#accessDeniedHandler(AccessDeniedHandler)}
+ * is used to determine how to handle CSRF attempts
+ *
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public final class CsrfConfigurer> extends AbstractHttpConfigurer {
+ private CsrfTokenRepository csrfTokenRepository = new HttpSessionCsrfTokenRepository();
+ private RequestMatcher requireCsrfProtectionMatcher;
+
+ /**
+ * Creates a new instance
+ * @see HttpSecurity#csrf()
+ */
+ public CsrfConfigurer() {
+ }
+
+ /**
+ * Specify the {@link CsrfTokenRepository} to use. The default is an {@link HttpSessionCsrfTokenRepository}.
+ *
+ * @param csrfTokenRepository the {@link CsrfTokenRepository} to use
+ * @return the {@link CsrfConfigurer} for further customizations
+ */
+ public CsrfConfigurer csrfTokenRepository(CsrfTokenRepository csrfTokenRepository) {
+ Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
+ this.csrfTokenRepository = csrfTokenRepository;
+ return this;
+ }
+
+ /**
+ * Specify the {@link RequestMatcher} to use for determining when CSRF
+ * should be applied. The default is to ignore GET, HEAD, TRACE, OPTIONS and
+ * process all other requests.
+ *
+ * @param requireCsrfProtectionMatcher
+ * the {@link RequestMatcher} to use
+ * @return the {@link CsrfConfigurer} for further customizations
+ */
+ public CsrfConfigurer requireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
+ Assert.notNull(csrfTokenRepository, "requireCsrfProtectionMatcher cannot be null");
+ this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void configure(H http) throws Exception {
+ CsrfFilter filter = new CsrfFilter(csrfTokenRepository);
+ if(requireCsrfProtectionMatcher != null) {
+ filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
+ }
+ ExceptionHandlingConfigurer exceptionConfig = http.getConfigurer(ExceptionHandlingConfigurer.class);
+ if(exceptionConfig != null) {
+ AccessDeniedHandler accessDeniedHandler = exceptionConfig.getAccessDeniedHandler();
+ if(accessDeniedHandler != null) {
+ filter.setAccessDeniedHandler(accessDeniedHandler);
+ }
+ }
+ LogoutConfigurer logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
+ if(logoutConfigurer != null) {
+ logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(csrfTokenRepository));
+ }
+ SessionManagementConfigurer sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
+ if(sessionConfigurer != null) {
+ sessionConfigurer.addSessionAuthenticationStrategy(new CsrfAuthenticationStrategy(csrfTokenRepository));
+ }
+ filter = postProcess(filter);
+ http.addFilter(filter);
+ }
+}
\ No newline at end of file
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java
index 0bc492d876..cfaf8e1eb2 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java
@@ -153,6 +153,15 @@ public final class ExceptionHandlingConfigurer>
return this.authenticationEntryPoint;
}
+ /**
+ * Gets the {@link AccessDeniedHandler} that is configured.
+ *
+ * @return the {@link AccessDeniedHandler}
+ */
+ AccessDeniedHandler getAccessDeniedHandler() {
+ return this.accessDeniedHandler;
+ }
+
@Override
public void configure(H http) throws Exception {
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java
index 4f36b8f102..8c7616d0eb 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java
@@ -16,7 +16,6 @@
package org.springframework.security.config.annotation.web.configurers;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpSession;
@@ -31,6 +30,8 @@ import org.springframework.security.web.authentication.logout.LogoutSuccessHandl
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+import org.springframework.security.web.util.AntPathRequestMatcher;
+import org.springframework.security.web.util.RequestMatcher;
/**
* Adds logout support. Other {@link SecurityConfigurer} instances may invoke
@@ -63,7 +64,7 @@ public final class LogoutConfigurer> extends Ab
private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();
private String logoutSuccessUrl = "/login?logout";
private LogoutSuccessHandler logoutSuccessHandler;
- private String logoutUrl = "/logout";
+ private RequestMatcher logoutRequestMatcher = new AntPathRequestMatcher("/logout", "POST");
private boolean permitAll;
private boolean customLogoutSuccess;
@@ -97,12 +98,22 @@ public final class LogoutConfigurer> extends Ab
}
/**
- * The URL that triggers logout to occur. The default is "/logout"
+ * The URL that triggers logout to occur on HTTP POST. The default is "/logout"
* @param logoutUrl the URL that will invoke logout.
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer logoutUrl(String logoutUrl) {
- this.logoutUrl = logoutUrl;
+ return logoutRequestMatcher(new AntPathRequestMatcher(logoutUrl, "POST"));
+ }
+
+
+ /**
+ * The RequestMatcher that triggers logout to occur on HTTP POST. The default is "/logout"
+ * @param logoutRequestMatcher the RequestMatcher used to determine if logout should occur.
+ * @return the {@link LogoutConfigurer} for further customization
+ */
+ public LogoutConfigurer logoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
+ this.logoutRequestMatcher = logoutRequestMatcher;
return this;
}
@@ -189,7 +200,8 @@ public final class LogoutConfigurer> extends Ab
@Override
public void init(H http) throws Exception {
if(permitAll) {
- PermitAllSupport.permitAll(http, this.logoutUrl, this.logoutSuccessUrl);
+ PermitAllSupport.permitAll(http, this.logoutSuccessUrl);
+ PermitAllSupport.permitAll(http, this.logoutRequestMatcher);
}
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
@@ -245,7 +257,7 @@ public final class LogoutConfigurer> extends Ab
logoutHandlers.add(contextLogoutHandler);
LogoutHandler[] handlers = logoutHandlers.toArray(new LogoutHandler[logoutHandlers.size()]);
LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
- result.setFilterProcessesUrl(logoutUrl);
+ result.setLogoutRequestMatcher(logoutRequestMatcher);
result = postProcess(result);
return result;
}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java
index 2291a2d71f..28ada4568e 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java
@@ -31,17 +31,25 @@ import org.springframework.security.web.util.RequestMatcher;
*/
final class PermitAllSupport {
- @SuppressWarnings("unchecked")
public static void permitAll(HttpSecurityBuilder extends HttpSecurityBuilder>> http, String... urls) {
+ for(String url : urls) {
+ if(url != null) {
+ permitAll(http, new ExactUrlRequestMatcher(url));
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void permitAll(HttpSecurityBuilder extends HttpSecurityBuilder>> http, RequestMatcher... requestMatchers) {
ExpressionUrlAuthorizationConfigurer> configurer = http.getConfigurer(ExpressionUrlAuthorizationConfigurer.class);
if(configurer == null) {
throw new IllegalStateException("permitAll only works with HttpSecurity.authorizeRequests()");
}
- for(String url : urls) {
- if(url != null) {
- configurer.addMapping(0, new UrlMapping(new ExactUrlRequestMatcher(url), SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
+ for(RequestMatcher matcher : requestMatchers) {
+ if(matcher != null) {
+ configurer.addMapping(0, new UrlMapping(matcher, SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
}
}
}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java
index 1d57e5664d..7d5747b6d9 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java
@@ -20,6 +20,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
+import org.springframework.security.web.util.AntPathRequestMatcher;
/**
* Adds request cache for Spring Security. Specifically this ensures that
@@ -71,6 +72,11 @@ public final class RequestCacheConfigurer> exte
return this;
}
+ @Override
+ public void init(H http) throws Exception {
+ http.setSharedObject(RequestCache.class, getRequestCache(http));
+ }
+
@Override
public void configure(H http) throws Exception {
RequestCache requestCache = getRequestCache(http);
@@ -93,6 +99,8 @@ public final class RequestCacheConfigurer> exte
if(result != null) {
return result;
}
- return new HttpSessionRequestCache();
+ HttpSessionRequestCache defaultCache = new HttpSessionRequestCache();
+ defaultCache.setRequestMatcher(new AntPathRequestMatcher("/**", "GET"));
+ return defaultCache;
}
}
\ No newline at end of file
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java
index 2971fbd951..b343e4f3a5 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java
@@ -64,12 +64,6 @@ public final class ServletApiConfigurer> extend
return this;
}
- @SuppressWarnings("unchecked")
- public H disable() {
- getBuilder().removeConfigurer(getClass());
- return getBuilder();
- }
-
@Override
@SuppressWarnings("unchecked")
public void configure(H http) throws Exception {
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
index 9763231f6e..de865a5d87 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
@@ -15,6 +15,7 @@
*/
package org.springframework.security.config.annotation.web.configurers;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -80,6 +81,7 @@ import org.springframework.util.Assert;
public final class SessionManagementConfigurer> extends AbstractHttpConfigurer {
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = createDefaultSessionFixationProtectionStrategy();
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
+ private List sessionAuthenticationStrategies = new ArrayList();
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
private Integer maximumSessions;
private String expiredUrl;
@@ -173,6 +175,18 @@ public final class SessionManagementConfigurer>
return this;
}
+ /**
+ * Adds an additional {@link SessionAuthenticationStrategy} to be used within the {@link CompositeSessionAuthenticationStrategy}.
+ *
+ * @param sessionAuthenticationStrategy
+ * @return the {@link SessionManagementConfigurer} for further
+ * customizations
+ */
+ SessionManagementConfigurer addSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
+ this.sessionAuthenticationStrategies.add(sessionAuthenticationStrategy);
+ return this;
+ }
+
public SessionFixationConfigurer sessionFixation() {
return new SessionFixationConfigurer();
}
@@ -400,6 +414,7 @@ public final class SessionManagementConfigurer>
if(sessionAuthenticationStrategy != null) {
return sessionAuthenticationStrategy;
}
+ List delegateStrategies = sessionAuthenticationStrategies;
if(isConcurrentSessionControlEnabled()) {
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
@@ -409,11 +424,11 @@ public final class SessionManagementConfigurer>
RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry);
registerSessionStrategy = postProcess(registerSessionStrategy);
- List delegateStrategies = Arrays.asList(concurrentSessionControlStrategy, sessionFixationAuthenticationStrategy, registerSessionStrategy);
- sessionAuthenticationStrategy = postProcess(new CompositeSessionAuthenticationStrategy(delegateStrategies));
+ delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy, sessionFixationAuthenticationStrategy, registerSessionStrategy));
} else {
- sessionAuthenticationStrategy = sessionFixationAuthenticationStrategy;
+ delegateStrategies.add(sessionFixationAuthenticationStrategy);
}
+ sessionAuthenticationStrategy = postProcess(new CompositeSessionAuthenticationStrategy(delegateStrategies));
return sessionAuthenticationStrategy;
}
diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java
index 4bcf8369ae..0e432cee21 100644
--- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java
+++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java
@@ -116,6 +116,7 @@ final class AuthenticationConfigBuilder {
private BeanReference jeeProviderRef;
private RootBeanDefinition preAuthEntryPoint;
private BeanMetadataElement mainEntryPoint;
+ private BeanMetadataElement accessDeniedHandler;
private BeanDefinition logoutFilter;
@SuppressWarnings("rawtypes")
@@ -125,9 +126,10 @@ final class AuthenticationConfigBuilder {
private final BeanReference requestCache;
private final BeanReference portMapper;
private final BeanReference portResolver;
+ private final BeanMetadataElement csrfLogoutHandler;
public AuthenticationConfigBuilder(Element element, ParserContext pc, SessionCreationPolicy sessionPolicy,
- BeanReference requestCache, BeanReference authenticationManager, BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver) {
+ BeanReference requestCache, BeanReference authenticationManager, BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
this.httpElt = element;
this.pc = pc;
this.requestCache = requestCache;
@@ -136,6 +138,7 @@ final class AuthenticationConfigBuilder {
&& sessionPolicy != SessionCreationPolicy.STATELESS;
this.portMapper = portMapper;
this.portResolver = portResolver;
+ this.csrfLogoutHandler = csrfLogoutHandler;
createAnonymousFilter();
createRememberMeFilter(authenticationManager);
@@ -483,7 +486,7 @@ final class AuthenticationConfigBuilder {
void createLogoutFilter() {
Element logoutElt = DomUtils.getChildElementByTagName(httpElt, Elements.LOGOUT);
if (logoutElt != null || autoConfig) {
- LogoutBeanDefinitionParser logoutParser = new LogoutBeanDefinitionParser(rememberMeServicesId);
+ LogoutBeanDefinitionParser logoutParser = new LogoutBeanDefinitionParser(rememberMeServicesId, csrfLogoutHandler);
logoutFilter = logoutParser.parse(logoutElt, pc);
logoutHandlers = logoutParser.getLogoutHandlers();
}
@@ -493,6 +496,9 @@ final class AuthenticationConfigBuilder {
ManagedList getLogoutHandlers() {
if(logoutHandlers == null && rememberMeProviderRef != null) {
logoutHandlers = new ManagedList();
+ if(csrfLogoutHandler != null) {
+ logoutHandlers.add(csrfLogoutHandler);
+ }
logoutHandlers.add(new RuntimeBeanReference(rememberMeServicesId));
logoutHandlers.add(new RootBeanDefinition(SecurityContextLogoutHandler.class));
}
@@ -504,6 +510,10 @@ final class AuthenticationConfigBuilder {
return mainEntryPoint;
}
+ BeanMetadataElement getAccessDeniedHandlerBean() {
+ return accessDeniedHandler;
+ }
+
void createAnonymousFilter() {
Element anonymousElt = DomUtils.getChildElementByTagName(httpElt, Elements.ANONYMOUS);
@@ -559,7 +569,8 @@ final class AuthenticationConfigBuilder {
void createExceptionTranslationFilter() {
BeanDefinitionBuilder etfBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
- etfBuilder.addPropertyValue("accessDeniedHandler", createAccessDeniedHandler(httpElt, pc));
+ accessDeniedHandler = createAccessDeniedHandler(httpElt, pc);
+ etfBuilder.addPropertyValue("accessDeniedHandler", accessDeniedHandler);
assert requestCache != null;
mainEntryPoint = selectEntryPoint();
etfBuilder.addConstructorArgValue(mainEntryPoint);
diff --git a/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java
new file mode 100644
index 0000000000..1cdeb8bad7
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2012 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.http;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
+import org.springframework.security.web.csrf.CsrfFilter;
+import org.springframework.security.web.csrf.CsrfLogoutHandler;
+import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
+import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+import org.w3c.dom.Element;
+
+/**
+ * Parser for the {@code CsrfFilter}.
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
+
+ private static final String REQUEST_DATA_VALUE_PROCESSOR = "requestDataValueProcessor";
+ private static final String DISPATCHER_SERVLET_CLASS_NAME = "org.springframework.web.servlet.DispatcherServlet";
+ private static final String ATT_MATCHER = "request-matcher-ref";
+ private static final String ATT_REPOSITORY = "token-repository-ref";
+
+ private String csrfRepositoryRef;
+
+ public BeanDefinition parse(Element element, ParserContext pc) {
+ boolean webmvcPresent = ClassUtils.isPresent(DISPATCHER_SERVLET_CLASS_NAME, getClass().getClassLoader());
+ if(webmvcPresent) {
+ RootBeanDefinition beanDefinition = new RootBeanDefinition(CsrfRequestDataValueProcessor.class);
+ BeanComponentDefinition componentDefinition =
+ new BeanComponentDefinition(beanDefinition, REQUEST_DATA_VALUE_PROCESSOR);
+ pc.registerBeanComponent(componentDefinition);
+ }
+
+ csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
+ String matcherRef = element.getAttribute(ATT_MATCHER);
+
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(CsrfFilter.class);
+
+ if(!StringUtils.hasText(csrfRepositoryRef)) {
+ RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(HttpSessionCsrfTokenRepository.class);
+ csrfRepositoryRef = pc.getReaderContext().generateBeanName(csrfTokenRepository);
+ pc.registerBeanComponent(new BeanComponentDefinition(csrfTokenRepository, csrfRepositoryRef));
+ }
+
+ builder.addConstructorArgReference(csrfRepositoryRef);
+
+ if(StringUtils.hasText(matcherRef)) {
+ builder.addPropertyReference("requireCsrfProtectionMatcher", matcherRef);
+ }
+
+ return builder.getBeanDefinition();
+ }
+
+ BeanDefinition getCsrfAuthenticationStrategy() {
+ BeanDefinitionBuilder csrfAuthenticationStrategy = BeanDefinitionBuilder.rootBeanDefinition(CsrfAuthenticationStrategy.class);
+ csrfAuthenticationStrategy.addConstructorArgReference(csrfRepositoryRef);
+ return csrfAuthenticationStrategy.getBeanDefinition();
+ }
+
+ BeanDefinition getCsrfLogoutHandler() {
+ BeanDefinitionBuilder csrfAuthenticationStrategy = BeanDefinitionBuilder.rootBeanDefinition(CsrfLogoutHandler.class);
+ csrfAuthenticationStrategy.addConstructorArgReference(csrfRepositoryRef);
+ return csrfAuthenticationStrategy.getBeanDefinition();
+ }
+}
diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
index 6bfe96b928..51272bf1ff 100644
--- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
+++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
@@ -28,7 +28,6 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
-import org.springframework.beans.factory.config.BeanReferenceFactoryBean;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
@@ -71,6 +70,7 @@ import org.springframework.security.web.servletapi.SecurityContextHolderAwareReq
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
+import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -126,6 +126,9 @@ class HttpConfigurationBuilder {
private BeanReference fsi;
private BeanReference requestCache;
private BeanDefinition addHeadersFilter;
+ private BeanDefinition csrfFilter;
+ private BeanMetadataElement csrfLogoutHandler;
+ private BeanMetadataElement csrfAuthStrategy;
public HttpConfigurationBuilder(Element element, ParserContext pc,
BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
@@ -152,6 +155,7 @@ class HttpConfigurationBuilder {
sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
}
+ createCsrfFilter();
createSecurityContextPersistenceFilter();
createSessionManagementFilters();
createWebAsyncManagerFilter();
@@ -195,6 +199,12 @@ class HttpConfigurationBuilder {
}
}
+ void setAccessDeniedHandler(BeanMetadataElement accessDeniedHandler) {
+ if(csrfFilter != null) {
+ csrfFilter.getPropertyValues().add("accessDeniedHandler", accessDeniedHandler);
+ }
+ }
+
// Needed to account for placeholders
static String createPath(String path, boolean lowerCase) {
return lowerCase ? path.toLowerCase() : path;
@@ -298,6 +308,10 @@ class HttpConfigurationBuilder {
BeanDefinitionBuilder sessionFixationStrategy = null;
BeanDefinitionBuilder registerSessionStrategy;
+ if(csrfAuthStrategy != null) {
+ delegateSessionStrategies.add(csrfAuthStrategy);
+ }
+
if (sessionControlEnabled) {
assert sessionRegistryRef != null;
concurrentSessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlAuthenticationStrategy.class);
@@ -541,6 +555,12 @@ class HttpConfigurationBuilder {
requestCacheBldr = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionRequestCache.class);
requestCacheBldr.addPropertyValue("createSessionAllowed", sessionPolicy == SessionCreationPolicy.IF_REQUIRED);
requestCacheBldr.addPropertyValue("portResolver", portResolver);
+ if(csrfFilter != null) {
+ BeanDefinitionBuilder requestCacheMatcherBldr = BeanDefinitionBuilder.rootBeanDefinition(AntPathRequestMatcher.class);
+ requestCacheMatcherBldr.addConstructorArgValue("/**");
+ requestCacheMatcherBldr.addConstructorArgValue("GET");
+ requestCacheBldr.addPropertyValue("requestMatcher", requestCacheMatcherBldr.getBeanDefinition());
+ }
}
BeanDefinition bean = requestCacheBldr.getBeanDefinition();
@@ -617,6 +637,20 @@ class HttpConfigurationBuilder {
}
+ private void createCsrfFilter() {
+ Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.CSRF);
+ if (elmt != null) {
+ CsrfBeanDefinitionParser csrfParser = new CsrfBeanDefinitionParser();
+ this.csrfFilter = csrfParser.parse(elmt, pc);
+ this.csrfAuthStrategy = csrfParser.getCsrfAuthenticationStrategy();
+ this.csrfLogoutHandler = csrfParser.getCsrfLogoutHandler();
+ }
+ }
+
+ BeanMetadataElement getCsrfLogoutHandler() {
+ return this.csrfLogoutHandler;
+ }
+
BeanReference getSessionStrategy() {
return sessionStrategyRef;
}
@@ -668,6 +702,10 @@ class HttpConfigurationBuilder {
filters.add(new OrderDecorator(addHeadersFilter, HEADERS_FILTER));
}
+ if (csrfFilter != null) {
+ filters.add(new OrderDecorator(csrfFilter, CSRF_FILTER));
+ }
+
return filters;
}
}
diff --git a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java
index 3d3fda7436..5043db0e74 100644
--- a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java
+++ b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java
@@ -137,10 +137,11 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
- httpBldr.getSessionStrategy(), portMapper, portResolver);
+ httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
+ httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());
authenticationProviders.addAll(authBldr.getProviders());
diff --git a/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java
index ace6458eef..57a0d46be8 100644
--- a/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java
+++ b/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java
@@ -15,6 +15,7 @@
*/
package org.springframework.security.config.http;
+import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -44,13 +45,15 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
static final String ATT_DELETE_COOKIES = "delete-cookies";
final String rememberMeServices;
- private ManagedList logoutHandlers = new ManagedList();
+ private ManagedList logoutHandlers = new ManagedList();
- public LogoutBeanDefinitionParser(String rememberMeServices) {
+ public LogoutBeanDefinitionParser(String rememberMeServices, BeanMetadataElement csrfLogoutHandler) {
this.rememberMeServices = rememberMeServices;
+ if(csrfLogoutHandler != null) {
+ logoutHandlers.add(csrfLogoutHandler);
+ }
}
- @SuppressWarnings("unchecked")
public BeanDefinition parse(Element element, ParserContext pc) {
String logoutUrl = null;
String successHandlerRef = null;
@@ -111,7 +114,7 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
return builder.getBeanDefinition();
}
- ManagedList getLogoutHandlers() {
+ ManagedList getLogoutHandlers() {
return logoutHandlers;
}
}
diff --git a/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java b/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
index 20194ecedb..d88c23ff25 100644
--- a/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
+++ b/config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
@@ -30,6 +30,7 @@ enum SecurityFilters {
/** {@link WebAsyncManagerIntegrationFilter} */
WEB_ASYNC_MANAGER_FILTER,
HEADERS_FILTER,
+ CSRF_FILTER,
LOGOUT_FILTER,
X509_FILTER,
PRE_AUTH_FILTER,
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.2.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-3.2.rnc
index 028241f14e..e67163eb67 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-3.2.rnc
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.2.rnc
@@ -281,7 +281,7 @@ http-firewall =
http =
## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "secured" attribute to "false".
- element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers?) }
+ element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf?) }
http.attlist &=
## The request URL pattern which will be mapped to the filter chain created by this element. If omitted, the filter chain will match all requests.
attribute pattern {xsd:token}?
@@ -718,8 +718,18 @@ jdbc-user-service.attlist &=
jdbc-user-service.attlist &=
role-prefix?
+csrf =
+ ## Element for configuration of the CsrfFilter for protection against CSRF. It also updates the default RequestCache to only replay "GET" requests.
+ element csrf {csrf-options.attlist}
+csrf-options.attlist &=
+ ## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
+ attribute request-matcher-ref { xsd:token }?
+csrf-options.attlist &=
+ ## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
+ attribute token-repository-ref { xsd:token }?
+
headers =
- ## Element for configuration of the AddHeadersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
+ ## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
element headers {cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & header*}
hsts =
@@ -783,7 +793,7 @@ header.attlist &=
## The value for the header.
attribute value {xsd:token}?
header.attlist &=
- ## Reference to a custom HeaderFactory implementation.
+ ## Reference to a custom HeaderWriter implementation.
ref?
any-user-service = user-service | jdbc-user-service | ldap-user-service
@@ -808,4 +818,4 @@ position =
## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
attribute position {named-security-filter}
-named-security-filter = "FIRST" | "CHANNEL_FILTER" | "CONCURRENT_SESSION_FILTER" | "SECURITY_CONTEXT_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" |"BASIC_AUTH_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "SESSION_MANAGEMENT_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
+named-security-filter = "FIRST" | "CHANNEL_FILTER" | "CONCURRENT_SESSION_FILTER" | "HEADERS_FILTER" | "CSRF_FILTER" | "SECURITY_CONTEXT_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" |"BASIC_AUTH_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "SESSION_MANAGEMENT_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.2.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-3.2.xsd
index bbc1055b6a..366c6e8f68 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-3.2.xsd
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.2.xsd
@@ -1025,6 +1025,7 @@
+
@@ -2238,9 +2239,34 @@
+
+
+ Element for configuration of the CsrfFilter for protection against CSRF. It also updates
+ the default RequestCache to only replay "GET" requests.
+
+
+
+
+
+
+
+
+
+ The RequestMatcher instance to be used to determine if CSRF should be applied. Default is
+ any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
+
+
+
+
+
+ The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository
+
+
+
+
- Element for configuration of the AddHeadersFilter. Enables easy setting for the
+ Element for configuration of the HeaderWritersFilter. Enables easy setting for the
X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
@@ -2479,6 +2505,8 @@
+
+
diff --git a/config/src/test/groovy/org/springframework/security/config/AbstractXmlConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/AbstractXmlConfigTests.groovy
index a7d6590182..420034e517 100644
--- a/config/src/test/groovy/org/springframework/security/config/AbstractXmlConfigTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/AbstractXmlConfigTests.groovy
@@ -1,6 +1,8 @@
package org.springframework.security.config
import groovy.xml.MarkupBuilder
+
+import org.mockito.Mockito;
import org.springframework.context.support.AbstractXmlApplicationContext
import org.springframework.security.config.util.InMemoryXmlApplicationContext
import org.springframework.security.core.context.SecurityContextHolder
@@ -37,6 +39,12 @@ abstract class AbstractXmlConfigTests extends Specification {
SecurityContextHolder.clearContext();
}
+ def mockBean(Class clazz, String id = clazz.simpleName) {
+ xml.'b:bean'(id: id, 'class': Mockito.class.name, 'factory-method':'mock') {
+ 'b:constructor-arg'(value : clazz.name)
+ }
+ }
+
def bean(String name, Class clazz) {
xml.'b:bean'(id: name, 'class': clazz.name)
}
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy
index 1978d67631..a29320da2b 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/BaseSpringSpec.groovy
@@ -25,16 +25,18 @@ import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.core.Authentication
-import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.context.SecurityContextImpl
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.context.HttpRequestResponseHolder
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
+import org.springframework.security.web.csrf.CsrfToken
+import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository
import spock.lang.AutoCleanup
import spock.lang.Specification
@@ -50,11 +52,26 @@ abstract class BaseSpringSpec extends Specification {
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
+ CsrfToken csrfToken
def setup() {
+ setupWeb(null)
+ }
+
+ def setupWeb(httpSession = null) {
request = new MockHttpServletRequest(method:"GET")
+ if(httpSession) {
+ request.session = httpSession
+ }
response = new MockHttpServletResponse()
chain = new MockFilterChain()
+ setupCsrf()
+ }
+
+ def setupCsrf(csrfTokenValue="BaseSpringSpec_CSRFTOKEN") {
+ csrfToken = new CsrfToken("X-CSRF-TOKEN","_csrf",csrfTokenValue)
+ new HttpSessionCsrfTokenRepository().saveToken(csrfToken, request,response)
+ request.setParameter(csrfToken.parameterName, csrfToken.token)
}
AuthenticationManagerBuilder authenticationBldr = new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR).inMemoryAuthentication().and()
@@ -117,6 +134,10 @@ abstract class BaseSpringSpec extends Specification {
authenticationProviders().find { provider.isAssignableFrom(it.class) }
}
+ def getCurrentAuthentication() {
+ new HttpSessionSecurityContextRepository().loadContext(new HttpRequestResponseHolder(request, response)).authentication
+ }
+
def login(String username="user", String role="ROLE_USER") {
login(new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(role)))
}
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/BaseWebSpecuritySpec.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/BaseWebSpecuritySpec.groovy
deleted file mode 100644
index 29f1c9d6ab..0000000000
--- a/config/src/test/groovy/org/springframework/security/config/annotation/BaseWebSpecuritySpec.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2002-2013 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.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.security.config.annotation;
-
-import org.springframework.context.ConfigurableApplicationContext
-import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-import org.springframework.mock.web.MockFilterChain;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.security.core.context.SecurityContextHolder
-import org.springframework.security.web.FilterChainProxy;
-
-import spock.lang.AutoCleanup
-import spock.lang.Specification
-
-/**
- *
- * @author Rob Winch
- */
-abstract class BaseWebSpecuritySpec extends BaseSpringSpec {
- FilterChainProxy springSecurityFilterChain
- MockHttpServletRequest request
- MockHttpServletResponse response
- MockFilterChain chain
-
- def setup() {
- request = new MockHttpServletRequest(method:"GET")
- response = new MockHttpServletResponse()
- chain = new MockFilterChain()
- }
-
-
- def loadConfig(Class>... configs) {
- super.loadConfig(configs)
- springSecurityFilterChain = context.getBean(FilterChainProxy)
- }
-
-
-}
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/SampleWebSecurityConfigurerAdapterTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/SampleWebSecurityConfigurerAdapterTests.groovy
index 5ef38b733d..8a2f65af32 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/SampleWebSecurityConfigurerAdapterTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/SampleWebSecurityConfigurerAdapterTests.groovy
@@ -20,8 +20,7 @@ import javax.servlet.http.HttpServletResponse
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
-import org.springframework.security.authentication.AuthenticationManager
-import org.springframework.security.config.annotation.BaseWebSpecuritySpec
+import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
@@ -34,7 +33,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
* @author Rob Winch
*
*/
-public class SampleWebSecurityConfigurerAdapterTests extends BaseWebSpecuritySpec {
+public class SampleWebSecurityConfigurerAdapterTests extends BaseSpringSpec {
def "README HelloWorld Sample works"() {
setup: "Sample Config is loaded"
loadConfig(HelloWorldWebSecurityConfigurerAdapter)
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy
index 333e2bad81..206b72fa28 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy
@@ -79,7 +79,8 @@ class WebSecurityConfigurerAdapterTests extends BaseSpringSpec {
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
'Pragma':'no-cache',
- 'X-XSS-Protection' : '1; mode=block']
+ 'X-XSS-Protection' : '1; mode=block',
+ 'X-CSRF-TOKEN' : csrfToken.token]
}
@EnableWebSecurity
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.groovy
new file mode 100644
index 0000000000..d82c72d81a
--- /dev/null
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.groovy
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2002-2013 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.config.annotation.web.configurers
+
+import javax.servlet.http.HttpServletResponse
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.BaseSpringSpec
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.web.access.AccessDeniedHandler
+import org.springframework.security.web.csrf.CsrfFilter;
+import org.springframework.security.web.csrf.CsrfTokenRepository;
+import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
+import org.springframework.security.web.util.RequestMatcher;
+
+import spock.lang.Unroll;
+
+/**
+ *
+ * @author Rob Winch
+ */
+class CsrfConfigurerTests extends BaseSpringSpec {
+
+ @Unroll
+ def "csrf applied by default"() {
+ setup:
+ loadConfig(CsrfAppliedDefaultConfig)
+ request.method = httpMethod
+ clearCsrfToken()
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ response.status == httpStatus
+ where:
+ httpMethod | httpStatus
+ 'POST' | HttpServletResponse.SC_FORBIDDEN
+ 'PUT' | HttpServletResponse.SC_FORBIDDEN
+ 'PATCH' | HttpServletResponse.SC_FORBIDDEN
+ 'DELETE' | HttpServletResponse.SC_FORBIDDEN
+ 'INVALID' | HttpServletResponse.SC_FORBIDDEN
+ 'GET' | HttpServletResponse.SC_OK
+ 'HEAD' | HttpServletResponse.SC_OK
+ 'TRACE' | HttpServletResponse.SC_OK
+ 'OPTIONS' | HttpServletResponse.SC_OK
+ }
+
+ def "csrf default creates CsrfRequestDataValueProcessor"() {
+ when:
+ loadConfig(CsrfAppliedDefaultConfig)
+ then:
+ context.getBean(CsrfRequestDataValueProcessor)
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class CsrfAppliedDefaultConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ }
+ }
+
+ def "csrf disable"() {
+ setup:
+ loadConfig(DisableCsrfConfig)
+ request.method = "POST"
+ clearCsrfToken()
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ !findFilter(CsrfFilter)
+ response.status == HttpServletResponse.SC_OK
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class DisableCsrfConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .csrf().disable()
+ }
+ }
+
+ def "csrf requireCsrfProtectionMatcher"() {
+ setup:
+ RequireCsrfProtectionMatcherConfig.matcher = Mock(RequestMatcher)
+ RequireCsrfProtectionMatcherConfig.matcher.matches(_) >>> [false,true]
+ loadConfig(RequireCsrfProtectionMatcherConfig)
+ clearCsrfToken()
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ response.status == HttpServletResponse.SC_OK
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ response.status == HttpServletResponse.SC_FORBIDDEN
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class RequireCsrfProtectionMatcherConfig extends WebSecurityConfigurerAdapter {
+ static RequestMatcher matcher
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .csrf()
+ .requireCsrfProtectionMatcher(matcher)
+ }
+ }
+
+ def "csrf csrfTokenRepository"() {
+ setup:
+ CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
+ loadConfig(CsrfTokenRepositoryConfig)
+ clearCsrfToken()
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ 1 * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
+ response.status == HttpServletResponse.SC_OK
+ }
+
+ def "csrf clears on logout"() {
+ setup:
+ CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
+ 1 * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
+ loadConfig(CsrfTokenRepositoryConfig)
+ login()
+ request.method = "POST"
+ request.servletPath = "/logout"
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ 1 * CsrfTokenRepositoryConfig.repo.saveToken(null, _, _)
+ }
+
+ def "csrf clears on login"() {
+ setup:
+ CsrfTokenRepositoryConfig.repo = Mock(CsrfTokenRepository)
+ 1 * CsrfTokenRepositoryConfig.repo.loadToken(_) >> csrfToken
+ loadConfig(CsrfTokenRepositoryConfig)
+ request.method = "POST"
+ request.getSession()
+ request.servletPath = "/login"
+ request.setParameter("username", "user")
+ request.setParameter("password", "password")
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ response.redirectedUrl == "/"
+ 1 * CsrfTokenRepositoryConfig.repo.saveToken(null, _, _)
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class CsrfTokenRepositoryConfig extends WebSecurityConfigurerAdapter {
+ static CsrfTokenRepository repo
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .formLogin()
+ .and()
+ .csrf()
+ .csrfTokenRepository(repo)
+ }
+ @Override
+ protected void registerAuthentication(AuthenticationManagerBuilder auth)
+ throws Exception {
+ auth
+ .inMemoryAuthentication()
+ .withUser("user").password("password").roles("USER")
+ }
+ }
+
+ def "csrf access denied handler"() {
+ setup:
+ AccessDeniedHandlerConfig.deniedHandler = Mock(AccessDeniedHandler)
+ 1 * AccessDeniedHandlerConfig.deniedHandler.handle(_, _, _)
+ loadConfig(AccessDeniedHandlerConfig)
+ clearCsrfToken()
+ request.method = "POST"
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ response.status == HttpServletResponse.SC_OK
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class AccessDeniedHandlerConfig extends WebSecurityConfigurerAdapter {
+ static AccessDeniedHandler deniedHandler
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .exceptionHandling()
+ .accessDeniedHandler(deniedHandler)
+ }
+ }
+
+ def "formLogin requires CSRF token"() {
+ setup:
+ loadConfig(FormLoginConfig)
+ clearCsrfToken()
+ request.setParameter("username", "user")
+ request.setParameter("password", "password")
+ request.servletPath = "/login"
+ request.method = "POST"
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then:
+ response.status == HttpServletResponse.SC_FORBIDDEN
+ currentAuthentication == null
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class FormLoginConfig extends WebSecurityConfigurerAdapter {
+ static AccessDeniedHandler deniedHandler
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .formLogin()
+ }
+ }
+
+ def "logout requires CSRF token"() {
+ setup:
+ loadConfig(LogoutConfig)
+ clearCsrfToken()
+ login()
+ request.servletPath = "/logout"
+ request.method = "POST"
+ when:
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then: "logout is not allowed and user is still authenticated"
+ response.status == HttpServletResponse.SC_FORBIDDEN
+ currentAuthentication != null
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class LogoutConfig extends WebSecurityConfigurerAdapter {
+ static AccessDeniedHandler deniedHandler
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .formLogin()
+ }
+ }
+
+ def "csrf disables POST requests from RequestCache"() {
+ setup:
+ CsrfDisablesPostRequestFromRequestCacheConfig.repo = Mock(CsrfTokenRepository)
+ loadConfig(CsrfDisablesPostRequestFromRequestCacheConfig)
+ request.servletPath = "/some-url"
+ request.requestURI = "/some-url"
+ request.method = "POST"
+ when: "CSRF passes and our session times out"
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then: "sent to the login page"
+ 1 * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
+ response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
+ response.redirectedUrl == "http://localhost/login"
+ when: "authenticate successfully"
+ super.setupWeb(request.session)
+ request.servletPath = "/login"
+ request.setParameter("username","user")
+ request.setParameter("password","password")
+ request.method = "POST"
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then: "sent to default success because we don't want csrf attempts made prior to authentication to pass"
+ 1 * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
+ response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
+ response.redirectedUrl == "/"
+ }
+
+ def "csrf enables GET requests with RequestCache"() {
+ setup:
+ CsrfDisablesPostRequestFromRequestCacheConfig.repo = Mock(CsrfTokenRepository)
+ loadConfig(CsrfDisablesPostRequestFromRequestCacheConfig)
+ request.servletPath = "/some-url"
+ request.requestURI = "/some-url"
+ request.method = "GET"
+ when: "CSRF passes and our session times out"
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then: "sent to the login page"
+ 1 * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
+ response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
+ response.redirectedUrl == "http://localhost/login"
+ when: "authenticate successfully"
+ super.setupWeb(request.session)
+ request.servletPath = "/login"
+ request.setParameter("username","user")
+ request.setParameter("password","password")
+ request.method = "POST"
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then: "sent to original URL since it was a GET"
+ 1 * CsrfDisablesPostRequestFromRequestCacheConfig.repo.loadToken(_) >> csrfToken
+ response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
+ response.redirectedUrl == "http://localhost/some-url"
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class CsrfDisablesPostRequestFromRequestCacheConfig extends WebSecurityConfigurerAdapter {
+ static CsrfTokenRepository repo
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .authorizeRequests()
+ .anyRequest().authenticated()
+ .and()
+ .formLogin()
+ .and()
+ .csrf()
+ .csrfTokenRepository(repo)
+ }
+ @Override
+ protected void registerAuthentication(AuthenticationManagerBuilder auth)
+ throws Exception {
+ auth
+ .inMemoryAuthentication()
+ .withUser("user").password("password").roles("USER")
+ }
+ }
+
+ def clearCsrfToken() {
+ request.removeAllParameters()
+ }
+}
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy
index 9ca1dbf854..5c51ebc448 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultFiltersTests.groovy
@@ -37,7 +37,8 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.context.SecurityContextPersistenceFilter
-import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
+import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
+import org.springframework.security.web.csrf.CsrfFilter
import org.springframework.security.web.header.HeaderWriterFilter
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
@@ -107,17 +108,17 @@ class DefaultFiltersTests extends BaseSpringSpec {
def "FilterChainProxyBuilder ignoring resources"() {
when:
- context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderIgnoringConfig)
+ loadConfig(FilterChainProxyBuilderIgnoringConfig)
then:
- List filterChains = context.getBean(FilterChainProxy).filterChains
- filterChains.size() == 2
- filterChains[0].requestMatcher.pattern == '/resources/**'
- filterChains[0].filters.empty
- filterChains[1].requestMatcher instanceof AnyRequestMatcher
- filterChains[1].filters.collect { it.class } ==
- [WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, LogoutFilter, RequestCacheAwareFilter,
- SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter,
- ExceptionTranslationFilter, FilterSecurityInterceptor ]
+ List filterChains = context.getBean(FilterChainProxy).filterChains
+ filterChains.size() == 2
+ filterChains[0].requestMatcher.pattern == '/resources/**'
+ filterChains[0].filters.empty
+ filterChains[1].requestMatcher instanceof AnyRequestMatcher
+ filterChains[1].filters.collect { it.class } ==
+ [WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, RequestCacheAwareFilter,
+ SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter,
+ ExceptionTranslationFilter, FilterSecurityInterceptor ]
}
@Configuration
@@ -139,17 +140,16 @@ class DefaultFiltersTests extends BaseSpringSpec {
def "DefaultFilters.permitAll()"() {
when:
- context = new AnnotationConfigApplicationContext(DefaultFiltersConfigPermitAll)
+ loadConfig(DefaultFiltersConfigPermitAll)
+ MockHttpServletResponse response = new MockHttpServletResponse()
+ request = new MockHttpServletRequest(servletPath : uri, queryString: query, method:"POST")
+ setupCsrf()
+ springSecurityFilterChain.doFilter(request, response, new MockFilterChain())
then:
- FilterChainProxy filterChain = context.getBean(FilterChainProxy)
-
- expect:
- MockHttpServletResponse response = new MockHttpServletResponse()
- filterChain.doFilter(new MockHttpServletRequest(servletPath : uri, queryString: query), response, new MockFilterChain())
- response.redirectedUrl == null
+ response.redirectedUrl == "/login?logout"
where:
- uri | query
- "/logout" | null
+ uri | query
+ "/logout" | null
}
@Configuration
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy
index 44f1889354..c05ad41fcf 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy
@@ -42,28 +42,16 @@ import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFi
*
*/
public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
- FilterChainProxy springSecurityFilterChain
- MockHttpServletRequest request
- MockHttpServletResponse response
- MockFilterChain chain
-
- def setup() {
- request = new MockHttpServletRequest(method:"GET")
- response = new MockHttpServletResponse()
- chain = new MockFilterChain()
- }
-
def "http/form-login default login generating page"() {
setup:
loadConfig(DefaultLoginPageConfig)
- springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
findFilter(DefaultLoginPageViewFilter)
response.getRedirectedUrl() == "http://localhost/login"
when: "request the login page"
- setup()
+ super.setup()
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
then:
@@ -73,10 +61,11 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {