mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-30 22:28:46 +00:00 
			
		
		
		
	SEC-1574: Add CSRF Support
This commit is contained in:
		
							parent
							
								
									5f35d9e3ec
								
							
						
					
					
						commit
						e9bb9e766e
					
				| @ -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" | ||||
|  | ||||
| @ -133,6 +133,13 @@ | ||||
|       <scope>compile</scope> | ||||
|       <optional>true</optional> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.springframework</groupId> | ||||
|       <artifactId>spring-webmvc</artifactId> | ||||
|       <version>3.2.3.RELEASE</version> | ||||
|       <scope>compile</scope> | ||||
|       <optional>true</optional> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.apache.tomcat</groupId> | ||||
|       <artifactId>tomcat-servlet-api</artifactId> | ||||
|  | ||||
| @ -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"; | ||||
| } | ||||
|  | ||||
| @ -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<Filter>, 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); | ||||
|  | ||||
| @ -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<Defaul | ||||
|         return getOrApply(new ServletApiConfigurer<HttpSecurity>()); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Adds CSRF support | ||||
|      * | ||||
|      * @return the {@link ServletApiConfigurer} for further customizations | ||||
|      * @throws Exception | ||||
|      */ | ||||
|     public CsrfConfigurer<HttpSecurity> csrf() throws Exception { | ||||
|         return getOrApply(new CsrfConfigurer<HttpSecurity>()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Provides logout support. This is automatically applied when using | ||||
|      * {@link WebSecurityConfigurerAdapter}. The default is that accessing | ||||
|  | ||||
| @ -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(); | ||||
|     } | ||||
| } | ||||
| @ -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 { | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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[] {}; | ||||
|     } | ||||
| } | ||||
| @ -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() | ||||
|  | ||||
| @ -30,4 +30,15 @@ import org.springframework.security.web.DefaultSecurityFilterChain; | ||||
|  */ | ||||
| abstract class AbstractHttpConfigurer<B extends HttpSecurityBuilder<B>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> { | ||||
| 
 | ||||
|     /** | ||||
|      * 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(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -39,7 +39,7 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi | ||||
|  * @author  Rob Winch | ||||
|  * @since  3.2 | ||||
|  */ | ||||
| public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,H> { | ||||
| public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> { | ||||
|     private String key; | ||||
|     private AuthenticationProvider authenticationProvider; | ||||
|     private AnonymousAuthenticationFilter authenticationFilter; | ||||
| @ -53,18 +53,6 @@ public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> 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. | ||||
|  | ||||
| @ -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 <a | ||||
|  * href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)" | ||||
|  * >CSRF</a> protection for the methods as specified by | ||||
|  * {@link #requireCsrfProtectionMatcher(RequestMatcher)}. | ||||
|  * | ||||
|  * <h2>Security Filters</h2> | ||||
|  * | ||||
|  * The following Filters are populated | ||||
|  * | ||||
|  * <ul> | ||||
|  * <li>{@link CsrfFilter}</li> | ||||
|  * </ul> | ||||
|  * | ||||
|  * <h2>Shared Objects Created</h2> | ||||
|  * | ||||
|  * No shared objects are created. | ||||
|  * | ||||
|  * <h2>Shared Objects Used</h2> | ||||
|  * | ||||
|  * <ul> | ||||
|  * <li> | ||||
|  * {@link ExceptionHandlingConfigurer#accessDeniedHandler(AccessDeniedHandler)} | ||||
|  * is used to determine how to handle CSRF attempts</li> | ||||
|  * </ul> | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> { | ||||
|     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<H> 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<H> 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<H> exceptionConfig = http.getConfigurer(ExceptionHandlingConfigurer.class); | ||||
|         if(exceptionConfig != null) { | ||||
|             AccessDeniedHandler accessDeniedHandler = exceptionConfig.getAccessDeniedHandler(); | ||||
|             if(accessDeniedHandler != null) { | ||||
|                 filter.setAccessDeniedHandler(accessDeniedHandler); | ||||
|             } | ||||
|         } | ||||
|         LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class); | ||||
|         if(logoutConfigurer != null) { | ||||
|             logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(csrfTokenRepository)); | ||||
|         } | ||||
|         SessionManagementConfigurer<H> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class); | ||||
|         if(sessionConfigurer != null) { | ||||
|             sessionConfigurer.addSessionAuthenticationStrategy(new CsrfAuthenticationStrategy(csrfTokenRepository)); | ||||
|         } | ||||
|         filter = postProcess(filter); | ||||
|         http.addFilter(filter); | ||||
|     } | ||||
| } | ||||
| @ -153,6 +153,15 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>> | ||||
|         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); | ||||
|  | ||||
| @ -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<H extends HttpSecurityBuilder<H>> 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<H extends HttpSecurityBuilder<H>> 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<H> 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<H> logoutRequestMatcher(RequestMatcher logoutRequestMatcher) { | ||||
|         this.logoutRequestMatcher = logoutRequestMatcher; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
| @ -189,7 +200,8 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> 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<H extends HttpSecurityBuilder<H>> 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; | ||||
|     } | ||||
|  | ||||
| @ -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))); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -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<H extends HttpSecurityBuilder<H>> 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<H extends HttpSecurityBuilder<H>> exte | ||||
|         if(result != null) { | ||||
|             return result; | ||||
|         } | ||||
|         return new HttpSessionRequestCache(); | ||||
|         HttpSessionRequestCache defaultCache = new HttpSessionRequestCache(); | ||||
|         defaultCache.setRequestMatcher(new AntPathRequestMatcher("/**", "GET")); | ||||
|         return defaultCache; | ||||
|     } | ||||
| } | ||||
| @ -64,12 +64,6 @@ public final class ServletApiConfigurer<H extends HttpSecurityBuilder<H>> extend | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public H disable() { | ||||
|         getBuilder().removeConfigurer(getClass()); | ||||
|         return getBuilder(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public void configure(H http) throws Exception { | ||||
|  | ||||
| @ -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<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> { | ||||
|     private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = createDefaultSessionFixationProtectionStrategy(); | ||||
|     private SessionAuthenticationStrategy sessionAuthenticationStrategy; | ||||
|     private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<SessionAuthenticationStrategy>(); | ||||
|     private SessionRegistry sessionRegistry = new SessionRegistryImpl(); | ||||
|     private Integer maximumSessions; | ||||
|     private String expiredUrl; | ||||
| @ -173,6 +175,18 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds an additional {@link SessionAuthenticationStrategy} to be used within the {@link CompositeSessionAuthenticationStrategy}. | ||||
|      * | ||||
|      * @param sessionAuthenticationStrategy | ||||
|      * @return the {@link SessionManagementConfigurer} for further | ||||
|      *         customizations | ||||
|      */ | ||||
|     SessionManagementConfigurer<H> addSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) { | ||||
|         this.sessionAuthenticationStrategies.add(sessionAuthenticationStrategy); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public SessionFixationConfigurer sessionFixation() { | ||||
|         return new SessionFixationConfigurer(); | ||||
|     } | ||||
| @ -400,6 +414,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> | ||||
|         if(sessionAuthenticationStrategy != null) { | ||||
|             return sessionAuthenticationStrategy; | ||||
|         } | ||||
|         List<SessionAuthenticationStrategy> delegateStrategies = sessionAuthenticationStrategies; | ||||
|         if(isConcurrentSessionControlEnabled()) { | ||||
|             ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry); | ||||
|             concurrentSessionControlStrategy.setMaximumSessions(maximumSessions); | ||||
| @ -409,11 +424,11 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> | ||||
|             RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry); | ||||
|             registerSessionStrategy = postProcess(registerSessionStrategy); | ||||
| 
 | ||||
|             List<SessionAuthenticationStrategy> 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; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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(); | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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()); | ||||
| 
 | ||||
|  | ||||
| @ -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<BeanMetadataElement> logoutHandlers = new ManagedList<BeanMetadataElement>(); | ||||
| 
 | ||||
|     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<BeanMetadataElement> getLogoutHandlers() { | ||||
|         return logoutHandlers; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -30,6 +30,7 @@ enum SecurityFilters { | ||||
|     /** {@link WebAsyncManagerIntegrationFilter} */ | ||||
|     WEB_ASYNC_MANAGER_FILTER, | ||||
|     HEADERS_FILTER, | ||||
|     CSRF_FILTER, | ||||
|     LOGOUT_FILTER, | ||||
|     X509_FILTER, | ||||
|     PRE_AUTH_FILTER, | ||||
|  | ||||
| @ -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 <http> 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" | ||||
|  | ||||
| @ -1025,6 +1025,7 @@ | ||||
|                </xs:complexType> | ||||
|             </xs:element> | ||||
|             <xs:element ref="security:headers"/> | ||||
|             <xs:element ref="security:csrf"/> | ||||
|          </xs:choice> | ||||
|          <xs:attributeGroup ref="security:http.attlist"/> | ||||
|       </xs:complexType> | ||||
| @ -2238,9 +2239,34 @@ | ||||
|          </xs:annotation> | ||||
|       </xs:attribute> | ||||
|   </xs:attributeGroup> | ||||
|   <xs:element name="csrf"> | ||||
|       <xs:annotation> | ||||
|          <xs:documentation>Element for configuration of the CsrfFilter for protection against CSRF. It also updates | ||||
|                 the default RequestCache to only replay "GET" requests. | ||||
|                 </xs:documentation> | ||||
|       </xs:annotation> | ||||
|       <xs:complexType> | ||||
|          <xs:attributeGroup ref="security:csrf-options.attlist"/> | ||||
|       </xs:complexType> | ||||
|    </xs:element> | ||||
|   <xs:attributeGroup name="csrf-options.attlist"> | ||||
|       <xs:attribute name="request-matcher-ref" type="xs:token"> | ||||
|          <xs:annotation> | ||||
|             <xs:documentation>The RequestMatcher instance to be used to determine if CSRF should be applied. Default is | ||||
|                 any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS" | ||||
|                 </xs:documentation> | ||||
|          </xs:annotation> | ||||
|       </xs:attribute> | ||||
|       <xs:attribute name="token-repository-ref" type="xs:token"> | ||||
|          <xs:annotation> | ||||
|             <xs:documentation>The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository | ||||
|                 </xs:documentation> | ||||
|          </xs:annotation> | ||||
|       </xs:attribute> | ||||
|   </xs:attributeGroup> | ||||
|   <xs:element name="headers"> | ||||
|       <xs:annotation> | ||||
|          <xs:documentation>Element for configuration of the AddHeadersFilter. Enables easy setting for the | ||||
|          <xs:documentation>Element for configuration of the HeaderWritersFilter. Enables easy setting for the | ||||
|                 X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers. | ||||
|                 </xs:documentation> | ||||
|       </xs:annotation> | ||||
| @ -2479,6 +2505,8 @@ | ||||
|          <xs:enumeration value="FIRST"/> | ||||
|          <xs:enumeration value="CHANNEL_FILTER"/> | ||||
|          <xs:enumeration value="CONCURRENT_SESSION_FILTER"/> | ||||
|          <xs:enumeration value="HEADERS_FILTER"/> | ||||
|          <xs:enumeration value="CSRF_FILTER"/> | ||||
|          <xs:enumeration value="SECURITY_CONTEXT_FILTER"/> | ||||
|          <xs:enumeration value="LOGOUT_FILTER"/> | ||||
|          <xs:enumeration value="X509_FILTER"/> | ||||
|  | ||||
| @ -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) | ||||
|     } | ||||
|  | ||||
| @ -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))) | ||||
|     } | ||||
|  | ||||
| @ -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) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -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) | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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() | ||||
|     } | ||||
| } | ||||
| @ -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<DefaultSecurityFilterChain> 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<DefaultSecurityFilterChain> 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 | ||||
|  | ||||
| @ -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 { | ||||
|     <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr> | ||||
|     <tr><td>Password:</td><td><input type='password' name='password'/></td></tr> | ||||
|     <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> | ||||
|     <input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> | ||||
|   </table> | ||||
| </form></body></html>""" | ||||
|         when: "fail to log in" | ||||
|             setup() | ||||
|             super.setup() | ||||
|             request.servletPath = "/login" | ||||
|             request.method = "POST" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
| @ -84,7 +73,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|             response.getRedirectedUrl() == "/login?error" | ||||
|         when: "request the error page" | ||||
|             HttpSession session = request.session | ||||
|             setup() | ||||
|             super.setup() | ||||
|             request.session = session | ||||
|             request.requestURI = "/login" | ||||
|             request.queryString = "error" | ||||
| @ -96,10 +85,11 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr> | ||||
|     <tr><td>Password:</td><td><input type='password' name='password'/></td></tr> | ||||
|     <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> | ||||
|     <input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> | ||||
|   </table> | ||||
| </form></body></html>""" | ||||
|         when: "login success" | ||||
|             setup() | ||||
|             super.setup() | ||||
|             request.servletPath = "/login" | ||||
|             request.method = "POST" | ||||
|             request.parameters.username = ["user"] as String[] | ||||
| @ -112,7 +102,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     def "logout success renders"() { | ||||
|         setup: | ||||
|             loadConfig(DefaultLoginPageConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: "logout success" | ||||
|             request.requestURI = "/login" | ||||
|             request.queryString = "logout" | ||||
| @ -125,6 +114,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr> | ||||
|     <tr><td>Password:</td><td><input type='password' name='password'/></td></tr> | ||||
|     <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> | ||||
|     <input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> | ||||
|   </table> | ||||
| </form></body></html>""" | ||||
|     } | ||||
| @ -144,7 +134,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     def "custom logout success handler prevents rendering"() { | ||||
|         setup: | ||||
|             loadConfig(DefaultLoginPageCustomLogoutSuccessHandlerConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: "logout success" | ||||
|             request.requestURI = "/login" | ||||
|             request.queryString = "logout" | ||||
| @ -172,7 +161,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     def "custom logout success url prevents rendering"() { | ||||
|         setup: | ||||
|             loadConfig(DefaultLoginPageCustomLogoutConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: "logout success" | ||||
|             request.requestURI = "/login" | ||||
|             request.queryString = "logout" | ||||
| @ -200,9 +188,8 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     def "http/form-login default login with remember me"() { | ||||
|         setup: | ||||
|             loadConfig(DefaultLoginPageWithRememberMeConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: "request the login page" | ||||
|             setup() | ||||
|             super.setup() | ||||
|             request.requestURI = "/login" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
| @ -213,6 +200,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     <tr><td>Password:</td><td><input type='password' name='password'/></td></tr> | ||||
|     <tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr> | ||||
|     <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> | ||||
|     <input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> | ||||
|   </table> | ||||
| </form></body></html>""" | ||||
|     } | ||||
| @ -234,7 +222,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     def "http/form-login default login with openid"() { | ||||
|         setup: | ||||
|             loadConfig(DefaultLoginPageWithOpenIDConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: "request the login page" | ||||
|             request.requestURI = "/login" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
| @ -244,6 +231,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     <tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr> | ||||
|     <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> | ||||
|   </table> | ||||
|     <input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> | ||||
| </form></body></html>""" | ||||
|     } | ||||
| 
 | ||||
| @ -262,7 +250,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     def "http/form-login default login with openid, form login, and rememberme"() { | ||||
|         setup: | ||||
|             loadConfig(DefaultLoginPageWithFormLoginOpenIDRememberMeConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: "request the login page" | ||||
|             request.requestURI = "/login" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
| @ -274,6 +261,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     <tr><td>Password:</td><td><input type='password' name='password'/></td></tr> | ||||
|     <tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr> | ||||
|     <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> | ||||
|     <input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> | ||||
|   </table> | ||||
| </form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'> | ||||
|  <table> | ||||
| @ -281,6 +269,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { | ||||
|     <tr><td><input type='checkbox' name='remember-me'></td><td>Remember me on this computer.</td></tr> | ||||
|     <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> | ||||
|   </table> | ||||
|     <input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> | ||||
| </form></body></html>""" | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -41,6 +41,7 @@ import org.springframework.security.web.authentication.logout.LogoutFilter | ||||
| import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy | ||||
| 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.savedrequest.RequestCacheAwareFilter | ||||
| import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter | ||||
| @ -64,7 +65,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec { | ||||
|             filterChains[0].filters.empty | ||||
|             filterChains[1].requestMatcher instanceof AnyRequestMatcher | ||||
|             filterChains[1].filters.collect { it.class.name.contains('$') ? it.class.superclass : it.class } == | ||||
|                     [WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, LogoutFilter, UsernamePasswordAuthenticationFilter, | ||||
|                     [WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, UsernamePasswordAuthenticationFilter, | ||||
|                      RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, | ||||
|                      AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor ] | ||||
| 
 | ||||
| @ -78,7 +79,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec { | ||||
|             !authFilter.requiresAuthentication(new MockHttpServletRequest(servletPath : "/login", method: "GET"), new MockHttpServletResponse()) | ||||
| 
 | ||||
|         and: "SessionFixationProtectionStrategy is configured correctly" | ||||
|             SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy") | ||||
|             SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy").delegateStrategies.find { SessionFixationProtectionStrategy } | ||||
|             sessionStrategy.migrateSessionAttributes | ||||
| 
 | ||||
|         and: "Exception handling is configured correctly" | ||||
| @ -112,11 +113,13 @@ class FormLoginConfigurerTests extends BaseSpringSpec { | ||||
|     def "FormLogin.permitAll()"() { | ||||
|         when: "load formLogin() with permitAll" | ||||
|             context = new AnnotationConfigApplicationContext(FormLoginConfigPermitAll) | ||||
| 
 | ||||
|         then: "the formLogin URLs are granted access" | ||||
|             FilterChainProxy filterChain = context.getBean(FilterChainProxy) | ||||
|             MockHttpServletResponse response = new MockHttpServletResponse() | ||||
|             filterChain.doFilter(new MockHttpServletRequest(servletPath : servletPath, requestURI: servletPath, queryString: query, method: method), response, new MockFilterChain()) | ||||
|             request = new MockHttpServletRequest(servletPath : servletPath, requestURI: servletPath, queryString: query, method: method) | ||||
|             setupCsrf() | ||||
| 
 | ||||
|         then: "the formLogin URLs are granted access" | ||||
|             filterChain.doFilter(request, response, new MockFilterChain()) | ||||
|             response.redirectedUrl == redirectUrl | ||||
| 
 | ||||
|         where: | ||||
|  | ||||
| @ -47,8 +47,11 @@ class LogoutConfigurerTests extends BaseSpringSpec { | ||||
|     def "invoke logout twice does not override"() { | ||||
|         when: | ||||
|             loadConfig(InvokeTwiceDoesNotOverride) | ||||
|             request.method = "POST" | ||||
|             request.servletPath = "/custom/logout" | ||||
|             findFilter(LogoutFilter).doFilter(request,response,chain) | ||||
|         then: | ||||
|             findFilter(LogoutFilter).filterProcessesUrl == "/custom/logout" | ||||
|             response.redirectedUrl == "/login?logout" | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
|  | ||||
| @ -39,35 +39,24 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi | ||||
|  * | ||||
|  */ | ||||
| public class NamespaceHttpBasicTests extends BaseSpringSpec { | ||||
|     FilterChainProxy springSecurityFilterChain | ||||
|     MockHttpServletRequest request | ||||
|     MockHttpServletResponse response | ||||
|     MockFilterChain chain | ||||
| 
 | ||||
|     def setup() { | ||||
|         request = new MockHttpServletRequest() | ||||
|         response = new MockHttpServletResponse() | ||||
|         chain = new MockFilterChain() | ||||
|     } | ||||
| 
 | ||||
|     def "http/http-basic"() { | ||||
|         setup: | ||||
|             loadConfig(HttpBasicConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             response.status == HttpServletResponse.SC_UNAUTHORIZED | ||||
|         when: "fail to log in" | ||||
|             setup() | ||||
|             login("user","invalid") | ||||
|             super.setup() | ||||
|             basicLogin("user","invalid") | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: "unauthorized" | ||||
|             response.status == HttpServletResponse.SC_UNAUTHORIZED | ||||
|             response.getHeader("WWW-Authenticate") == 'Basic realm="Spring Security Application"' | ||||
|         when: "login success" | ||||
|             setup() | ||||
|             login() | ||||
|             super.setup() | ||||
|             basicLogin() | ||||
|         then: "sent to default succes page" | ||||
|             !response.committed | ||||
|     } | ||||
| @ -86,9 +75,8 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec { | ||||
|     def "http@realm"() { | ||||
|         setup: | ||||
|             loadConfig(CustomHttpBasicConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: | ||||
|             login("user","invalid") | ||||
|             basicLogin("user","invalid") | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: "unauthorized" | ||||
|             response.status == HttpServletResponse.SC_UNAUTHORIZED | ||||
| @ -109,7 +97,6 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec { | ||||
|     def "http-basic@authentication-details-source-ref"() { | ||||
|         when: | ||||
|             loadConfig(AuthenticationDetailsSourceHttpBasicConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         then: | ||||
|             findFilter(BasicAuthenticationFilter).authenticationDetailsSource.class == CustomAuthenticationDetailsSource | ||||
|     } | ||||
| @ -128,20 +115,20 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec { | ||||
|     def "http-basic@entry-point-ref"() { | ||||
|         setup: | ||||
|             loadConfig(EntryPointRefHttpBasicConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR | ||||
|         when: "fail to log in" | ||||
|             setup() | ||||
|             login("user","invalid") | ||||
|             super.setup() | ||||
|             basicLogin("user","invalid") | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: "custom" | ||||
|             response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR | ||||
|         when: "login success" | ||||
|             setup() | ||||
|             login() | ||||
|             super.setup() | ||||
|             basicLogin() | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: "sent to default succes page" | ||||
|             !response.committed | ||||
|     } | ||||
| @ -162,7 +149,7 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     def login(String username="user",String password="password") { | ||||
|     def basicLogin(String username="user",String password="password") { | ||||
|         def credentials = username + ":" + password | ||||
|         request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64()) | ||||
|     } | ||||
|  | ||||
| @ -36,6 +36,7 @@ import org.springframework.security.web.util.AnyRequestMatcher | ||||
|  * | ||||
|  */ | ||||
| public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
| 
 | ||||
|     def "http/headers"() { | ||||
|         setup: | ||||
|             loadConfig(HeadersDefaultConfig) | ||||
| @ -48,7 +49,8 @@ public class NamespaceHttpHeadersTests 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] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -68,7 +70,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate', | ||||
|                 'Pragma':'no-cache'] | ||||
|                 'Pragma':'no-cache', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -88,7 +91,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains'] | ||||
|             responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -107,7 +111,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['Strict-Transport-Security': 'max-age=15768000'] | ||||
|             responseHeaders == ['Strict-Transport-Security': 'max-age=15768000', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -128,7 +133,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['X-Frame-Options': 'SAMEORIGIN'] | ||||
|             responseHeaders == ['X-Frame-Options': 'SAMEORIGIN', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -150,7 +156,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['X-Frame-Options': 'ALLOW-FROM https://example.com'] | ||||
|             responseHeaders == ['X-Frame-Options': 'ALLOW-FROM https://example.com', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| @ -171,7 +178,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['X-XSS-Protection': '1; mode=block'] | ||||
|             responseHeaders == ['X-XSS-Protection': '1; mode=block', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -191,7 +199,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['X-XSS-Protection': '1'] | ||||
|             responseHeaders == ['X-XSS-Protection': '1', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -211,7 +220,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['X-Content-Type-Options': 'nosniff'] | ||||
|             responseHeaders == ['X-Content-Type-Options': 'nosniff', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -233,7 +243,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             responseHeaders == ['customHeaderName': 'customHeaderValue'] | ||||
|             responseHeaders == ['customHeaderName': 'customHeaderValue', | ||||
|                 'X-CSRF-TOKEN' : csrfToken.token] | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
| @ -245,4 +256,5 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec { | ||||
|                     .addHeaderWriter(new StaticHeadersWriter("customHeaderName", "customHeaderValue")) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -71,23 +71,13 @@ import org.springframework.security.web.util.RequestMatcher | ||||
|  * | ||||
|  */ | ||||
| public class NamespaceHttpLogoutTests extends BaseSpringSpec { | ||||
|     FilterChainProxy springSecurityFilterChain | ||||
|     MockHttpServletRequest request | ||||
|     MockHttpServletResponse response | ||||
|     MockFilterChain chain | ||||
| 
 | ||||
|     def setup() { | ||||
|         request = new MockHttpServletRequest() | ||||
|         response = new MockHttpServletResponse() | ||||
|         chain = new MockFilterChain() | ||||
|     } | ||||
| 
 | ||||
|     def "http/logout"() { | ||||
|         setup: | ||||
|             loadConfig(HttpLogoutConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|             login() | ||||
|             request.setRequestURI("/logout") | ||||
|             request.servletPath = "/logout" | ||||
|             request.method = "POST" | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
| @ -106,9 +96,9 @@ public class NamespaceHttpLogoutTests extends BaseSpringSpec { | ||||
|     def "http/logout custom"() { | ||||
|         setup: | ||||
|             loadConfig(CustomHttpLogoutConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|             login() | ||||
|             request.setRequestURI("/custom-logout") | ||||
|             request.servletPath = "/custom-logout" | ||||
|             request.method = "POST" | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
| @ -135,9 +125,9 @@ public class NamespaceHttpLogoutTests extends BaseSpringSpec { | ||||
|     def "http/logout@success-handler-ref"() { | ||||
|         setup: | ||||
|             loadConfig(SuccessHandlerRefHttpLogoutConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|             login() | ||||
|             request.setRequestURI("/logout") | ||||
|             request.servletPath = "/logout" | ||||
|             request.method = "POST" | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|  | ||||
| @ -44,21 +44,9 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS | ||||
|  * | ||||
|  */ | ||||
| public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec { | ||||
|     FilterChainProxy springSecurityFilterChain | ||||
|     MockHttpServletRequest request | ||||
|     MockHttpServletResponse response | ||||
|     MockFilterChain chain | ||||
| 
 | ||||
|     def setup() { | ||||
|         request = new MockHttpServletRequest() | ||||
|         response = new MockHttpServletResponse() | ||||
|         chain = new MockFilterChain() | ||||
|     } | ||||
| 
 | ||||
|     def "http/openid-login"() { | ||||
|         when: | ||||
|             loadConfig(OpenIDLoginConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         then: | ||||
|             findFilter(OpenIDAuthenticationFilter).consumer.class == OpenID4JavaConsumer | ||||
|         when: | ||||
| @ -66,7 +54,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec { | ||||
|         then: | ||||
|             response.getRedirectedUrl() == "http://localhost/login" | ||||
|         when: "fail to log in" | ||||
|             setup() | ||||
|             super.setup() | ||||
|             request.servletPath = "/login/openid" | ||||
|             request.method = "POST" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
| @ -89,7 +77,6 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec { | ||||
|     def "http/openid-login/attribute-exchange"() { | ||||
|         when: | ||||
|             loadConfig(OpenIDLoginAttributeExchangeConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|             OpenID4JavaConsumer consumer = findFilter(OpenIDAuthenticationFilter).consumer | ||||
|         then: | ||||
|             consumer.class == OpenID4JavaConsumer | ||||
| @ -117,7 +104,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec { | ||||
|         then: | ||||
|             response.getRedirectedUrl() == "http://localhost/login" | ||||
|         when: "fail to log in" | ||||
|             setup() | ||||
|             super.setup() | ||||
|             request.servletPath = "/login/openid" | ||||
|             request.method = "POST" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
| @ -165,13 +152,12 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec { | ||||
|     def "http/openid-login custom"() { | ||||
|         setup: | ||||
|             loadConfig(OpenIDLoginCustomConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             response.getRedirectedUrl() == "http://localhost/authentication/login" | ||||
|         when: "fail to log in" | ||||
|             setup() | ||||
|             super.setup() | ||||
|             request.servletPath = "/authentication/login/process" | ||||
|             request.method = "POST" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
| @ -200,7 +186,6 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             OpenIDLoginCustomRefsConfig.AUDS = Mock(AuthenticationUserDetailsService) | ||||
|             loadConfig(OpenIDLoginCustomRefsConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         then: "CustomWebAuthenticationDetailsSource is used" | ||||
|             findFilter(OpenIDAuthenticationFilter).authenticationDetailsSource.class == CustomWebAuthenticationDetailsSource | ||||
|             findAuthenticationProvider(OpenIDAuthenticationProvider).userDetailsService == OpenIDLoginCustomRefsConfig.AUDS | ||||
|  | ||||
| @ -55,22 +55,10 @@ import org.springframework.test.util.ReflectionTestUtils | ||||
|  * | ||||
|  */ | ||||
| public class NamespaceHttpX509Tests extends BaseSpringSpec { | ||||
|     FilterChainProxy springSecurityFilterChain | ||||
|     MockHttpServletRequest request | ||||
|     MockHttpServletResponse response | ||||
|     MockFilterChain chain | ||||
| 
 | ||||
|     def setup() { | ||||
|         request = new MockHttpServletRequest() | ||||
|         response = new MockHttpServletResponse() | ||||
|         chain = new MockFilterChain() | ||||
|     } | ||||
| 
 | ||||
|     def "http/x509 can authenticate"() { | ||||
|         setup: | ||||
|             X509Certificate certificate = loadCert("rod.cer") | ||||
|             loadConfig(X509Config) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: | ||||
|             request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) | ||||
|             springSecurityFilterChain.doFilter(request, response, chain); | ||||
| @ -148,7 +136,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec { | ||||
|         setup: | ||||
|             X509Certificate certificate = loadCert("rodatexampledotcom.cer") | ||||
|             loadConfig(SubjectPrincipalRegexConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: | ||||
|             request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) | ||||
|             springSecurityFilterChain.doFilter(request, response, chain); | ||||
| @ -182,7 +169,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec { | ||||
|         setup: | ||||
|             X509Certificate certificate = loadCert("rodatexampledotcom.cer") | ||||
|             loadConfig(UserDetailsServiceRefConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: | ||||
|             request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) | ||||
|             springSecurityFilterChain.doFilter(request, response, chain); | ||||
| @ -216,7 +202,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec { | ||||
|         setup: | ||||
|             X509Certificate certificate = loadCert("rodatexampledotcom.cer") | ||||
|             loadConfig(AuthenticationUserDetailsServiceConfig) | ||||
|             springSecurityFilterChain = context.getBean(FilterChainProxy) | ||||
|         when: | ||||
|             request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] ) | ||||
|             springSecurityFilterChain.doFilter(request, response, chain); | ||||
|  | ||||
| @ -81,8 +81,10 @@ public class NamespaceRememberMeTests extends BaseSpringSpec { | ||||
|         when: "logout" | ||||
|             super.setup() | ||||
|             request.setSession(session) | ||||
|             super.setupCsrf() | ||||
|             request.setCookies(rememberMeCookie) | ||||
|             request.requestURI = "/logout" | ||||
|             request.servletPath = "/logout" | ||||
|             request.method = "POST" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|             rememberMeCookie = getRememberMeCookie() | ||||
|         then: "logout cookie expired" | ||||
|  | ||||
| @ -41,7 +41,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             loadConfig(SessionManagementConfig) | ||||
|         then: | ||||
|             findFilter(SessionManagementFilter).sessionAuthenticationStrategy instanceof SessionFixationProtectionStrategy | ||||
|             findSessionAuthenticationStrategy(SessionFixationProtectionStrategy) | ||||
|     } | ||||
| 
 | ||||
|     @EnableWebSecurity | ||||
| @ -91,7 +91,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             loadConfig(RefsSessionManagementConfig) | ||||
|         then: | ||||
|             findFilter(SessionManagementFilter).sessionAuthenticationStrategy ==  RefsSessionManagementConfig.SAS | ||||
|             findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it ==  RefsSessionManagementConfig.SAS } | ||||
|     } | ||||
| 
 | ||||
|     @EnableWebSecurity | ||||
| @ -110,7 +110,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             loadConfig(SFPNoneSessionManagementConfig) | ||||
|         then: | ||||
|             findFilter(SessionManagementFilter).sessionAuthenticationStrategy.class ==  NullAuthenticatedSessionStrategy | ||||
|             findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it instanceof  NullAuthenticatedSessionStrategy } | ||||
|     } | ||||
| 
 | ||||
|     @EnableWebSecurity | ||||
| @ -128,7 +128,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             loadConfig(SFPMigrateSessionManagementConfig) | ||||
|         then: | ||||
|             findFilter(SessionManagementFilter).sessionAuthenticationStrategy.migrateSessionAttributes | ||||
|             findSessionAuthenticationStrategy(SessionFixationProtectionStrategy).migrateSessionAttributes | ||||
|     } | ||||
| 
 | ||||
|     @EnableWebSecurity | ||||
| @ -145,7 +145,11 @@ class NamespaceSessionManagementTests extends BaseSpringSpec { | ||||
|         when: | ||||
|             loadConfig(SFPNewSessionSessionManagementConfig) | ||||
|         then: | ||||
|             !findFilter(SessionManagementFilter).sessionAuthenticationStrategy.migrateSessionAttributes | ||||
|             !findSessionAuthenticationStrategy(SessionFixationProtectionStrategy).migrateSessionAttributes | ||||
|     } | ||||
| 
 | ||||
|     def findSessionAuthenticationStrategy(def c) { | ||||
|         findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it.class.isAssignableFrom(c) } | ||||
|     } | ||||
| 
 | ||||
|     @EnableWebSecurity | ||||
|  | ||||
| @ -26,6 +26,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe | ||||
| import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter | ||||
| import org.springframework.security.web.AuthenticationEntryPoint | ||||
| import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; | ||||
| import org.springframework.security.web.csrf.CsrfLogoutHandler; | ||||
| import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter | ||||
| 
 | ||||
| /** | ||||
| @ -59,7 +60,7 @@ class ServletApiConfigurerTests extends BaseSpringSpec { | ||||
|         and: "requestFactory != null" | ||||
|             filter.requestFactory != null | ||||
|         and: "logoutHandlers populated" | ||||
|             filter.logoutHandlers.collect { it.class } == [SecurityContextLogoutHandler] | ||||
|             filter.logoutHandlers.collect { it.class } == [CsrfLogoutHandler, SecurityContextLogoutHandler] | ||||
|     } | ||||
| 
 | ||||
|     @CompileStatic | ||||
|  | ||||
| @ -57,8 +57,11 @@ abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests { | ||||
|     } | ||||
| 
 | ||||
|     List getFilters(String url) { | ||||
|         def fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY); | ||||
|         return fcp.getFilters(url) | ||||
|         springSecurityFilterChain.getFilters(url) | ||||
|     } | ||||
| 
 | ||||
|     Filter getSpringSecurityFilterChain() { | ||||
|         appContext.getBean(BeanIds.FILTER_CHAIN_PROXY) | ||||
|     } | ||||
| 
 | ||||
|     FilterInvocation createFilterinvocation(String path, String method) { | ||||
|  | ||||
| @ -0,0 +1,257 @@ | ||||
| /* | ||||
|  * 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 static org.mockito.Mockito.* | ||||
| import static org.mockito.Matchers.* | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse | ||||
| 
 | ||||
| import org.spockframework.compiler.model.WhenBlock; | ||||
| import org.springframework.mock.web.MockFilterChain | ||||
| import org.springframework.mock.web.MockHttpServletRequest | ||||
| import org.springframework.mock.web.MockHttpServletResponse | ||||
| import org.springframework.security.access.AccessDeniedException; | ||||
| import org.springframework.security.config.annotation.web.configurers.CsrfConfigurerTests.CsrfTokenRepositoryConfig; | ||||
| import org.springframework.security.config.annotation.web.configurers.CsrfConfigurerTests.RequireCsrfProtectionMatcherConfig | ||||
| import org.springframework.security.web.access.AccessDeniedHandler; | ||||
| import org.springframework.security.web.csrf.CsrfFilter | ||||
| import org.springframework.security.web.csrf.CsrfToken; | ||||
| 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 CsrfConfigTests extends AbstractHttpConfigTests { | ||||
|     MockHttpServletRequest request = new MockHttpServletRequest() | ||||
|     MockHttpServletResponse response = new MockHttpServletResponse() | ||||
|     MockFilterChain chain = new MockFilterChain() | ||||
| 
 | ||||
|     def 'no http csrf filter by default'() { | ||||
|         when: | ||||
|             httpAutoConfig { | ||||
|             } | ||||
|             createAppContext() | ||||
|         then: | ||||
|             !getFilter(CsrfFilter) | ||||
|     } | ||||
| 
 | ||||
|     @Unroll | ||||
|     def 'csrf defaults'() { | ||||
|         setup: | ||||
|             httpAutoConfig { | ||||
|                 'csrf'() | ||||
|             } | ||||
|             createAppContext() | ||||
|         when: | ||||
|             request.method = httpMethod | ||||
|             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: | ||||
|             httpAutoConfig { | ||||
|                 'csrf'() | ||||
|             } | ||||
|             createAppContext() | ||||
|         then: | ||||
|             appContext.getBean("requestDataValueProcessor",CsrfRequestDataValueProcessor) | ||||
|     } | ||||
| 
 | ||||
|     def 'csrf custom AccessDeniedHandler'() { | ||||
|         setup: | ||||
|             httpAutoConfig { | ||||
|                 'access-denied-handler'(ref:'adh') | ||||
|                 'csrf'() | ||||
|             } | ||||
|             mockBean(AccessDeniedHandler,'adh') | ||||
|             createAppContext() | ||||
|             AccessDeniedHandler adh = appContext.getBean(AccessDeniedHandler) | ||||
|             request.method = "POST" | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             verify(adh).handle(any(HttpServletRequest),any(HttpServletResponse),any(AccessDeniedException)) | ||||
|             response.status == HttpServletResponse.SC_OK // our mock doesn't do anything | ||||
|     } | ||||
| 
 | ||||
|     def "csrf disables posts for RequestCache"() { | ||||
|         setup: | ||||
|             httpAutoConfig { | ||||
|                 'csrf'('token-repository-ref':'repo') | ||||
|                 'intercept-url'(pattern:"/**",access:'ROLE_USER') | ||||
|             } | ||||
|             mockBean(CsrfTokenRepository,'repo') | ||||
|             createAppContext() | ||||
|             CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository) | ||||
|             CsrfToken token = new CsrfToken("X-CSRF-TOKEN","_csrf", "abc") | ||||
|             when(repo.loadToken(any(HttpServletRequest))).thenReturn(token) | ||||
|             request.setParameter(token.parameterName,token.token) | ||||
|             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" | ||||
|             response.status == HttpServletResponse.SC_MOVED_TEMPORARILY | ||||
|             response.redirectedUrl == "http://localhost/spring_security_login" | ||||
|         when: "authenticate successfully" | ||||
|             response = new MockHttpServletResponse() | ||||
|             request = new MockHttpServletRequest(session: request.session) | ||||
|             request.requestURI = "/j_spring_security_check" | ||||
|             request.setParameter(token.parameterName,token.token) | ||||
|             request.setParameter("j_username","user") | ||||
|             request.setParameter("j_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" | ||||
|             response.status == HttpServletResponse.SC_MOVED_TEMPORARILY | ||||
|             response.redirectedUrl == "/" | ||||
|     } | ||||
| 
 | ||||
|     def "csrf enables gets for RequestCache"() { | ||||
|         setup: | ||||
|             httpAutoConfig { | ||||
|                 'csrf'('token-repository-ref':'repo') | ||||
|                 'intercept-url'(pattern:"/**",access:'ROLE_USER') | ||||
|             } | ||||
|             mockBean(CsrfTokenRepository,'repo') | ||||
|             createAppContext() | ||||
|             CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository) | ||||
|             CsrfToken token = new CsrfToken("X-CSRF-TOKEN","_csrf", "abc") | ||||
|             when(repo.loadToken(any(HttpServletRequest))).thenReturn(token) | ||||
|             request.setParameter(token.parameterName,token.token) | ||||
|             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" | ||||
|             response.status == HttpServletResponse.SC_MOVED_TEMPORARILY | ||||
|             response.redirectedUrl == "http://localhost/spring_security_login" | ||||
|         when: "authenticate successfully" | ||||
|             response = new MockHttpServletResponse() | ||||
|             request = new MockHttpServletRequest(session: request.session) | ||||
|             request.requestURI = "/j_spring_security_check" | ||||
|             request.setParameter(token.parameterName,token.token) | ||||
|             request.setParameter("j_username","user") | ||||
|             request.setParameter("j_password","password") | ||||
|             request.method = "POST" | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: "sent to original URL since it was a GET" | ||||
|             response.status == HttpServletResponse.SC_MOVED_TEMPORARILY | ||||
|             response.redirectedUrl == "http://localhost/some-url" | ||||
|     } | ||||
| 
 | ||||
|     def "csrf requireCsrfProtectionMatcher"() { | ||||
|         setup: | ||||
|             httpAutoConfig { | ||||
|                 'csrf'('request-matcher-ref':'matcher') | ||||
|             } | ||||
|             mockBean(RequestMatcher,'matcher') | ||||
|             createAppContext() | ||||
|             RequestMatcher matcher = appContext.getBean("matcher",RequestMatcher) | ||||
|         when: | ||||
|             when(matcher.matches(any(HttpServletRequest))).thenReturn(false) | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             response.status == HttpServletResponse.SC_OK | ||||
|         when: | ||||
|             when(matcher.matches(any(HttpServletRequest))).thenReturn(true) | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             response.status == HttpServletResponse.SC_FORBIDDEN | ||||
|     } | ||||
| 
 | ||||
|     def "csrf csrfTokenRepository"() { | ||||
|         setup: | ||||
|             httpAutoConfig { | ||||
|                 'csrf'('token-repository-ref':'repo') | ||||
|             } | ||||
|             mockBean(CsrfTokenRepository,'repo') | ||||
|             createAppContext() | ||||
|             CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository) | ||||
|             CsrfToken token = new CsrfToken("X-CSRF-TOKEN","_csrf", "abc") | ||||
|             when(repo.loadToken(any(HttpServletRequest))).thenReturn(token) | ||||
|             request.setParameter(token.parameterName,token.token) | ||||
|             request.method = "POST" | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             response.status == HttpServletResponse.SC_OK | ||||
|         when: | ||||
|             request.setParameter(token.parameterName,token.token+"INVALID") | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             response.status == HttpServletResponse.SC_FORBIDDEN | ||||
|     } | ||||
| 
 | ||||
|     def "csrf clears on login"() { | ||||
|         setup: | ||||
|             httpAutoConfig { | ||||
|                 'csrf'('token-repository-ref':'repo') | ||||
|             } | ||||
|             mockBean(CsrfTokenRepository,'repo') | ||||
|             createAppContext() | ||||
|             CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository) | ||||
|             CsrfToken token = new CsrfToken("X-CSRF-TOKEN","_csrf", "abc") | ||||
|             when(repo.loadToken(any(HttpServletRequest))).thenReturn(token) | ||||
|             request.setParameter(token.parameterName,token.token) | ||||
|             request.method = "POST" | ||||
|             request.setParameter("j_username","user") | ||||
|             request.setParameter("j_password","password") | ||||
|             request.requestURI = "/j_spring_security_check" | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             verify(repo).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse)) | ||||
|     } | ||||
| 
 | ||||
|     def "csrf clears on logout"() { | ||||
|         setup: | ||||
|             httpAutoConfig { | ||||
|                 'csrf'('token-repository-ref':'repo') | ||||
|             } | ||||
|             mockBean(CsrfTokenRepository,'repo') | ||||
|             createAppContext() | ||||
|             CsrfTokenRepository repo = appContext.getBean("repo",CsrfTokenRepository) | ||||
|             CsrfToken token = new CsrfToken("X-CSRF-TOKEN","_csrf", "abc") | ||||
|             when(repo.loadToken(any(HttpServletRequest))).thenReturn(token) | ||||
|             request.setParameter(token.parameterName,token.token) | ||||
|             request.method = "POST" | ||||
|             request.requestURI = "/j_spring_security_logout" | ||||
|         when: | ||||
|             springSecurityFilterChain.doFilter(request,response,chain) | ||||
|         then: | ||||
|             verify(repo).saveToken(eq(null),any(HttpServletRequest), any(HttpServletResponse)) | ||||
|     } | ||||
| } | ||||
| @ -275,9 +275,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests { | ||||
|             httpAutoConfig { | ||||
|                 'session-management'('session-authentication-strategy-ref':'ss') | ||||
|             } | ||||
|             xml.'b:bean'(id: 'ss', 'class': Mockito.class.name, 'factory-method':'mock') { | ||||
|                 'b:constructor-arg'(value : SessionAuthenticationStrategy.class.name) | ||||
|             } | ||||
|             mockBean(SessionAuthenticationStrategy,'ss') | ||||
|             createAppContext() | ||||
| 
 | ||||
|             MockHttpServletRequest request = new MockHttpServletRequest(); | ||||
|  | ||||
| @ -0,0 +1,85 @@ | ||||
| /* | ||||
|  * 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 static org.fest.assertions.Assertions.assertThat; | ||||
| import static org.mockito.Matchers.any; | ||||
| import static org.mockito.Matchers.eq; | ||||
| import static org.powermock.api.mockito.PowerMockito.spy; | ||||
| import static org.powermock.api.mockito.PowerMockito.when; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.powermock.core.classloader.annotations.PrepareForTest; | ||||
| import org.powermock.modules.junit4.PowerMockRunner; | ||||
| import org.springframework.context.ConfigurableApplicationContext; | ||||
| import org.springframework.context.annotation.AnnotationConfigApplicationContext; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 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.util.ClassUtils; | ||||
| 
 | ||||
| /** | ||||
|  * @author Rob Winch | ||||
|  * | ||||
|  */ | ||||
| @RunWith(PowerMockRunner.class) | ||||
| @PrepareForTest({ClassUtils.class}) | ||||
| public class CsrfConfigurerNoWebMvcTests { | ||||
|     ConfigurableApplicationContext context; | ||||
| 
 | ||||
|     @After | ||||
|     public void teardown() { | ||||
|         if(context != null) { | ||||
|             context.close(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void missingDispatcherServletPreventsCsrfRequestDataValueProcessor() { | ||||
|         spy(ClassUtils.class); | ||||
|         when(ClassUtils.isPresent(eq("org.springframework.web.servlet.DispatcherServlet"), any(ClassLoader.class))).thenReturn(false); | ||||
| 
 | ||||
|         loadContext(CsrfDefaultsConfig.class); | ||||
| 
 | ||||
|         assertThat(context.containsBeanDefinition("requestDataValueProcessor")).isFalse(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void findDispatcherServletPreventsCsrfRequestDataValueProcessor() { | ||||
|         loadContext(CsrfDefaultsConfig.class); | ||||
| 
 | ||||
|         assertThat(context.containsBeanDefinition("requestDataValueProcessor")).isTrue(); | ||||
|     } | ||||
| 
 | ||||
|     @EnableWebSecurity | ||||
|     @Configuration | ||||
|     static class CsrfDefaultsConfig extends WebSecurityConfigurerAdapter { | ||||
| 
 | ||||
|         @Override | ||||
|         protected void configure(HttpSecurity http) throws Exception { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void loadContext(Class<?> configs) { | ||||
|         AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); | ||||
|         annotationConfigApplicationContext.register(configs); | ||||
|         annotationConfigApplicationContext.refresh(); | ||||
|         this.context = annotationConfigApplicationContext; | ||||
|     } | ||||
| } | ||||
| @ -49,6 +49,8 @@ import org.springframework.security.core.Authentication; | ||||
| import org.springframework.security.core.context.SecurityContextImpl; | ||||
| 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 org.springframework.util.ReflectionUtils; | ||||
| 
 | ||||
| 
 | ||||
| @ -94,6 +96,8 @@ public class SessionManagementConfigurerServlet31Tests { | ||||
|         request.setMethod("POST"); | ||||
|         request.setParameter("username", "user"); | ||||
|         request.setParameter("password", "password"); | ||||
|         CsrfToken token = new HttpSessionCsrfTokenRepository().generateAndSaveToken(request, response); | ||||
|         request.setParameter(token.getParameterName(),token.getToken()); | ||||
|         when(ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId")).thenReturn(method); | ||||
| 
 | ||||
|         loadConfig(SessionManagementDefaultSessionFixationServlet31Config.class); | ||||
|  | ||||
| @ -89,6 +89,14 @@ The <<security-config-java,`SecurityConfig`>> will: | ||||
| * Allow the user with the *Username* _user_ and the *Password* _password_ to authenticate with form based authentication | ||||
| * Allow the user with the *Username* _user_ and the *Password* _password_ to authenticate with HTTP basic authentication | ||||
| * Allow the user to logout | ||||
| * http://en.wikipedia.org/wiki/Cross-site_request_forgery[CSRF attack] prevention | ||||
| * http://en.wikipedia.org/wiki/Session_fixation[Session Fixation] protection | ||||
| * Security Header integration | ||||
| ** http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security[HTTP Strict Transport Security] for secure requests | ||||
| ** http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx[X-Content-Type-Options] integration | ||||
| ** Cache Control (can be overridden later by your application to allow caching of your static resources) | ||||
| ** http://msdn.microsoft.com/en-us/library/dd565647(v=vs.85).aspx[X-XSS-Protection] integration | ||||
| ** X-Frame-Options integration to help prevent http://en.wikipedia.org/wiki/Clickjacking[Clickjacking] | ||||
| * Integrate with the following Servlet API methods | ||||
| ** http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getRemoteUser()[HttpServletRequest#getRemoteUser()] | ||||
| ** http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal()[HttpServletRequest.html#getUserPrincipal()] | ||||
|  | ||||
| @ -112,10 +112,12 @@ Now that we can view the user name, let's update the application to allow loggin | ||||
| [subs="verbatim,quotes"] | ||||
| ---- | ||||
| <div class="nav-collapse collapse"> | ||||
|   *<c:url var="logoutUrl" value="/logout"/> | ||||
|   <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"> | ||||
|     <input type="submit" value="Log out" /> | ||||
|   </form:form>* | ||||
|   <p class="navbar-text pull-right"> | ||||
|     <c:out value="${pageContext.request.remoteUser}"/> | ||||
|     *<c:url var="logoutUrl" value="/logout"/> | ||||
|     <a href="${logoutUrl}">Log out</a>* | ||||
|   </p> | ||||
|   <ul class="nav"> | ||||
|     <c:url var="inboxUrl" value="/"/> | ||||
| @ -125,8 +127,12 @@ Now that we can view the user name, let's update the application to allow loggin | ||||
|   </ul> | ||||
| </div> | ||||
| ---- | ||||
| In order to help protect against http://en.wikipedia.org/wiki/Cross-site_request_forgery[CSRF attacks], by default, Spring Security Java Configuration log out requires: | ||||
| 
 | ||||
| Refresh the page at http://localhost:8080/sample/ and you will see the log out link. Click the link and see that the application logs you out successfully. | ||||
| * the HTTP method must be a POST | ||||
| * the CSRF token must be added to the request. Since we are using Spring MVC, the CSRF token is automatically added as a hidden input for you (view the source to see it). If you were not using Spring MVC, you can access the CsrfToken on the ServletRequest using the attribute _csrf | ||||
| 
 | ||||
| Refresh the page at http://localhost:8080/sample/ and you will see the log out button. Click the button and see that the application logs you out successfully. | ||||
| 
 | ||||
| include::hello-includes/basic-authentication.asc[] | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| = Hello Spring Security Java Config | ||||
| :author: Rob Winch | ||||
| :starter-appname: insecure  | ||||
| :starter-appname: insecure | ||||
| :completed-appname: helloworld-jc | ||||
| :verify-starter-app-include: hello-includes/verify-insecure-app.asc | ||||
| 
 | ||||
| @ -77,18 +77,24 @@ Now that we can view the user name, let's update the application to allow loggin | ||||
| <body> | ||||
|   <div class="container"> | ||||
|     <h1>This is secured!</h1> | ||||
|     <p> | ||||
|       Hello <b><c:out value="${pageContext.request.remoteUser}"/></b> | ||||
|     </p> | ||||
|     <c:url var="logoutUrl" value="/logout"/> | ||||
|     <p> | ||||
|       Hello <b><c:out value="${pageContext.request.remoteUser}"/></b>  | ||||
|     </p> | ||||
|     <p> | ||||
|       <a href="${logoutUrl}">Click here</a> to log out. | ||||
|     </p> | ||||
|     <form class="form-inline" action="${logoutUrl}" method="post"> | ||||
|       <input type="submit" value="Log out" /> | ||||
|       <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> | ||||
|     </form> | ||||
|   </div> | ||||
| </body> | ||||
| ---- | ||||
| 
 | ||||
| Refresh the page at http://localhost:8080/sample/ and you will see the log out link. Click the link and see that the application logs you out successfully. | ||||
| In order to help protect against http://en.wikipedia.org/wiki/Cross-site_request_forgery[CSRF attacks], by default, Spring Security Java Configuration log out requires: | ||||
| 
 | ||||
| * the HTTP method must be a POST | ||||
| * the CSRF token must be added to the request You can access it on the ServletRequest using the attribute _csrf as illustrated above. If you were using Spring MVC, the CSRF token is automatically added as a hidden input for you. | ||||
| 
 | ||||
| Refresh the page at http://localhost:8080/sample/ and you will see the log out button. Click the logout button and see that the application logs you out successfully. | ||||
| 
 | ||||
| include::hello-includes/basic-authentication.asc[] | ||||
| 
 | ||||
|  | ||||
| @ -211,6 +211,7 @@ | ||||
|                 <itemizedlist> | ||||
|                     <listitem><link xlink:href="#nsa-access-denied-handler">access-denied-handler</link></listitem> | ||||
|                     <listitem><link xlink:href="#nsa-anonymous">anonymous</link></listitem> | ||||
|                     <listitem><link xlink:href="#nsa-csrf">csrf</link></listitem> | ||||
|                     <listitem><link xlink:href="#nsa-custom-filter">custom-filter</link></listitem> | ||||
|                     <listitem><link xlink:href="#nsa-expression-handler">expression-handler</link></listitem> | ||||
|                     <listitem><link xlink:href="#nsa-form-login">form-login</link></listitem> | ||||
| @ -518,6 +519,30 @@ | ||||
|                 </section> | ||||
|             </section> | ||||
|         </section> | ||||
|         <section xml:id="nsa-csrf"> | ||||
|             <title><literal><csrf></literal></title> | ||||
|             <para>This element will add <a href="">CSRF</a> to the application. It also updates the default RequestCache | ||||
|                 to only replay "GET" requests upon successful authentication.</para> | ||||
|             <section xml:id="nsa-csrf-parents"> | ||||
|                 <title>Parent Elements of <literal><csrf></literal></title> | ||||
|                 <itemizedlist> | ||||
|                     <listitem><link xlink:href="#nsa-http">http</link></listitem> | ||||
|                 </itemizedlist> | ||||
|             </section> | ||||
|             <section xml:id="nsa-csrf-attributes"> | ||||
|                 <title><literal><csrf></literal> Attributes</title> | ||||
|                 <section xml:id="nsa-csrf-token-repository-ref"> | ||||
|                     <title><literal>token-repository-ref</literal></title> | ||||
|                     <para>The CsrfTokenRepository to use. The default is | ||||
|                         <classname>HttpSessionCsrfTokenRepository</classname>.</para> | ||||
|                 </section> | ||||
|                 <section xml:id="nsa-csrf-request-matcher-ref"> | ||||
|                     <title><literal>request-matcher-ref</literal></title> | ||||
|                     <para>The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any | ||||
|                         HTTP method except "GET", "TRACE", "HEAD", "OPTIONS".</para> | ||||
|                 </section> | ||||
|             </section> | ||||
|         </section> | ||||
|         <section xml:id="nsa-custom-filter"> | ||||
|             <title><literal><custom-filter></literal></title> | ||||
|             <para>This element is used to add a filter to the filter chain. It doesn't create any | ||||
|  | ||||
| @ -716,9 +716,14 @@ List<OpenIDAttribute> attributes = token.getAttributes();</programlisting>The | ||||
|                         </row> | ||||
|                         <row> | ||||
|                             <entry>HEADERS_FILTER</entry> | ||||
|                             <entry><literal>HeadersFilter</literal> </entry> | ||||
|                             <entry><literal>HeaderWriterFilter</literal> </entry> | ||||
|                             <entry><literal>http/headers</literal></entry> | ||||
|                         </row> | ||||
|                         <row> | ||||
|                             <entry>CSRF_FILTER</entry> | ||||
|                             <entry><literal>CsrfFilter</literal> </entry> | ||||
|                             <entry><literal>http/csrf</literal></entry> | ||||
|                         </row> | ||||
|                         <row> | ||||
|                             <entry> LOGOUT_FILTER </entry> | ||||
|                             <entry><literal>LogoutFilter</literal></entry> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,13 +103,16 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
|                 <c:url var="composeUrl" value="/?form"/> | ||||
|                 <li><a href="${composeUrl}">Compose</a></li> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <li><a href="${logoutUrl}">Log out</a></li> | ||||
|               </ul> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -22,8 +22,7 @@ dependencies { | ||||
|             "javax.servlet:jstl:$jstlVersion", | ||||
|             "hsqldb:hsqldb:$hsqlVersion", | ||||
|             "org.slf4j:jcl-over-slf4j:$slf4jVersion", | ||||
|             "ch.qos.logback:logback-classic:$logbackVersion" | ||||
| 
 | ||||
|     optional "net.sf.ehcache:ehcache:$ehcacheVersion" | ||||
|             "ch.qos.logback:logback-classic:$logbackVersion", | ||||
|             "net.sf.ehcache:ehcache:$ehcacheVersion" | ||||
| 
 | ||||
| } | ||||
| @ -123,13 +123,6 @@ | ||||
|       <scope>compile</scope> | ||||
|       <optional>true</optional> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>net.sf.ehcache</groupId> | ||||
|       <artifactId>ehcache</artifactId> | ||||
|       <version>1.6.2</version> | ||||
|       <scope>compile</scope> | ||||
|       <optional>true</optional> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.apache.tomcat</groupId> | ||||
|       <artifactId>tomcat-servlet-api</artifactId> | ||||
| @ -154,6 +147,12 @@ | ||||
|       <version>1.2</version> | ||||
|       <scope>runtime</scope> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>net.sf.ehcache</groupId> | ||||
|       <artifactId>ehcache</artifactId> | ||||
|       <version>1.6.2</version> | ||||
|       <scope>runtime</scope> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.slf4j</groupId> | ||||
|       <artifactId>jcl-over-slf4j</artifactId> | ||||
|  | ||||
| @ -31,6 +31,7 @@ | ||||
|         <logout logout-success-url="/index.jsp"/> | ||||
|         <remember-me /> | ||||
|         <headers/> | ||||
|         <csrf/> | ||||
|         <custom-filter ref="switchUserProcessingFilter" position="SWITCH_USER_FILTER"/> | ||||
|     </http> | ||||
| 
 | ||||
|  | ||||
| @ -33,6 +33,8 @@ | ||||
|     <b>Please fix all errors!</b> | ||||
|   </spring:hasBindErrors> | ||||
|   <br><br> | ||||
| 
 | ||||
|   <input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/> | ||||
|   <input name="execute" type="submit" alignment="center" value="Execute"> | ||||
| </form> | ||||
| <a href="<c:url value="../hello.htm"/>">Home</a> | ||||
|  | ||||
| @ -13,12 +13,12 @@ | ||||
|       <td alignment="right" width="20%">Recipient:</td> | ||||
|       <spring:bind path="addPermission.recipient"> | ||||
|         <td width="20%"> | ||||
| 		    <select name="<c:out value="${status.expression}"/>"> | ||||
| 		      <c:forEach var="thisRecipient" items="${recipients}"> | ||||
| 		        <option <c:if test="${thisRecipient.key == status.value}">selected</c:if> value="<c:out value="${thisRecipient.key}"/>"> | ||||
| 		        <c:out value="${thisRecipient.value}"/></option> | ||||
| 			    </c:forEach> | ||||
| 		    </select> | ||||
|             <select name="<c:out value="${status.expression}"/>"> | ||||
|               <c:forEach var="thisRecipient" items="${recipients}"> | ||||
|                 <option <c:if test="${thisRecipient.key == status.value}">selected</c:if> value="<c:out value="${thisRecipient.key}"/>"> | ||||
|                 <c:out value="${thisRecipient.value}"/></option> | ||||
|                 </c:forEach> | ||||
|             </select> | ||||
|         </td> | ||||
|         <td width="60%"> | ||||
|           <font color="red"><c:out value="${status.errorMessage}"/></font> | ||||
| @ -29,12 +29,12 @@ | ||||
|       <td alignment="right" width="20%">Permission:</td> | ||||
|       <spring:bind path="addPermission.permission"> | ||||
|         <td width="20%"> | ||||
| 		    <select name="<c:out value="${status.expression}"/>"> | ||||
| 		      <c:forEach var="thisPermission" items="${permissions}"> | ||||
| 		        <option <c:if test="${thisPermission.key == status.value}">selected</c:if> value="<c:out value="${thisPermission.key}"/>"> | ||||
| 		        <c:out value="${thisPermission.value}"/></option> | ||||
| 			    </c:forEach> | ||||
| 		    </select> | ||||
|             <select name="<c:out value="${status.expression}"/>"> | ||||
|               <c:forEach var="thisPermission" items="${permissions}"> | ||||
|                 <option <c:if test="${thisPermission.key == status.value}">selected</c:if> value="<c:out value="${thisPermission.key}"/>"> | ||||
|                 <c:out value="${thisPermission.value}"/></option> | ||||
|                 </c:forEach> | ||||
|             </select> | ||||
|         </td> | ||||
|         <td width="60%"> | ||||
|           <font color="red"><c:out value="${status.errorMessage}"/></font> | ||||
| @ -47,6 +47,7 @@ | ||||
|     <b>Please fix all errors!</b> | ||||
|   </spring:hasBindErrors> | ||||
|   <br><br> | ||||
|   <input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/> | ||||
|   <input name="execute" type="submit" alignment="center" value="Execute"> | ||||
| </form> | ||||
| <p> | ||||
|  | ||||
| @ -33,6 +33,7 @@ | ||||
|          </td></tr> | ||||
|         <tr><td colspan='2'><input name="exit" type="submit" value="Exit"></td></tr> | ||||
|       </table> | ||||
|       <input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/> | ||||
|     </form> | ||||
|   </body> | ||||
| </html> | ||||
|  | ||||
| @ -40,7 +40,7 @@ | ||||
|         <tr><td colspan='2'><input name="submit" type="submit"></td></tr> | ||||
|         <tr><td colspan='2'><input name="reset" type="reset"></td></tr> | ||||
|       </table> | ||||
| 
 | ||||
|       <input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/> | ||||
|     </form> | ||||
| 
 | ||||
|   </body> | ||||
|  | ||||
| @ -36,7 +36,7 @@ | ||||
|         <tr><td>User:</td><td><input type='text' name='j_username'></td></tr> | ||||
|         <tr><td colspan='2'><input name="switch" type="submit" value="Switch to User"></td></tr> | ||||
|       </table> | ||||
| 
 | ||||
|       <input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/> | ||||
|     </form> | ||||
| 
 | ||||
|   </body> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,10 +103,10 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <a href="${logoutUrl}">Log out</a> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|  | ||||
| @ -24,13 +24,14 @@ | ||||
|   <body> | ||||
|     <div class="container"> | ||||
|       <h1>This is secured!</h1> | ||||
|       <c:url var="logoutUrl" value="/logout"/> | ||||
|       <p> | ||||
|         Hello <b><c:out value="${pageContext.request.remoteUser}"/></b> | ||||
|       </p> | ||||
|       <p> | ||||
|         <a href="${logoutUrl}">Click here</a> to log out. | ||||
|       </p> | ||||
|       <c:url var="logoutUrl" value="/logout"/> | ||||
|       <form class="form-inline" action="${logoutUrl}" method="post"> | ||||
|           <input type="submit" value="Log out" /> | ||||
|           <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> | ||||
|       </form> | ||||
|     </div> | ||||
|   </body> | ||||
| </html> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,13 +103,16 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
|                 <c:url var="composeUrl" value="/?form"/> | ||||
|                 <li><a href="${composeUrl}">Compose</a></li> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <li><a href="${logoutUrl}">Log out</a></li> | ||||
|               </ul> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,6 +103,11 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,13 +103,16 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
|                 <c:url var="composeUrl" value="/?form"/> | ||||
|                 <li><a href="${composeUrl}">Compose</a></li> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <li><a href="${logoutUrl}">Log out</a></li> | ||||
|               </ul> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,13 +103,16 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
|                 <c:url var="composeUrl" value="/?form"/> | ||||
|                 <li><a href="${composeUrl}">Compose</a></li> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <li><a href="${logoutUrl}">Log out</a></li> | ||||
|               </ul> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -127,12 +127,6 @@ | ||||
|       <version>3.2.3.RELEASE</version> | ||||
|       <scope>compile</scope> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.springframework</groupId> | ||||
|       <artifactId>spring-core</artifactId> | ||||
|       <version>3.2.3.RELEASE</version> | ||||
|       <scope>compile</scope> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.springframework</groupId> | ||||
|       <artifactId>spring-core</artifactId> | ||||
| @ -145,6 +139,12 @@ | ||||
|         </exclusion> | ||||
|       </exclusions> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.springframework</groupId> | ||||
|       <artifactId>spring-core</artifactId> | ||||
|       <version>3.2.3.RELEASE</version> | ||||
|       <scope>compile</scope> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.springframework</groupId> | ||||
|       <artifactId>spring-instrument</artifactId> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -104,6 +104,11 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
| @ -111,8 +116,6 @@ | ||||
|                 <li><a href="${composeUrl}">Compose</a></li> | ||||
|                 <c:url var="userUrl" value="/user/"/> | ||||
|                 <li><a href="${userUrl}">User</a></li> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <li><a href="${logoutUrl}">Log out</a></li> | ||||
|               </ul> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,13 +103,16 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
|                 <c:url var="composeUrl" value="/?form"/> | ||||
|                 <li><a href="${composeUrl}">Compose</a></li> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <li><a href="${logoutUrl}">Log out</a></li> | ||||
|               </ul> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,13 +103,16 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
|                 <c:url var="composeUrl" value="/?form"/> | ||||
|                 <li><a href="${composeUrl}">Compose</a></li> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <li><a href="${logoutUrl}">Log out</a></li> | ||||
|               </ul> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -75,7 +75,7 @@ | ||||
|       a { | ||||
|           color: green; | ||||
|       } | ||||
|       .navbar-text a { | ||||
|       .navbar-form { | ||||
|         margin-left: 1em; | ||||
|       } | ||||
|     </style> | ||||
| @ -103,13 +103,16 @@ | ||||
|             <c:url var="logoUrl" value="/resources/img/logo.png"/> | ||||
|             <a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a> | ||||
|             <div class="nav-collapse collapse"> | ||||
|               <c:url var="logoutUrl" value="/logout"/> | ||||
|               <form:form class="navbar-form pull-right" action="${logoutUrl}" method="post"><input type="submit" value="Log out" /></form:form> | ||||
|               <p class="navbar-text pull-right"> | ||||
|                 <c:out value="${pageContext.request.remoteUser}"/> | ||||
|               </p> | ||||
|               <ul class="nav"> | ||||
|                 <c:url var="inboxUrl" value="/"/> | ||||
|                 <li><a href="${inboxUrl}">Inbox</a></li> | ||||
|                 <c:url var="composeUrl" value="/?form"/> | ||||
|                 <li><a href="${composeUrl}">Compose</a></li> | ||||
|                 <c:url var="logoutUrl" value="/logout"/> | ||||
|                 <li><a href="${logoutUrl}">Log out</a></li> | ||||
|               </ul> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -122,6 +122,13 @@ | ||||
|       <scope>compile</scope> | ||||
|       <optional>true</optional> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.springframework</groupId> | ||||
|       <artifactId>spring-webmvc</artifactId> | ||||
|       <version>3.2.3.RELEASE</version> | ||||
|       <scope>compile</scope> | ||||
|       <optional>true</optional> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.apache.tomcat</groupId> | ||||
|       <artifactId>tomcat-servlet-api</artifactId> | ||||
|  | ||||
| @ -28,6 +28,7 @@ import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.springframework.security.core.Authentication; | ||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||
| import org.springframework.security.web.util.RequestMatcher; | ||||
| import org.springframework.security.web.util.UrlUtils; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.StringUtils; | ||||
| @ -49,7 +50,9 @@ public class LogoutFilter extends GenericFilterBean { | ||||
| 
 | ||||
|     //~ Instance fields ================================================================================================ | ||||
| 
 | ||||
|     private String filterProcessesUrl = "/j_spring_security_logout"; | ||||
|     private String filterProcessesUrl; | ||||
|     private RequestMatcher logoutRequestMatcher; | ||||
| 
 | ||||
|     private final List<LogoutHandler> handlers; | ||||
|     private final LogoutSuccessHandler logoutSuccessHandler; | ||||
| 
 | ||||
| @ -65,6 +68,7 @@ public class LogoutFilter extends GenericFilterBean { | ||||
|         this.handlers = Arrays.asList(handlers); | ||||
|         Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null"); | ||||
|         this.logoutSuccessHandler = logoutSuccessHandler; | ||||
|         setFilterProcessesUrl("/j_spring_security_logout"); | ||||
|     } | ||||
| 
 | ||||
|     public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) { | ||||
| @ -77,6 +81,7 @@ public class LogoutFilter extends GenericFilterBean { | ||||
|             urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl); | ||||
|         } | ||||
|         logoutSuccessHandler = urlLogoutSuccessHandler; | ||||
|         setFilterProcessesUrl("/j_spring_security_logout"); | ||||
|     } | ||||
| 
 | ||||
|     //~ Methods ======================================================================================================== | ||||
| @ -114,35 +119,55 @@ public class LogoutFilter extends GenericFilterBean { | ||||
|      * @return <code>true</code> if logout should occur, <code>false</code> otherwise | ||||
|      */ | ||||
|     protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) { | ||||
|         String uri = request.getRequestURI(); | ||||
|         int pathParamIndex = uri.indexOf(';'); | ||||
| 
 | ||||
|         if (pathParamIndex > 0) { | ||||
|             // strip everything from the first semi-colon | ||||
|             uri = uri.substring(0, pathParamIndex); | ||||
|         } | ||||
| 
 | ||||
|         int queryParamIndex = uri.indexOf('?'); | ||||
| 
 | ||||
|         if (queryParamIndex > 0) { | ||||
|             // strip everything from the first question mark | ||||
|             uri = uri.substring(0, queryParamIndex); | ||||
|         } | ||||
| 
 | ||||
|         if ("".equals(request.getContextPath())) { | ||||
|             return uri.endsWith(filterProcessesUrl); | ||||
|         } | ||||
| 
 | ||||
|         return uri.endsWith(request.getContextPath() + filterProcessesUrl); | ||||
|         return logoutRequestMatcher.matches(request); | ||||
|     } | ||||
| 
 | ||||
|     public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) { | ||||
|         Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null"); | ||||
|         this.logoutRequestMatcher = logoutRequestMatcher; | ||||
|     } | ||||
| 
 | ||||
|     @Deprecated | ||||
|     public void setFilterProcessesUrl(String filterProcessesUrl) { | ||||
|         Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid value for" + | ||||
|                 " 'filterProcessesUrl'"); | ||||
|         this.logoutRequestMatcher = new FilterProcessUrlRequestMatcher(filterProcessesUrl); | ||||
|         this.filterProcessesUrl = filterProcessesUrl; | ||||
|     } | ||||
| 
 | ||||
|     @Deprecated | ||||
|     protected String getFilterProcessesUrl() { | ||||
|         return filterProcessesUrl; | ||||
|     } | ||||
| 
 | ||||
|     private static final class FilterProcessUrlRequestMatcher implements RequestMatcher { | ||||
|         private final String filterProcessesUrl; | ||||
| 
 | ||||
|         private FilterProcessUrlRequestMatcher(String filterProcessesUrl) { | ||||
|             Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified"); | ||||
|             Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL"); | ||||
|             this.filterProcessesUrl = filterProcessesUrl; | ||||
|         } | ||||
| 
 | ||||
|         public boolean matches(HttpServletRequest request) { | ||||
|             String uri = request.getRequestURI(); | ||||
|             int pathParamIndex = uri.indexOf(';'); | ||||
| 
 | ||||
|             if (pathParamIndex > 0) { | ||||
|                 // strip everything from the first semi-colon | ||||
|                 uri = uri.substring(0, pathParamIndex); | ||||
|             } | ||||
| 
 | ||||
|             int queryParamIndex = uri.indexOf('?'); | ||||
| 
 | ||||
|             if (queryParamIndex > 0) { | ||||
|                 // strip everything from the first question mark | ||||
|                 uri = uri.substring(0, queryParamIndex); | ||||
|             } | ||||
| 
 | ||||
|             if ("".equals(request.getContextPath())) { | ||||
|                 return uri.endsWith(filterProcessesUrl); | ||||
|             } | ||||
| 
 | ||||
|             return uri.endsWith(request.getContextPath() + filterProcessesUrl); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -67,7 +67,7 @@ public class CompositeSessionAuthenticationStrategy implements SessionAuthentica | ||||
|                 throw new IllegalArgumentException("delegateStrategies cannot contain null entires. Got " + delegateStrategies); | ||||
|             } | ||||
|         } | ||||
|         this.delegateStrategies = new ArrayList<SessionAuthenticationStrategy>(delegateStrategies); | ||||
|         this.delegateStrategies = delegateStrategies; | ||||
|     } | ||||
| 
 | ||||
|     /* (non-Javadoc) | ||||
|  | ||||
| @ -27,6 +27,7 @@ import javax.servlet.http.HttpSession; | ||||
| 
 | ||||
| import org.springframework.security.core.AuthenticationException; | ||||
| import org.springframework.security.web.WebAttributes; | ||||
| import org.springframework.security.web.csrf.CsrfToken; | ||||
| import org.springframework.web.filter.GenericFilterBean; | ||||
| 
 | ||||
| /** | ||||
| @ -164,6 +165,7 @@ public class DefaultLoginPageViewFilter extends GenericFilterBean { | ||||
|             } | ||||
| 
 | ||||
|             sb.append("    <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n"); | ||||
|             renderHiddenInputs(sb, request); | ||||
|             sb.append("  </table>\n"); | ||||
|             sb.append("</form>"); | ||||
|         } | ||||
| @ -181,6 +183,7 @@ public class DefaultLoginPageViewFilter extends GenericFilterBean { | ||||
| 
 | ||||
|             sb.append("    <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n"); | ||||
|             sb.append("  </table>\n"); | ||||
|             renderHiddenInputs(sb, request); | ||||
|             sb.append("</form>"); | ||||
|         } | ||||
| 
 | ||||
| @ -189,6 +192,14 @@ public class DefaultLoginPageViewFilter extends GenericFilterBean { | ||||
|         return sb.toString(); | ||||
|     } | ||||
| 
 | ||||
|     private void renderHiddenInputs(StringBuilder sb, HttpServletRequest request) { | ||||
|         CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); | ||||
| 
 | ||||
|         if(token != null) { | ||||
|             sb.append("    <input name=\""+ token.getParameterName() +"\" type=\"hidden\" value=\""+ token.getToken() +"\" />\n"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private boolean isLogoutSuccess(HttpServletRequest request) { | ||||
|         return logoutSuccessUrl != null && matches(request, logoutSuccessUrl); | ||||
|     } | ||||
|  | ||||
| @ -0,0 +1,56 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.springframework.security.core.Authentication; | ||||
| import org.springframework.security.web.authentication.session.SessionAuthenticationException; | ||||
| import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; | ||||
| import org.springframework.util.Assert; | ||||
| 
 | ||||
| /** | ||||
|  * {@link CsrfAuthenticationStrategy} is in charge of removing the {@link CsrfToken} upon | ||||
|  * authenticating. A new {@link CsrfToken} will then be generated by the framework upon | ||||
|  * the next request. | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| public final class CsrfAuthenticationStrategy implements | ||||
|         SessionAuthenticationStrategy { | ||||
| 
 | ||||
|     private final CsrfTokenRepository csrfTokenRepository; | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new instance | ||||
|      * @param csrfTokenRepository the {@link CsrfTokenRepository} to use | ||||
|      */ | ||||
|     public CsrfAuthenticationStrategy(CsrfTokenRepository csrfTokenRepository) { | ||||
|         Assert.notNull(csrfTokenRepository,"csrfTokenRepository cannot be null"); | ||||
|         this.csrfTokenRepository = csrfTokenRepository; | ||||
|     } | ||||
| 
 | ||||
|     /* (non-Javadoc) | ||||
|      * @see org.springframework.security.web.authentication.session.SessionAuthenticationStrategy#onAuthentication(org.springframework.security.core.Authentication, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) | ||||
|      */ | ||||
|     public void onAuthentication(Authentication authentication, | ||||
|             HttpServletRequest request, HttpServletResponse response) | ||||
|             throws SessionAuthenticationException { | ||||
|         this.csrfTokenRepository.saveToken(null, request, response); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,141 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import javax.servlet.http.HttpSession; | ||||
| 
 | ||||
| import org.springframework.security.web.access.AccessDeniedHandler; | ||||
| import org.springframework.security.web.access.AccessDeniedHandlerImpl; | ||||
| import org.springframework.security.web.util.RequestMatcher; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * Applies <a | ||||
|  * href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)" | ||||
|  * >CSRF</a> protection using a synchronizer token pattern. Developers are | ||||
|  * required to ensure that {@link CsrfFilter} is invoked for any request that | ||||
|  * allows state to change. Typically this just means that they should ensure | ||||
|  * their web application follows proper REST semantics (i.e. do not change state | ||||
|  * with the HTTP methods GET, HEAD, TRACE, OPTIONS). | ||||
|  * </p> | ||||
|  * | ||||
|  * <p> | ||||
|  * Typically the {@link CsrfTokenRepository} implementation chooses to store the | ||||
|  * {@link CsrfToken} in {@link HttpSession} with | ||||
|  * {@link HttpSessionCsrfTokenRepository}. This is preferred to storing the | ||||
|  * token in a cookie which. | ||||
|  * </p> | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| public final class CsrfFilter extends OncePerRequestFilter { | ||||
|     private final CsrfTokenRepository tokenRepository; | ||||
|     private RequestMatcher requireCsrfProtectionMatcher = new DefaultRequiresCsrfMatcher(); | ||||
|     private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); | ||||
| 
 | ||||
|     public CsrfFilter(CsrfTokenRepository csrfTokenRepository) { | ||||
|         Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null"); | ||||
|         this.tokenRepository = csrfTokenRepository; | ||||
|     } | ||||
| 
 | ||||
|     /* (non-Javadoc) | ||||
|      * @see org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain) | ||||
|      */ | ||||
|     @Override | ||||
|     protected void doFilterInternal(HttpServletRequest request, | ||||
|             HttpServletResponse response, FilterChain filterChain) | ||||
|             throws ServletException, IOException { | ||||
|         CsrfToken csrfToken = tokenRepository.loadToken(request); | ||||
|         if(csrfToken == null) { | ||||
|             csrfToken = tokenRepository.generateAndSaveToken(request, response); | ||||
|         } | ||||
|         request.setAttribute(CsrfToken.class.getName(), csrfToken); | ||||
|         request.setAttribute(csrfToken.getParameterName(), csrfToken); | ||||
|         response.addHeader(csrfToken.getHeaderName(), csrfToken.getToken()); | ||||
| 
 | ||||
|         if(!requireCsrfProtectionMatcher.matches(request)) { | ||||
|             filterChain.doFilter(request, response); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         String actualToken = request.getHeader(csrfToken.getHeaderName()); | ||||
|         if(actualToken == null) { | ||||
|             actualToken = request.getParameter(csrfToken.getParameterName()); | ||||
|         } | ||||
|         if(!csrfToken.getToken().equals(actualToken)) { | ||||
|             accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken)); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         filterChain.doFilter(request, response); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Specifies a {@link RequestMatcher} that is used to determine if CSRF | ||||
|      * protection should be applied. If the {@link RequestMatcher} returns true | ||||
|      * for a given request, then CSRF protection is applied. | ||||
|      * | ||||
|      * <p> | ||||
|      * The default is to apply CSRF protection for any HTTP method other than | ||||
|      * GET, HEAD, TRACE, OPTIONS. | ||||
|      * </p> | ||||
|      * | ||||
|      * @param requireCsrfProtectionMatcher | ||||
|      *            the {@link RequestMatcher} used to determine if CSRF | ||||
|      *            protection should be applied. | ||||
|      */ | ||||
|     public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) { | ||||
|         Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null"); | ||||
|         this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Specifies a {@link AccessDeniedHandler} that should be used when CSRF protection fails. | ||||
|      * | ||||
|      * <p> | ||||
|      * The default is to use AccessDeniedHandlerImpl with no arguments. | ||||
|      * </p> | ||||
|      * | ||||
|      * @param accessDeniedHandler | ||||
|      *            the {@link AccessDeniedHandler} to use | ||||
|      */ | ||||
|     public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) { | ||||
|         Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null"); | ||||
|         this.accessDeniedHandler = accessDeniedHandler; | ||||
|     } | ||||
| 
 | ||||
|     private static class DefaultRequiresCsrfMatcher implements RequestMatcher { | ||||
|         private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$"); | ||||
| 
 | ||||
|         /* (non-Javadoc) | ||||
|          * @see org.springframework.security.web.util.RequestMatcher#matches(javax.servlet.http.HttpServletRequest) | ||||
|          */ | ||||
|         public boolean matches(HttpServletRequest request) { | ||||
|             return !allowedMethods.matcher(request.getMethod()).matches(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.springframework.security.core.Authentication; | ||||
| import org.springframework.security.web.authentication.logout.LogoutHandler; | ||||
| import org.springframework.util.Assert; | ||||
| 
 | ||||
| /** | ||||
|  * {@link CsrfLogoutHandler} is in charge of removing the {@link CsrfToken} upon | ||||
|  * logout. A new {@link CsrfToken} will then be generated by the framework upon | ||||
|  * the next request. | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| public final class CsrfLogoutHandler implements LogoutHandler { | ||||
|     private final CsrfTokenRepository csrfTokenRepository; | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new instance | ||||
|      * @param csrfTokenRepository the {@link CsrfTokenRepository} to use | ||||
|      */ | ||||
|     public CsrfLogoutHandler(CsrfTokenRepository csrfTokenRepository) { | ||||
|         Assert.notNull(csrfTokenRepository,"csrfTokenRepository cannot be null"); | ||||
|         this.csrfTokenRepository = csrfTokenRepository; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Clears the {@link CsrfToken} | ||||
|      * | ||||
|      * @see org.springframework.security.web.authentication.logout.LogoutHandler#logout(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.security.core.Authentication) | ||||
|      */ | ||||
|     public void logout(HttpServletRequest request, | ||||
|             HttpServletResponse response, Authentication authentication) { | ||||
|         this.csrfTokenRepository.saveToken(null, request, response); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,78 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| import org.springframework.util.Assert; | ||||
| 
 | ||||
| /** | ||||
|  * A CSRF token that is used to protect against CSRF attacks. | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| @SuppressWarnings("serial") | ||||
| public final class CsrfToken implements Serializable { | ||||
| 
 | ||||
|     private final String token; | ||||
| 
 | ||||
|     private final String parameterName; | ||||
| 
 | ||||
|     private final String headerName; | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new instance | ||||
|      * @param headerName the HTTP header name to use | ||||
|      * @param parameterName the HTTP parameter name to use | ||||
|      * @param token the value of the token (i.e. expected value of the HTTP parameter of parametername). | ||||
|      */ | ||||
|     public CsrfToken(String headerName, String parameterName, String token) { | ||||
|         Assert.hasLength(headerName, "headerName cannot be null or empty"); | ||||
|         Assert.hasLength(parameterName, "parameterName cannot be null or empty"); | ||||
|         Assert.hasLength(token, "token cannot be null or empty"); | ||||
|         this.headerName = headerName; | ||||
|         this.parameterName = parameterName; | ||||
|         this.token = token; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gets the HTTP header that the CSRF is populated on the response and can | ||||
|      * be placed on requests instead of the parameter. Cannot be null. | ||||
|      * | ||||
|      * @return the HTTP header that the CSRF is populated on the response and | ||||
|      *         can be placed on requests instead of the parameter | ||||
|      */ | ||||
|     public String getHeaderName() { | ||||
|         return headerName; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gets the HTTP parameter name that should contain the token. Cannot be null. | ||||
|      * @return the HTTP parameter name that should contain the token. | ||||
|      */ | ||||
|     public String getParameterName() { | ||||
|         return parameterName; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gets the token value. Cannot be null. | ||||
|      * @return the token value | ||||
|      */ | ||||
|     public String getToken() { | ||||
|         return token; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,71 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import javax.servlet.http.HttpSession; | ||||
| 
 | ||||
| /** | ||||
|  * An API to allow changing the method in which the expected {@link CsrfToken} | ||||
|  * is associated to the {@link HttpServletRequest}. For example, it may be | ||||
|  * stored in {@link HttpSession}. | ||||
|  * | ||||
|  * @see HttpSessionCsrfTokenRepository | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  * | ||||
|  */ | ||||
| public interface CsrfTokenRepository { | ||||
| 
 | ||||
|     /** | ||||
|      * Generates and saves the expected {@link CsrfToken} | ||||
|      * | ||||
|      * @param request | ||||
|      *            the {@link HttpServletRequest} to use | ||||
|      * @param response | ||||
|      *            the {@link HttpServletResponse} to use | ||||
|      * @return the {@link CsrfToken} that was generated and saved. Cannot be | ||||
|      *         null. | ||||
|      */ | ||||
|     CsrfToken generateAndSaveToken(HttpServletRequest request, | ||||
|             HttpServletResponse response); | ||||
| 
 | ||||
|     /** | ||||
|      * Saves the {@link CsrfToken} using the {@link HttpServletRequest} and | ||||
|      * {@link HttpServletResponse}. If the {@link CsrfToken} is null, it is the | ||||
|      * same as deleting it. | ||||
|      * | ||||
|      * @param token | ||||
|      *            the {@link CsrfToken} to save or null to delete | ||||
|      * @param request | ||||
|      *            the {@link HttpServletRequest} to use | ||||
|      * @param response | ||||
|      *            the {@link HttpServletResponse} to use | ||||
|      */ | ||||
|     void saveToken(CsrfToken token, HttpServletRequest request, | ||||
|             HttpServletResponse response); | ||||
| 
 | ||||
|     /** | ||||
|      * Loads the expected {@link CsrfToken} from the {@link HttpServletRequest} | ||||
|      * | ||||
|      * @param request | ||||
|      *            the {@link HttpServletRequest} to use | ||||
|      * @return the {@link CsrfToken} or null if none exists | ||||
|      */ | ||||
|     CsrfToken loadToken(HttpServletRequest request); | ||||
| } | ||||
| @ -0,0 +1,109 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import javax.servlet.http.HttpSession; | ||||
| 
 | ||||
| import org.springframework.util.Assert; | ||||
| 
 | ||||
| /** | ||||
|  * A {@link CsrfTokenRepository} that stores the {@link CsrfToken} in the {@link HttpSession}. | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository { | ||||
|     private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"; | ||||
| 
 | ||||
|     private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN"; | ||||
| 
 | ||||
|     private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); | ||||
| 
 | ||||
|     private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; | ||||
| 
 | ||||
|     private String headerName = DEFAULT_CSRF_HEADER_NAME; | ||||
| 
 | ||||
|     private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME; | ||||
| 
 | ||||
|     /* | ||||
|      * (non-Javadoc) | ||||
|      * @see org.springframework.security.web.csrf.CsrfTokenRepository#saveToken(org.springframework.security.web.csrf.CsrfToken, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) | ||||
|      */ | ||||
|     public void saveToken(CsrfToken token, HttpServletRequest request, | ||||
|             HttpServletResponse response) { | ||||
|         HttpSession session = request.getSession(); | ||||
|         if(token == null) { | ||||
|             session.removeAttribute(sessionAttributeName); | ||||
|         } else { | ||||
|             session.setAttribute(sessionAttributeName, token); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* (non-Javadoc) | ||||
|      * @see org.springframework.security.web.csrf.CsrfTokenRepository#loadToken(javax.servlet.http.HttpServletRequest) | ||||
|      */ | ||||
|     public CsrfToken loadToken(HttpServletRequest request) { | ||||
|         return (CsrfToken) request.getSession().getAttribute(sessionAttributeName); | ||||
|     } | ||||
| 
 | ||||
|     /* (non-Javadoc) | ||||
|      * @see org.springframework.security.web.csrf.CsrfTokenRepository#generateNewToken(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) | ||||
|      */ | ||||
|     public CsrfToken generateAndSaveToken(HttpServletRequest request, | ||||
|             HttpServletResponse response) { | ||||
|         CsrfToken token = new CsrfToken(headerName, parameterName, createNewToken()); | ||||
|         saveToken(token, request, response); | ||||
|         return token; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the {@link HttpServletRequest} parameter name that the {@link CsrfToken} is expected to appear on | ||||
|      * @param parameterName the new parameter name to use | ||||
|      */ | ||||
|     public void setParameterName(String parameterName) { | ||||
|         Assert.hasLength(parameterName, "parameterName cannot be null or empty"); | ||||
|         this.parameterName = parameterName; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the header name that the {@link CsrfToken} is expected to appear on | ||||
|      * and the header that the response will contain the {@link CsrfToken}. | ||||
|      * | ||||
|      * @param parameterName | ||||
|      *            the new parameter name to use | ||||
|      */ | ||||
|     public void setHeaderName(String parameterName) { | ||||
|         Assert.hasLength(parameterName, "parameterName cannot be null or empty"); | ||||
|         this.parameterName = parameterName; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the {@link HttpSession} attribute name that the {@link CsrfToken} is stored in | ||||
|      * @param sessionAttributeName the new attribute name to use | ||||
|      */ | ||||
|     public void setSessionAttributeName(String sessionAttributeName) { | ||||
|         Assert.hasLength(sessionAttributeName, "sessionAttributename cannot be null or empty"); | ||||
|         this.sessionAttributeName = sessionAttributeName; | ||||
|     } | ||||
| 
 | ||||
|     private String createNewToken() { | ||||
|         return UUID.randomUUID().toString(); | ||||
|     } | ||||
| } | ||||
| @ -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.web.csrf; | ||||
| 
 | ||||
| import org.springframework.security.access.AccessDeniedException; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Thrown when an invalid or missing {@link CsrfToken} is found in the HttpServletRequest | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| @SuppressWarnings("serial") | ||||
| public class InvalidCsrfTokenException extends AccessDeniedException { | ||||
| 
 | ||||
|     /** | ||||
|      * @param msg | ||||
|      */ | ||||
|     public InvalidCsrfTokenException(CsrfToken expectedAccessToken, String actualAccessToken) { | ||||
|         super("Invalid CSRF Token '" + actualAccessToken | ||||
|                 + "' was found on the request parameter '" | ||||
|                 + expectedAccessToken.getParameterName() + "' or header '" | ||||
|                 + expectedAccessToken.getHeaderName() + "'."); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,86 @@ | ||||
| /* | ||||
|  * 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.web.servlet.support.csrf; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| 
 | ||||
| import org.springframework.security.web.csrf.CsrfToken; | ||||
| import org.springframework.web.servlet.support.RequestDataValueProcessor; | ||||
| 
 | ||||
| /** | ||||
|  * Integration with Spring Web MVC that automatically adds the {@link CsrfToken} | ||||
|  * into forms with hidden inputs when using Spring tag libraries. | ||||
|  * | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| public final class CsrfRequestDataValueProcessor implements | ||||
|         RequestDataValueProcessor { | ||||
| 
 | ||||
|     /* | ||||
|      * (non-Javadoc) | ||||
|      * | ||||
|      * @see org.springframework.web.servlet.support.RequestDataValueProcessor# | ||||
|      * processAction(javax.servlet.http.HttpServletRequest, java.lang.String) | ||||
|      */ | ||||
|     public String processAction(HttpServletRequest request, String action) { | ||||
|         return action; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      * (non-Javadoc) | ||||
|      * | ||||
|      * @see org.springframework.web.servlet.support.RequestDataValueProcessor# | ||||
|      * processFormFieldValue(javax.servlet.http.HttpServletRequest, | ||||
|      * java.lang.String, java.lang.String, java.lang.String) | ||||
|      */ | ||||
|     public String processFormFieldValue(HttpServletRequest request, | ||||
|             String name, String value, String type) { | ||||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      * (non-Javadoc) | ||||
|      * | ||||
|      * @see org.springframework.web.servlet.support.RequestDataValueProcessor# | ||||
|      * getExtraHiddenFields(javax.servlet.http.HttpServletRequest) | ||||
|      */ | ||||
|     public Map<String, String> getExtraHiddenFields(HttpServletRequest request) { | ||||
|         CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class | ||||
|                 .getName()); | ||||
|         if (token == null) { | ||||
|             return Collections.emptyMap(); | ||||
|         } | ||||
|         Map<String, String> hiddenFields = new HashMap<String, String>(1); | ||||
|         hiddenFields.put(token.getParameterName(), token.getToken()); | ||||
|         return hiddenFields; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      * (non-Javadoc) | ||||
|      * | ||||
|      * @see | ||||
|      * org.springframework.web.servlet.support.RequestDataValueProcessor#processUrl | ||||
|      * (javax.servlet.http.HttpServletRequest, java.lang.String) | ||||
|      */ | ||||
|     public String processUrl(HttpServletRequest request, String url) { | ||||
|         return url; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,64 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.Mock; | ||||
| import org.mockito.runners.MockitoJUnitRunner; | ||||
| import org.springframework.mock.web.MockHttpServletRequest; | ||||
| import org.springframework.mock.web.MockHttpServletResponse; | ||||
| import org.springframework.security.authentication.TestingAuthenticationToken; | ||||
| 
 | ||||
| /** | ||||
|  * @author Rob Winch | ||||
|  * | ||||
|  */ | ||||
| @RunWith(MockitoJUnitRunner.class) | ||||
| public class CsrfAuthenticationStrategyTests { | ||||
|     @Mock | ||||
|     private CsrfTokenRepository csrfTokenRepository; | ||||
| 
 | ||||
|     private MockHttpServletRequest request; | ||||
| 
 | ||||
|     private MockHttpServletResponse response; | ||||
| 
 | ||||
|     private CsrfAuthenticationStrategy strategy; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setup() { | ||||
|         request = new MockHttpServletRequest(); | ||||
|         response = new MockHttpServletResponse(); | ||||
|         strategy = new CsrfAuthenticationStrategy(csrfTokenRepository); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorNullCsrfTokenRepository() { | ||||
|         new CsrfAuthenticationStrategy(null); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void logoutRemovesCsrfToken() { | ||||
|         strategy.onAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"),request, response); | ||||
| 
 | ||||
|         verify(csrfTokenRepository).saveToken(null, request, response); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @ -0,0 +1,303 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import static org.fest.assertions.Assertions.assertThat; | ||||
| import static org.mockito.Matchers.any; | ||||
| import static org.mockito.Matchers.eq; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.verifyZeroInteractions; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.Mock; | ||||
| import org.mockito.runners.MockitoJUnitRunner; | ||||
| import org.springframework.mock.web.MockHttpServletRequest; | ||||
| import org.springframework.mock.web.MockHttpServletResponse; | ||||
| import org.springframework.security.web.access.AccessDeniedHandler; | ||||
| import org.springframework.security.web.util.RequestMatcher; | ||||
| 
 | ||||
| /** | ||||
|  * @author Rob Winch | ||||
|  * | ||||
|  */ | ||||
| @RunWith(MockitoJUnitRunner.class) | ||||
| public class CsrfFilterTests { | ||||
| 
 | ||||
|     @Mock | ||||
|     private RequestMatcher requestMatcher; | ||||
|     @Mock | ||||
|     private CsrfTokenRepository tokenRepository; | ||||
|     @Mock | ||||
|     private FilterChain filterChain; | ||||
|     @Mock | ||||
|     private AccessDeniedHandler deniedHandler; | ||||
| 
 | ||||
|     private MockHttpServletRequest request; | ||||
|     private MockHttpServletResponse response; | ||||
|     private CsrfToken token; | ||||
| 
 | ||||
| 
 | ||||
|     private CsrfFilter filter; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setup() { | ||||
|         token = new CsrfToken("headerName","paramName", "csrfTokenValue"); | ||||
|         resetRequestResponse(); | ||||
|         filter = new CsrfFilter(tokenRepository); | ||||
|         filter.setRequireCsrfProtectionMatcher(requestMatcher); | ||||
|         filter.setAccessDeniedHandler(deniedHandler); | ||||
|     } | ||||
| 
 | ||||
|     private void resetRequestResponse() { | ||||
|         request = new MockHttpServletRequest(); | ||||
|         response = new MockHttpServletResponse(); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorNullRepository() { | ||||
|         new CsrfFilter(null); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterAccessDeniedNoTokenPresent() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class)); | ||||
|         verifyZeroInteractions(filterChain); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterAccessDeniedIncorrectTokenPresent() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
|         request.setParameter(token.getParameterName(), token.getToken()+ " INVALID"); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class)); | ||||
|         verifyZeroInteractions(filterChain); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterAccessDeniedIncorrectTokenPresentHeader() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
|         request.addHeader(token.getHeaderName(), token.getToken()+ " INVALID"); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class)); | ||||
|         verifyZeroInteractions(filterChain); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterAccessDeniedIncorrectTokenPresentHeaderPreferredOverParameter() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
|         request.setParameter(token.getParameterName(), token.getToken()); | ||||
|         request.addHeader(token.getHeaderName(), token.getToken()+ " INVALID"); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class)); | ||||
|         verifyZeroInteractions(filterChain); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterNotCsrfRequestExistingToken() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(false); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(filterChain).doFilter(request, response); | ||||
|         verifyZeroInteractions(deniedHandler); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterNotCsrfRequestGenerateToken() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(false); | ||||
|         when(tokenRepository.generateAndSaveToken(request, response)).thenReturn(token); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(filterChain).doFilter(request, response); | ||||
|         verifyZeroInteractions(deniedHandler); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterIsCsrfRequestExistingTokenHeader() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
|         request.addHeader(token.getHeaderName(), token.getToken()); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(filterChain).doFilter(request, response); | ||||
|         verifyZeroInteractions(deniedHandler); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterIsCsrfRequestExistingTokenHeaderPreferredOverInvalidParam() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
|         request.setParameter(token.getParameterName(), token.getToken()+ " INVALID"); | ||||
|         request.addHeader(token.getHeaderName(), token.getToken()); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(filterChain).doFilter(request, response); | ||||
|         verifyZeroInteractions(deniedHandler); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterIsCsrfRequestExistingToken() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
|         request.setParameter(token.getParameterName(), token.getToken()); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(filterChain).doFilter(request, response); | ||||
|         verifyZeroInteractions(deniedHandler); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterIsCsrfRequestGenerateToken() throws ServletException, IOException { | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.generateAndSaveToken(request, response)).thenReturn(token); | ||||
|         request.setParameter(token.getParameterName(), token.getToken()); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         verify(filterChain).doFilter(request, response); | ||||
|         verifyZeroInteractions(deniedHandler); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterDefaultRequireCsrfProtectionMatcherAllowedMethods() throws ServletException, IOException { | ||||
|         filter = new CsrfFilter(tokenRepository); | ||||
|         filter.setAccessDeniedHandler(deniedHandler); | ||||
| 
 | ||||
|         for(String method : Arrays.asList("GET","TRACE", "OPTIONS", "HEAD")) { | ||||
|             resetRequestResponse(); | ||||
|             when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
|             request.setMethod(method); | ||||
| 
 | ||||
|             filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|             verify(filterChain).doFilter(request, response); | ||||
|             verifyZeroInteractions(deniedHandler); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterDefaultRequireCsrfProtectionMatcherDeniedMethods() throws ServletException, IOException { | ||||
|         filter = new CsrfFilter(tokenRepository); | ||||
|         filter.setAccessDeniedHandler(deniedHandler); | ||||
| 
 | ||||
|         for(String method : Arrays.asList("POST","PUT", "PATCH", "DELETE", "INVALID")) { | ||||
|             resetRequestResponse(); | ||||
|             when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
|             request.setMethod(method); | ||||
| 
 | ||||
|             filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|             verify(deniedHandler).handle(eq(request), eq(response), any(InvalidCsrfTokenException.class)); | ||||
|             verifyZeroInteractions(filterChain); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void doFilterDefaultAccessDenied() throws ServletException, IOException { | ||||
|         filter = new CsrfFilter(tokenRepository); | ||||
|         filter.setRequireCsrfProtectionMatcher(requestMatcher); | ||||
|         when(requestMatcher.matches(request)).thenReturn(true); | ||||
|         when(tokenRepository.loadToken(request)).thenReturn(token); | ||||
| 
 | ||||
|         filter.doFilter(request, response, filterChain); | ||||
| 
 | ||||
|         assertThat(response.getHeader(token.getHeaderName())).isEqualTo(token.getToken()); | ||||
|         assertThat(request.getAttribute(token.getParameterName())).isEqualTo(token); | ||||
|         assertThat(request.getAttribute(CsrfToken.class.getName())).isEqualTo(token); | ||||
| 
 | ||||
|         assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); | ||||
|         verifyZeroInteractions(filterChain); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void setRequireCsrfProtectionMatcherNull() { | ||||
|         filter.setRequireCsrfProtectionMatcher(null); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void setAccessDeniedHandlerNull() { | ||||
|         filter.setAccessDeniedHandler(null); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import static org.mockito.Mockito.verify; | ||||
| 
 | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.mockito.Mock; | ||||
| import org.mockito.runners.MockitoJUnitRunner; | ||||
| import org.springframework.mock.web.MockHttpServletRequest; | ||||
| import org.springframework.mock.web.MockHttpServletResponse; | ||||
| import org.springframework.security.authentication.TestingAuthenticationToken; | ||||
| 
 | ||||
| /** | ||||
|  * @author Rob Winch | ||||
|  * @since 3.2 | ||||
|  */ | ||||
| @RunWith(MockitoJUnitRunner.class) | ||||
| public class CsrfLogoutHandlerTests { | ||||
|     @Mock | ||||
|     private CsrfTokenRepository csrfTokenRepository; | ||||
| 
 | ||||
|     private MockHttpServletRequest request; | ||||
| 
 | ||||
|     private MockHttpServletResponse response; | ||||
| 
 | ||||
|     private CsrfLogoutHandler handler; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setup() { | ||||
|         request = new MockHttpServletRequest(); | ||||
|         response = new MockHttpServletResponse(); | ||||
|         handler = new CsrfLogoutHandler(csrfTokenRepository); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorNullCsrfTokenRepository() { | ||||
|         new CsrfLogoutHandler(null); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void logoutRemovesCsrfToken() { | ||||
|         handler.logout(request, response, new TestingAuthenticationToken("user", "password", "ROLE_USER")); | ||||
| 
 | ||||
|         verify(csrfTokenRepository).saveToken(null, request, response); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,58 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| /** | ||||
|  * @author Rob Winch | ||||
|  * | ||||
|  */ | ||||
| public class CsrfTokenTests { | ||||
|     private final String headerName = "headerName"; | ||||
|     private final String parameterName = "parameterName"; | ||||
|     private final String tokenValue = "tokenValue"; | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorNullHeaderName() { | ||||
|         new CsrfToken(null,parameterName, tokenValue); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorEmptyHeaderName() { | ||||
|         new CsrfToken("",parameterName, tokenValue); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorNullParameterName() { | ||||
|         new CsrfToken(headerName,null, tokenValue); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorEmptyParameterName() { | ||||
|         new CsrfToken(headerName,"", tokenValue); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorNullTokenValue() { | ||||
|         new CsrfToken(headerName,parameterName, null); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void constructorEmptyTokenValue() { | ||||
|         new CsrfToken(headerName,parameterName, ""); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,127 @@ | ||||
| /* | ||||
|  * 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.web.csrf; | ||||
| 
 | ||||
| import static org.fest.assertions.Assertions.assertThat; | ||||
| 
 | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.springframework.mock.web.MockHttpServletRequest; | ||||
| import org.springframework.mock.web.MockHttpServletResponse; | ||||
| 
 | ||||
| /** | ||||
|  * @author Rob Winch | ||||
|  * | ||||
|  */ | ||||
| public class HttpSessionCsrfTokenRepositoryTests { | ||||
|     private MockHttpServletRequest request; | ||||
| 
 | ||||
|     private MockHttpServletResponse response; | ||||
| 
 | ||||
|     private CsrfToken token; | ||||
|     private HttpSessionCsrfTokenRepository repo; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setup() { | ||||
|         request = new MockHttpServletRequest(); | ||||
|         response = new MockHttpServletResponse(); | ||||
|         repo = new HttpSessionCsrfTokenRepository(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void generateAndSaveToken() { | ||||
|         token = repo.generateAndSaveToken(request, response); | ||||
| 
 | ||||
|         assertThat(token.getParameterName()).isEqualTo("_csrf"); | ||||
|         assertThat(token.getToken()).isNotEmpty(); | ||||
| 
 | ||||
|         CsrfToken loadedToken = repo.loadToken(request); | ||||
| 
 | ||||
|         assertThat(loadedToken).isEqualTo(token); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void generateAndSaveTokenCustomParameter() { | ||||
|         String paramName = "_csrf"; | ||||
|         repo.setParameterName(paramName); | ||||
| 
 | ||||
|         token = repo.generateAndSaveToken(request, response); | ||||
| 
 | ||||
|         assertThat(token.getParameterName()).isEqualTo(paramName); | ||||
|         assertThat(token.getToken()).isNotEmpty(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void loadTokenNull() { | ||||
|         assertThat(repo.loadToken(request)).isNull(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void saveToken() { | ||||
|         CsrfToken tokenToSave = new CsrfToken("123", "abc", "def"); | ||||
|         repo.saveToken(tokenToSave, request, response); | ||||
| 
 | ||||
|         String attrName = request.getSession().getAttributeNames() | ||||
|                 .nextElement(); | ||||
|         CsrfToken loadedToken = (CsrfToken) request.getSession().getAttribute( | ||||
|                 attrName); | ||||
| 
 | ||||
|         assertThat(loadedToken).isEqualTo(tokenToSave); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void saveTokenCustomSessionAttribute() { | ||||
|         CsrfToken tokenToSave = new CsrfToken("123", "abc", "def"); | ||||
|         String sessionAttributeName = "custom"; | ||||
|         repo.setSessionAttributeName(sessionAttributeName); | ||||
|         repo.saveToken(tokenToSave, request, response); | ||||
| 
 | ||||
|         CsrfToken loadedToken = (CsrfToken) request.getSession().getAttribute( | ||||
|                 sessionAttributeName); | ||||
| 
 | ||||
|         assertThat(loadedToken).isEqualTo(tokenToSave); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void saveTokenNullToken() { | ||||
|         saveToken(); | ||||
| 
 | ||||
|         repo.saveToken(null, request, response); | ||||
| 
 | ||||
|         assertThat(request.getSession().getAttributeNames().hasMoreElements()) | ||||
|                 .isFalse(); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void setSessionAttributeNameEmpty() { | ||||
|         repo.setSessionAttributeName(""); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void setSessionAttributeNameNull() { | ||||
|         repo.setSessionAttributeName(null); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void setParameterNameEmpty() { | ||||
|         repo.setParameterName(""); | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected = IllegalArgumentException.class) | ||||
|     public void setParameterNameNull() { | ||||
|         repo.setParameterName(null); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,79 @@ | ||||
| /* | ||||
|  * 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.web.servlet.support.csrf; | ||||
| 
 | ||||
| import static org.fest.assertions.Assertions.assertThat; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.springframework.mock.web.MockHttpServletRequest; | ||||
| import org.springframework.mock.web.MockHttpServletResponse; | ||||
| import org.springframework.security.web.csrf.CsrfToken; | ||||
| 
 | ||||
| /** | ||||
|  * @author Rob Winch | ||||
|  * | ||||
|  */ | ||||
| public class CsrfRequestDataValueProcessorTests { | ||||
|     private MockHttpServletRequest request; | ||||
| 
 | ||||
|     private MockHttpServletResponse response; | ||||
| 
 | ||||
|     private CsrfRequestDataValueProcessor processor; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setup() { | ||||
|         request = new MockHttpServletRequest(); | ||||
|         response = new MockHttpServletResponse(); | ||||
|         processor = new CsrfRequestDataValueProcessor(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void getExtraHiddenFieldsNoCsrfToken() { | ||||
|         assertThat(processor.getExtraHiddenFields(request)).isEmpty(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void getExtraHiddenFieldsHasCsrfToken() { | ||||
|         CsrfToken token = new CsrfToken("1", "a", "b"); | ||||
|         request.setAttribute(CsrfToken.class.getName(), token); | ||||
|         Map<String,String> expected = new HashMap<String,String>(); | ||||
|         expected.put(token.getParameterName(),token.getToken()); | ||||
| 
 | ||||
|         assertThat(processor.getExtraHiddenFields(request)).isEqualTo(expected); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void processAction() { | ||||
|         String action = "action"; | ||||
|         assertThat(processor.processAction(request, action)).isEqualTo(action); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void processFormFieldValue() { | ||||
|         String value = "action"; | ||||
|         assertThat(processor.processFormFieldValue(request, "name", value, "hidden")).isEqualTo(value); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void processUrl() { | ||||
|         String url = "url"; | ||||
|         assertThat(processor.processUrl(request, url)).isEqualTo(url); | ||||
|     } | ||||
| } | ||||
| @ -11,6 +11,8 @@ dependencies { | ||||
|             "org.springframework:spring-tx:$springVersion", | ||||
|             "org.springframework:spring-web:$springVersion" | ||||
| 
 | ||||
|     optional "org.springframework:spring-webmvc:$springVersion" | ||||
| 
 | ||||
|     provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion" | ||||
| 
 | ||||
|     testCompile project(':spring-security-core').sourceSets.test.output, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user