mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-04-19 05:40:21 +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-ldap'),
|
||||||
project(':spring-security-openid'),
|
project(':spring-security-openid'),
|
||||||
"org.springframework:spring-web:$springVersion",
|
"org.springframework:spring-web:$springVersion",
|
||||||
|
"org.springframework:spring-webmvc:$springVersion",
|
||||||
"org.aspectj:aspectjweaver:$aspectjVersion"
|
"org.aspectj:aspectjweaver:$aspectjVersion"
|
||||||
|
|
||||||
provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
|
provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
|
||||||
|
@ -133,6 +133,13 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webmvc</artifactId>
|
||||||
|
<version>3.2.3.RELEASE</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.tomcat</groupId>
|
<groupId>org.apache.tomcat</groupId>
|
||||||
<artifactId>tomcat-servlet-api</artifactId>
|
<artifactId>tomcat-servlet-api</artifactId>
|
||||||
|
@ -55,4 +55,5 @@ public abstract class Elements {
|
|||||||
public static final String DEBUG = "debug";
|
public static final String DEBUG = "debug";
|
||||||
public static final String HTTP_FIREWALL = "http-firewall";
|
public static final String HTTP_FIREWALL = "http-firewall";
|
||||||
public static final String HEADERS = "headers";
|
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.authentication.www.DigestAuthenticationFilter;
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
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.header.HeaderWriterFilter;
|
||||||
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
|
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
|
||||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||||
@ -69,6 +70,8 @@ final class FilterComparator implements Comparator<Filter>, Serializable {
|
|||||||
order += STEP;
|
order += STEP;
|
||||||
put(HeaderWriterFilter.class, order);
|
put(HeaderWriterFilter.class, order);
|
||||||
order += STEP;
|
order += STEP;
|
||||||
|
put(CsrfFilter.class, order);
|
||||||
|
order += STEP;
|
||||||
put(LogoutFilter.class, order);
|
put(LogoutFilter.class, order);
|
||||||
order += STEP;
|
order += STEP;
|
||||||
put(X509AuthenticationFilter.class, order);
|
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.configuration.WebSecurityConfigurerAdapter;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
|
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.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.ExceptionHandlingConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
|
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>());
|
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
|
* Provides logout support. This is automatically applied when using
|
||||||
* {@link WebSecurityConfigurerAdapter}. The default is that accessing
|
* {@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)
|
@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||||
@Target(value={java.lang.annotation.ElementType.TYPE})
|
@Target(value={java.lang.annotation.ElementType.TYPE})
|
||||||
@Documented
|
@Documented
|
||||||
@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class})
|
@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class,AuthenticationConfiguration.class, SpringWebMvcImportSelector.class})
|
||||||
public @interface EnableWebSecurity {
|
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);
|
http.setSharedObject(ContentNegotiationStrategy.class, contentNegotiationStrategy);
|
||||||
if(!disableDefaults) {
|
if(!disableDefaults) {
|
||||||
http
|
http
|
||||||
|
.csrf().and()
|
||||||
.addFilter(new WebAsyncManagerIntegrationFilter())
|
.addFilter(new WebAsyncManagerIntegrationFilter())
|
||||||
.exceptionHandling().and()
|
.exceptionHandling().and()
|
||||||
.headers().and()
|
.headers().and()
|
||||||
|
@ -30,4 +30,15 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
|
|||||||
*/
|
*/
|
||||||
abstract class AbstractHttpConfigurer<B extends HttpSecurityBuilder<B>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
|
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
|
* @author Rob Winch
|
||||||
* @since 3.2
|
* @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 String key;
|
||||||
private AuthenticationProvider authenticationProvider;
|
private AuthenticationProvider authenticationProvider;
|
||||||
private AnonymousAuthenticationFilter authenticationFilter;
|
private AnonymousAuthenticationFilter authenticationFilter;
|
||||||
@ -53,18 +53,6 @@ public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> extends
|
|||||||
public AnonymousConfigurer() {
|
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
|
* Sets the key to identify tokens created for anonymous authentication. Default is a secure randomly generated
|
||||||
* key.
|
* 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;
|
return this.authenticationEntryPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link AccessDeniedHandler} that is configured.
|
||||||
|
*
|
||||||
|
* @return the {@link AccessDeniedHandler}
|
||||||
|
*/
|
||||||
|
AccessDeniedHandler getAccessDeniedHandler() {
|
||||||
|
return this.accessDeniedHandler;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(H http) throws Exception {
|
public void configure(H http) throws Exception {
|
||||||
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
|
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
package org.springframework.security.config.annotation.web.configurers;
|
package org.springframework.security.config.annotation.web.configurers;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
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.SecurityContextLogoutHandler;
|
||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
|
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
|
* 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 SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();
|
||||||
private String logoutSuccessUrl = "/login?logout";
|
private String logoutSuccessUrl = "/login?logout";
|
||||||
private LogoutSuccessHandler logoutSuccessHandler;
|
private LogoutSuccessHandler logoutSuccessHandler;
|
||||||
private String logoutUrl = "/logout";
|
private RequestMatcher logoutRequestMatcher = new AntPathRequestMatcher("/logout", "POST");
|
||||||
private boolean permitAll;
|
private boolean permitAll;
|
||||||
private boolean customLogoutSuccess;
|
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.
|
* @param logoutUrl the URL that will invoke logout.
|
||||||
* @return the {@link LogoutConfigurer} for further customization
|
* @return the {@link LogoutConfigurer} for further customization
|
||||||
*/
|
*/
|
||||||
public LogoutConfigurer<H> logoutUrl(String logoutUrl) {
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +200,8 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends Ab
|
|||||||
@Override
|
@Override
|
||||||
public void init(H http) throws Exception {
|
public void init(H http) throws Exception {
|
||||||
if(permitAll) {
|
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);
|
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
|
||||||
@ -245,7 +257,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends Ab
|
|||||||
logoutHandlers.add(contextLogoutHandler);
|
logoutHandlers.add(contextLogoutHandler);
|
||||||
LogoutHandler[] handlers = logoutHandlers.toArray(new LogoutHandler[logoutHandlers.size()]);
|
LogoutHandler[] handlers = logoutHandlers.toArray(new LogoutHandler[logoutHandlers.size()]);
|
||||||
LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
|
LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
|
||||||
result.setFilterProcessesUrl(logoutUrl);
|
result.setLogoutRequestMatcher(logoutRequestMatcher);
|
||||||
result = postProcess(result);
|
result = postProcess(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -31,17 +31,25 @@ import org.springframework.security.web.util.RequestMatcher;
|
|||||||
*/
|
*/
|
||||||
final class PermitAllSupport {
|
final class PermitAllSupport {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static void permitAll(HttpSecurityBuilder<? extends HttpSecurityBuilder<?>> http, String... urls) {
|
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);
|
ExpressionUrlAuthorizationConfigurer<?> configurer = http.getConfigurer(ExpressionUrlAuthorizationConfigurer.class);
|
||||||
|
|
||||||
if(configurer == null) {
|
if(configurer == null) {
|
||||||
throw new IllegalStateException("permitAll only works with HttpSecurity.authorizeRequests()");
|
throw new IllegalStateException("permitAll only works with HttpSecurity.authorizeRequests()");
|
||||||
}
|
}
|
||||||
|
|
||||||
for(String url : urls) {
|
for(RequestMatcher matcher : requestMatchers) {
|
||||||
if(url != null) {
|
if(matcher != null) {
|
||||||
configurer.addMapping(0, new UrlMapping(new ExactUrlRequestMatcher(url), SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
|
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.HttpSessionRequestCache;
|
||||||
import org.springframework.security.web.savedrequest.RequestCache;
|
import org.springframework.security.web.savedrequest.RequestCache;
|
||||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||||
|
import org.springframework.security.web.util.AntPathRequestMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds request cache for Spring Security. Specifically this ensures that
|
* 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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(H http) throws Exception {
|
||||||
|
http.setSharedObject(RequestCache.class, getRequestCache(http));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(H http) throws Exception {
|
public void configure(H http) throws Exception {
|
||||||
RequestCache requestCache = getRequestCache(http);
|
RequestCache requestCache = getRequestCache(http);
|
||||||
@ -93,6 +99,8 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> exte
|
|||||||
if(result != null) {
|
if(result != null) {
|
||||||
return result;
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public H disable() {
|
|
||||||
getBuilder().removeConfigurer(getClass());
|
|
||||||
return getBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void configure(H http) throws Exception {
|
public void configure(H http) throws Exception {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.web.configurers;
|
package org.springframework.security.config.annotation.web.configurers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -80,6 +81,7 @@ import org.springframework.util.Assert;
|
|||||||
public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
|
public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
|
||||||
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = createDefaultSessionFixationProtectionStrategy();
|
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = createDefaultSessionFixationProtectionStrategy();
|
||||||
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
|
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
|
||||||
|
private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<SessionAuthenticationStrategy>();
|
||||||
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
|
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
|
||||||
private Integer maximumSessions;
|
private Integer maximumSessions;
|
||||||
private String expiredUrl;
|
private String expiredUrl;
|
||||||
@ -173,6 +175,18 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
return this;
|
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() {
|
public SessionFixationConfigurer sessionFixation() {
|
||||||
return new SessionFixationConfigurer();
|
return new SessionFixationConfigurer();
|
||||||
}
|
}
|
||||||
@ -400,6 +414,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
if(sessionAuthenticationStrategy != null) {
|
if(sessionAuthenticationStrategy != null) {
|
||||||
return sessionAuthenticationStrategy;
|
return sessionAuthenticationStrategy;
|
||||||
}
|
}
|
||||||
|
List<SessionAuthenticationStrategy> delegateStrategies = sessionAuthenticationStrategies;
|
||||||
if(isConcurrentSessionControlEnabled()) {
|
if(isConcurrentSessionControlEnabled()) {
|
||||||
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
|
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
|
||||||
concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
|
concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
|
||||||
@ -409,11 +424,11 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry);
|
RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry);
|
||||||
registerSessionStrategy = postProcess(registerSessionStrategy);
|
registerSessionStrategy = postProcess(registerSessionStrategy);
|
||||||
|
|
||||||
List<SessionAuthenticationStrategy> delegateStrategies = Arrays.asList(concurrentSessionControlStrategy, sessionFixationAuthenticationStrategy, registerSessionStrategy);
|
delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy, sessionFixationAuthenticationStrategy, registerSessionStrategy));
|
||||||
sessionAuthenticationStrategy = postProcess(new CompositeSessionAuthenticationStrategy(delegateStrategies));
|
|
||||||
} else {
|
} else {
|
||||||
sessionAuthenticationStrategy = sessionFixationAuthenticationStrategy;
|
delegateStrategies.add(sessionFixationAuthenticationStrategy);
|
||||||
}
|
}
|
||||||
|
sessionAuthenticationStrategy = postProcess(new CompositeSessionAuthenticationStrategy(delegateStrategies));
|
||||||
return sessionAuthenticationStrategy;
|
return sessionAuthenticationStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ final class AuthenticationConfigBuilder {
|
|||||||
private BeanReference jeeProviderRef;
|
private BeanReference jeeProviderRef;
|
||||||
private RootBeanDefinition preAuthEntryPoint;
|
private RootBeanDefinition preAuthEntryPoint;
|
||||||
private BeanMetadataElement mainEntryPoint;
|
private BeanMetadataElement mainEntryPoint;
|
||||||
|
private BeanMetadataElement accessDeniedHandler;
|
||||||
|
|
||||||
private BeanDefinition logoutFilter;
|
private BeanDefinition logoutFilter;
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
@ -125,9 +126,10 @@ final class AuthenticationConfigBuilder {
|
|||||||
private final BeanReference requestCache;
|
private final BeanReference requestCache;
|
||||||
private final BeanReference portMapper;
|
private final BeanReference portMapper;
|
||||||
private final BeanReference portResolver;
|
private final BeanReference portResolver;
|
||||||
|
private final BeanMetadataElement csrfLogoutHandler;
|
||||||
|
|
||||||
public AuthenticationConfigBuilder(Element element, ParserContext pc, SessionCreationPolicy sessionPolicy,
|
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.httpElt = element;
|
||||||
this.pc = pc;
|
this.pc = pc;
|
||||||
this.requestCache = requestCache;
|
this.requestCache = requestCache;
|
||||||
@ -136,6 +138,7 @@ final class AuthenticationConfigBuilder {
|
|||||||
&& sessionPolicy != SessionCreationPolicy.STATELESS;
|
&& sessionPolicy != SessionCreationPolicy.STATELESS;
|
||||||
this.portMapper = portMapper;
|
this.portMapper = portMapper;
|
||||||
this.portResolver = portResolver;
|
this.portResolver = portResolver;
|
||||||
|
this.csrfLogoutHandler = csrfLogoutHandler;
|
||||||
|
|
||||||
createAnonymousFilter();
|
createAnonymousFilter();
|
||||||
createRememberMeFilter(authenticationManager);
|
createRememberMeFilter(authenticationManager);
|
||||||
@ -483,7 +486,7 @@ final class AuthenticationConfigBuilder {
|
|||||||
void createLogoutFilter() {
|
void createLogoutFilter() {
|
||||||
Element logoutElt = DomUtils.getChildElementByTagName(httpElt, Elements.LOGOUT);
|
Element logoutElt = DomUtils.getChildElementByTagName(httpElt, Elements.LOGOUT);
|
||||||
if (logoutElt != null || autoConfig) {
|
if (logoutElt != null || autoConfig) {
|
||||||
LogoutBeanDefinitionParser logoutParser = new LogoutBeanDefinitionParser(rememberMeServicesId);
|
LogoutBeanDefinitionParser logoutParser = new LogoutBeanDefinitionParser(rememberMeServicesId, csrfLogoutHandler);
|
||||||
logoutFilter = logoutParser.parse(logoutElt, pc);
|
logoutFilter = logoutParser.parse(logoutElt, pc);
|
||||||
logoutHandlers = logoutParser.getLogoutHandlers();
|
logoutHandlers = logoutParser.getLogoutHandlers();
|
||||||
}
|
}
|
||||||
@ -493,6 +496,9 @@ final class AuthenticationConfigBuilder {
|
|||||||
ManagedList getLogoutHandlers() {
|
ManagedList getLogoutHandlers() {
|
||||||
if(logoutHandlers == null && rememberMeProviderRef != null) {
|
if(logoutHandlers == null && rememberMeProviderRef != null) {
|
||||||
logoutHandlers = new ManagedList();
|
logoutHandlers = new ManagedList();
|
||||||
|
if(csrfLogoutHandler != null) {
|
||||||
|
logoutHandlers.add(csrfLogoutHandler);
|
||||||
|
}
|
||||||
logoutHandlers.add(new RuntimeBeanReference(rememberMeServicesId));
|
logoutHandlers.add(new RuntimeBeanReference(rememberMeServicesId));
|
||||||
logoutHandlers.add(new RootBeanDefinition(SecurityContextLogoutHandler.class));
|
logoutHandlers.add(new RootBeanDefinition(SecurityContextLogoutHandler.class));
|
||||||
}
|
}
|
||||||
@ -504,6 +510,10 @@ final class AuthenticationConfigBuilder {
|
|||||||
return mainEntryPoint;
|
return mainEntryPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BeanMetadataElement getAccessDeniedHandlerBean() {
|
||||||
|
return accessDeniedHandler;
|
||||||
|
}
|
||||||
|
|
||||||
void createAnonymousFilter() {
|
void createAnonymousFilter() {
|
||||||
Element anonymousElt = DomUtils.getChildElementByTagName(httpElt, Elements.ANONYMOUS);
|
Element anonymousElt = DomUtils.getChildElementByTagName(httpElt, Elements.ANONYMOUS);
|
||||||
|
|
||||||
@ -559,7 +569,8 @@ final class AuthenticationConfigBuilder {
|
|||||||
|
|
||||||
void createExceptionTranslationFilter() {
|
void createExceptionTranslationFilter() {
|
||||||
BeanDefinitionBuilder etfBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
|
BeanDefinitionBuilder etfBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
|
||||||
etfBuilder.addPropertyValue("accessDeniedHandler", createAccessDeniedHandler(httpElt, pc));
|
accessDeniedHandler = createAccessDeniedHandler(httpElt, pc);
|
||||||
|
etfBuilder.addPropertyValue("accessDeniedHandler", accessDeniedHandler);
|
||||||
assert requestCache != null;
|
assert requestCache != null;
|
||||||
mainEntryPoint = selectEntryPoint();
|
mainEntryPoint = selectEntryPoint();
|
||||||
etfBuilder.addConstructorArgValue(mainEntryPoint);
|
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.BeanMetadataElement;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.beans.factory.config.BeanReference;
|
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.config.RuntimeBeanReference;
|
||||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||||
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
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.ConcurrentSessionFilter;
|
||||||
import org.springframework.security.web.session.SessionManagementFilter;
|
import org.springframework.security.web.session.SessionManagementFilter;
|
||||||
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
|
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
|
||||||
|
import org.springframework.security.web.util.AntPathRequestMatcher;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@ -126,6 +126,9 @@ class HttpConfigurationBuilder {
|
|||||||
private BeanReference fsi;
|
private BeanReference fsi;
|
||||||
private BeanReference requestCache;
|
private BeanReference requestCache;
|
||||||
private BeanDefinition addHeadersFilter;
|
private BeanDefinition addHeadersFilter;
|
||||||
|
private BeanDefinition csrfFilter;
|
||||||
|
private BeanMetadataElement csrfLogoutHandler;
|
||||||
|
private BeanMetadataElement csrfAuthStrategy;
|
||||||
|
|
||||||
public HttpConfigurationBuilder(Element element, ParserContext pc,
|
public HttpConfigurationBuilder(Element element, ParserContext pc,
|
||||||
BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
|
BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
|
||||||
@ -152,6 +155,7 @@ class HttpConfigurationBuilder {
|
|||||||
sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
|
sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createCsrfFilter();
|
||||||
createSecurityContextPersistenceFilter();
|
createSecurityContextPersistenceFilter();
|
||||||
createSessionManagementFilters();
|
createSessionManagementFilters();
|
||||||
createWebAsyncManagerFilter();
|
createWebAsyncManagerFilter();
|
||||||
@ -195,6 +199,12 @@ class HttpConfigurationBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAccessDeniedHandler(BeanMetadataElement accessDeniedHandler) {
|
||||||
|
if(csrfFilter != null) {
|
||||||
|
csrfFilter.getPropertyValues().add("accessDeniedHandler", accessDeniedHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Needed to account for placeholders
|
// Needed to account for placeholders
|
||||||
static String createPath(String path, boolean lowerCase) {
|
static String createPath(String path, boolean lowerCase) {
|
||||||
return lowerCase ? path.toLowerCase() : path;
|
return lowerCase ? path.toLowerCase() : path;
|
||||||
@ -298,6 +308,10 @@ class HttpConfigurationBuilder {
|
|||||||
BeanDefinitionBuilder sessionFixationStrategy = null;
|
BeanDefinitionBuilder sessionFixationStrategy = null;
|
||||||
BeanDefinitionBuilder registerSessionStrategy;
|
BeanDefinitionBuilder registerSessionStrategy;
|
||||||
|
|
||||||
|
if(csrfAuthStrategy != null) {
|
||||||
|
delegateSessionStrategies.add(csrfAuthStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
if (sessionControlEnabled) {
|
if (sessionControlEnabled) {
|
||||||
assert sessionRegistryRef != null;
|
assert sessionRegistryRef != null;
|
||||||
concurrentSessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlAuthenticationStrategy.class);
|
concurrentSessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlAuthenticationStrategy.class);
|
||||||
@ -541,6 +555,12 @@ class HttpConfigurationBuilder {
|
|||||||
requestCacheBldr = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionRequestCache.class);
|
requestCacheBldr = BeanDefinitionBuilder.rootBeanDefinition(HttpSessionRequestCache.class);
|
||||||
requestCacheBldr.addPropertyValue("createSessionAllowed", sessionPolicy == SessionCreationPolicy.IF_REQUIRED);
|
requestCacheBldr.addPropertyValue("createSessionAllowed", sessionPolicy == SessionCreationPolicy.IF_REQUIRED);
|
||||||
requestCacheBldr.addPropertyValue("portResolver", portResolver);
|
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();
|
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() {
|
BeanReference getSessionStrategy() {
|
||||||
return sessionStrategyRef;
|
return sessionStrategyRef;
|
||||||
}
|
}
|
||||||
@ -668,6 +702,10 @@ class HttpConfigurationBuilder {
|
|||||||
filters.add(new OrderDecorator(addHeadersFilter, HEADERS_FILTER));
|
filters.add(new OrderDecorator(addHeadersFilter, HEADERS_FILTER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (csrfFilter != null) {
|
||||||
|
filters.add(new OrderDecorator(csrfFilter, CSRF_FILTER));
|
||||||
|
}
|
||||||
|
|
||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,10 +137,11 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
|
|
||||||
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
|
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
|
||||||
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
|
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
|
||||||
httpBldr.getSessionStrategy(), portMapper, portResolver);
|
httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
|
||||||
|
|
||||||
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
|
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
|
||||||
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
|
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
|
||||||
|
httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());
|
||||||
|
|
||||||
authenticationProviders.addAll(authBldr.getProviders());
|
authenticationProviders.addAll(authBldr.getProviders());
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.http;
|
package org.springframework.security.config.http;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeanMetadataElement;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
@ -44,13 +45,15 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
static final String ATT_DELETE_COOKIES = "delete-cookies";
|
static final String ATT_DELETE_COOKIES = "delete-cookies";
|
||||||
|
|
||||||
final String rememberMeServices;
|
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;
|
this.rememberMeServices = rememberMeServices;
|
||||||
|
if(csrfLogoutHandler != null) {
|
||||||
|
logoutHandlers.add(csrfLogoutHandler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public BeanDefinition parse(Element element, ParserContext pc) {
|
public BeanDefinition parse(Element element, ParserContext pc) {
|
||||||
String logoutUrl = null;
|
String logoutUrl = null;
|
||||||
String successHandlerRef = null;
|
String successHandlerRef = null;
|
||||||
@ -111,7 +114,7 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
return builder.getBeanDefinition();
|
return builder.getBeanDefinition();
|
||||||
}
|
}
|
||||||
|
|
||||||
ManagedList getLogoutHandlers() {
|
ManagedList<BeanMetadataElement> getLogoutHandlers() {
|
||||||
return logoutHandlers;
|
return logoutHandlers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ enum SecurityFilters {
|
|||||||
/** {@link WebAsyncManagerIntegrationFilter} */
|
/** {@link WebAsyncManagerIntegrationFilter} */
|
||||||
WEB_ASYNC_MANAGER_FILTER,
|
WEB_ASYNC_MANAGER_FILTER,
|
||||||
HEADERS_FILTER,
|
HEADERS_FILTER,
|
||||||
|
CSRF_FILTER,
|
||||||
LOGOUT_FILTER,
|
LOGOUT_FILTER,
|
||||||
X509_FILTER,
|
X509_FILTER,
|
||||||
PRE_AUTH_FILTER,
|
PRE_AUTH_FILTER,
|
||||||
|
@ -281,7 +281,7 @@ http-firewall =
|
|||||||
|
|
||||||
http =
|
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".
|
## 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 &=
|
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.
|
## 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}?
|
attribute pattern {xsd:token}?
|
||||||
@ -718,8 +718,18 @@ jdbc-user-service.attlist &=
|
|||||||
jdbc-user-service.attlist &=
|
jdbc-user-service.attlist &=
|
||||||
role-prefix?
|
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 =
|
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*}
|
element headers {cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & header*}
|
||||||
|
|
||||||
hsts =
|
hsts =
|
||||||
@ -783,7 +793,7 @@ header.attlist &=
|
|||||||
## The value for the header.
|
## The value for the header.
|
||||||
attribute value {xsd:token}?
|
attribute value {xsd:token}?
|
||||||
header.attlist &=
|
header.attlist &=
|
||||||
## Reference to a custom HeaderFactory implementation.
|
## Reference to a custom HeaderWriter implementation.
|
||||||
ref?
|
ref?
|
||||||
|
|
||||||
any-user-service = user-service | jdbc-user-service | ldap-user-service
|
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.
|
## 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}
|
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:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element ref="security:headers"/>
|
<xs:element ref="security:headers"/>
|
||||||
|
<xs:element ref="security:csrf"/>
|
||||||
</xs:choice>
|
</xs:choice>
|
||||||
<xs:attributeGroup ref="security:http.attlist"/>
|
<xs:attributeGroup ref="security:http.attlist"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
@ -2238,9 +2239,34 @@
|
|||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</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:element name="headers">
|
||||||
<xs:annotation>
|
<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.
|
X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
@ -2479,6 +2505,8 @@
|
|||||||
<xs:enumeration value="FIRST"/>
|
<xs:enumeration value="FIRST"/>
|
||||||
<xs:enumeration value="CHANNEL_FILTER"/>
|
<xs:enumeration value="CHANNEL_FILTER"/>
|
||||||
<xs:enumeration value="CONCURRENT_SESSION_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="SECURITY_CONTEXT_FILTER"/>
|
||||||
<xs:enumeration value="LOGOUT_FILTER"/>
|
<xs:enumeration value="LOGOUT_FILTER"/>
|
||||||
<xs:enumeration value="X509_FILTER"/>
|
<xs:enumeration value="X509_FILTER"/>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package org.springframework.security.config
|
package org.springframework.security.config
|
||||||
|
|
||||||
import groovy.xml.MarkupBuilder
|
import groovy.xml.MarkupBuilder
|
||||||
|
|
||||||
|
import org.mockito.Mockito;
|
||||||
import org.springframework.context.support.AbstractXmlApplicationContext
|
import org.springframework.context.support.AbstractXmlApplicationContext
|
||||||
import org.springframework.security.config.util.InMemoryXmlApplicationContext
|
import org.springframework.security.config.util.InMemoryXmlApplicationContext
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
@ -37,6 +39,12 @@ abstract class AbstractXmlConfigTests extends Specification {
|
|||||||
SecurityContextHolder.clearContext();
|
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) {
|
def bean(String name, Class clazz) {
|
||||||
xml.'b:bean'(id: name, 'class': clazz.name)
|
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.mock.web.MockHttpServletResponse
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
import org.springframework.security.authentication.AuthenticationProvider
|
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.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||||
import org.springframework.security.core.Authentication
|
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.SecurityContextHolder
|
||||||
import org.springframework.security.core.context.SecurityContextImpl
|
import org.springframework.security.core.context.SecurityContextImpl
|
||||||
import org.springframework.security.web.FilterChainProxy
|
import org.springframework.security.web.FilterChainProxy
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
|
||||||
import org.springframework.security.web.context.HttpRequestResponseHolder
|
import org.springframework.security.web.context.HttpRequestResponseHolder
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
|
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.AutoCleanup
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
@ -50,11 +52,26 @@ abstract class BaseSpringSpec extends Specification {
|
|||||||
MockHttpServletRequest request
|
MockHttpServletRequest request
|
||||||
MockHttpServletResponse response
|
MockHttpServletResponse response
|
||||||
MockFilterChain chain
|
MockFilterChain chain
|
||||||
|
CsrfToken csrfToken
|
||||||
|
|
||||||
def setup() {
|
def setup() {
|
||||||
|
setupWeb(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setupWeb(httpSession = null) {
|
||||||
request = new MockHttpServletRequest(method:"GET")
|
request = new MockHttpServletRequest(method:"GET")
|
||||||
|
if(httpSession) {
|
||||||
|
request.session = httpSession
|
||||||
|
}
|
||||||
response = new MockHttpServletResponse()
|
response = new MockHttpServletResponse()
|
||||||
chain = new MockFilterChain()
|
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()
|
AuthenticationManagerBuilder authenticationBldr = new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR).inMemoryAuthentication().and()
|
||||||
@ -117,6 +134,10 @@ abstract class BaseSpringSpec extends Specification {
|
|||||||
authenticationProviders().find { provider.isAssignableFrom(it.class) }
|
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") {
|
def login(String username="user", String role="ROLE_USER") {
|
||||||
login(new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(role)))
|
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.beans.factory.annotation.Autowired
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.core.annotation.Order
|
import org.springframework.core.annotation.Order
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.config.annotation.BaseSpringSpec
|
||||||
import org.springframework.security.config.annotation.BaseWebSpecuritySpec
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
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.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.builders.WebSecurity
|
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
|
* @author Rob Winch
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class SampleWebSecurityConfigurerAdapterTests extends BaseWebSpecuritySpec {
|
public class SampleWebSecurityConfigurerAdapterTests extends BaseSpringSpec {
|
||||||
def "README HelloWorld Sample works"() {
|
def "README HelloWorld Sample works"() {
|
||||||
setup: "Sample Config is loaded"
|
setup: "Sample Config is loaded"
|
||||||
loadConfig(HelloWorldWebSecurityConfigurerAdapter)
|
loadConfig(HelloWorldWebSecurityConfigurerAdapter)
|
||||||
|
@ -79,7 +79,8 @@ class WebSecurityConfigurerAdapterTests extends BaseSpringSpec {
|
|||||||
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
||||||
'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
|
'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
|
||||||
'Pragma':'no-cache',
|
'Pragma':'no-cache',
|
||||||
'X-XSS-Protection' : '1; mode=block']
|
'X-XSS-Protection' : '1; mode=block',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@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.UsernamePasswordAuthenticationFilter
|
||||||
import org.springframework.security.web.authentication.logout.LogoutFilter
|
import org.springframework.security.web.authentication.logout.LogoutFilter
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter
|
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.header.HeaderWriterFilter
|
||||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
|
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
|
||||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
|
||||||
@ -107,17 +108,17 @@ class DefaultFiltersTests extends BaseSpringSpec {
|
|||||||
|
|
||||||
def "FilterChainProxyBuilder ignoring resources"() {
|
def "FilterChainProxyBuilder ignoring resources"() {
|
||||||
when:
|
when:
|
||||||
context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderIgnoringConfig)
|
loadConfig(FilterChainProxyBuilderIgnoringConfig)
|
||||||
then:
|
then:
|
||||||
List<DefaultSecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
|
List<DefaultSecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
|
||||||
filterChains.size() == 2
|
filterChains.size() == 2
|
||||||
filterChains[0].requestMatcher.pattern == '/resources/**'
|
filterChains[0].requestMatcher.pattern == '/resources/**'
|
||||||
filterChains[0].filters.empty
|
filterChains[0].filters.empty
|
||||||
filterChains[1].requestMatcher instanceof AnyRequestMatcher
|
filterChains[1].requestMatcher instanceof AnyRequestMatcher
|
||||||
filterChains[1].filters.collect { it.class } ==
|
filterChains[1].filters.collect { it.class } ==
|
||||||
[WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, LogoutFilter, RequestCacheAwareFilter,
|
[WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, RequestCacheAwareFilter,
|
||||||
SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter,
|
SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter,
|
||||||
ExceptionTranslationFilter, FilterSecurityInterceptor ]
|
ExceptionTranslationFilter, FilterSecurityInterceptor ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -139,17 +140,16 @@ class DefaultFiltersTests extends BaseSpringSpec {
|
|||||||
|
|
||||||
def "DefaultFilters.permitAll()"() {
|
def "DefaultFilters.permitAll()"() {
|
||||||
when:
|
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:
|
then:
|
||||||
FilterChainProxy filterChain = context.getBean(FilterChainProxy)
|
response.redirectedUrl == "/login?logout"
|
||||||
|
|
||||||
expect:
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
|
||||||
filterChain.doFilter(new MockHttpServletRequest(servletPath : uri, queryString: query), response, new MockFilterChain())
|
|
||||||
response.redirectedUrl == null
|
|
||||||
where:
|
where:
|
||||||
uri | query
|
uri | query
|
||||||
"/logout" | null
|
"/logout" | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@ -42,28 +42,16 @@ import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFi
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
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"() {
|
def "http/form-login default login generating page"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(DefaultLoginPageConfig)
|
loadConfig(DefaultLoginPageConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
findFilter(DefaultLoginPageViewFilter)
|
findFilter(DefaultLoginPageViewFilter)
|
||||||
response.getRedirectedUrl() == "http://localhost/login"
|
response.getRedirectedUrl() == "http://localhost/login"
|
||||||
when: "request the login page"
|
when: "request the login page"
|
||||||
setup()
|
super.setup()
|
||||||
request.requestURI = "/login"
|
request.requestURI = "/login"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
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>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>Password:</td><td><input type='password' name='password'/></td></tr>
|
||||||
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></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>
|
</table>
|
||||||
</form></body></html>"""
|
</form></body></html>"""
|
||||||
when: "fail to log in"
|
when: "fail to log in"
|
||||||
setup()
|
super.setup()
|
||||||
request.servletPath = "/login"
|
request.servletPath = "/login"
|
||||||
request.method = "POST"
|
request.method = "POST"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
@ -84,7 +73,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
|||||||
response.getRedirectedUrl() == "/login?error"
|
response.getRedirectedUrl() == "/login?error"
|
||||||
when: "request the error page"
|
when: "request the error page"
|
||||||
HttpSession session = request.session
|
HttpSession session = request.session
|
||||||
setup()
|
super.setup()
|
||||||
request.session = session
|
request.session = session
|
||||||
request.requestURI = "/login"
|
request.requestURI = "/login"
|
||||||
request.queryString = "error"
|
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>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>Password:</td><td><input type='password' name='password'/></td></tr>
|
||||||
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></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>
|
</table>
|
||||||
</form></body></html>"""
|
</form></body></html>"""
|
||||||
when: "login success"
|
when: "login success"
|
||||||
setup()
|
super.setup()
|
||||||
request.servletPath = "/login"
|
request.servletPath = "/login"
|
||||||
request.method = "POST"
|
request.method = "POST"
|
||||||
request.parameters.username = ["user"] as String[]
|
request.parameters.username = ["user"] as String[]
|
||||||
@ -112,7 +102,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
|||||||
def "logout success renders"() {
|
def "logout success renders"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(DefaultLoginPageConfig)
|
loadConfig(DefaultLoginPageConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when: "logout success"
|
when: "logout success"
|
||||||
request.requestURI = "/login"
|
request.requestURI = "/login"
|
||||||
request.queryString = "logout"
|
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>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>Password:</td><td><input type='password' name='password'/></td></tr>
|
||||||
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></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>
|
</table>
|
||||||
</form></body></html>"""
|
</form></body></html>"""
|
||||||
}
|
}
|
||||||
@ -144,7 +134,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
|||||||
def "custom logout success handler prevents rendering"() {
|
def "custom logout success handler prevents rendering"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(DefaultLoginPageCustomLogoutSuccessHandlerConfig)
|
loadConfig(DefaultLoginPageCustomLogoutSuccessHandlerConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when: "logout success"
|
when: "logout success"
|
||||||
request.requestURI = "/login"
|
request.requestURI = "/login"
|
||||||
request.queryString = "logout"
|
request.queryString = "logout"
|
||||||
@ -172,7 +161,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
|||||||
def "custom logout success url prevents rendering"() {
|
def "custom logout success url prevents rendering"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(DefaultLoginPageCustomLogoutConfig)
|
loadConfig(DefaultLoginPageCustomLogoutConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when: "logout success"
|
when: "logout success"
|
||||||
request.requestURI = "/login"
|
request.requestURI = "/login"
|
||||||
request.queryString = "logout"
|
request.queryString = "logout"
|
||||||
@ -200,9 +188,8 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
|||||||
def "http/form-login default login with remember me"() {
|
def "http/form-login default login with remember me"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(DefaultLoginPageWithRememberMeConfig)
|
loadConfig(DefaultLoginPageWithRememberMeConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when: "request the login page"
|
when: "request the login page"
|
||||||
setup()
|
super.setup()
|
||||||
request.requestURI = "/login"
|
request.requestURI = "/login"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
@ -213,6 +200,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
|||||||
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
|
<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><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>
|
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
|
||||||
|
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
|
||||||
</table>
|
</table>
|
||||||
</form></body></html>"""
|
</form></body></html>"""
|
||||||
}
|
}
|
||||||
@ -234,7 +222,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
|||||||
def "http/form-login default login with openid"() {
|
def "http/form-login default login with openid"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(DefaultLoginPageWithOpenIDConfig)
|
loadConfig(DefaultLoginPageWithOpenIDConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when: "request the login page"
|
when: "request the login page"
|
||||||
request.requestURI = "/login"
|
request.requestURI = "/login"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
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>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>
|
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
|
||||||
</form></body></html>"""
|
</form></body></html>"""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +250,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
|
|||||||
def "http/form-login default login with openid, form login, and rememberme"() {
|
def "http/form-login default login with openid, form login, and rememberme"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(DefaultLoginPageWithFormLoginOpenIDRememberMeConfig)
|
loadConfig(DefaultLoginPageWithFormLoginOpenIDRememberMeConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when: "request the login page"
|
when: "request the login page"
|
||||||
request.requestURI = "/login"
|
request.requestURI = "/login"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
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>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><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>
|
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
|
||||||
|
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
|
||||||
</table>
|
</table>
|
||||||
</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
|
</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
|
||||||
<table>
|
<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><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>
|
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
|
||||||
</form></body></html>"""
|
</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.authentication.session.SessionFixationProtectionStrategy
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter
|
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.header.HeaderWriterFilter
|
||||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
|
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
|
||||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
|
||||||
@ -64,7 +65,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
|
|||||||
filterChains[0].filters.empty
|
filterChains[0].filters.empty
|
||||||
filterChains[1].requestMatcher instanceof AnyRequestMatcher
|
filterChains[1].requestMatcher instanceof AnyRequestMatcher
|
||||||
filterChains[1].filters.collect { it.class.name.contains('$') ? it.class.superclass : it.class } ==
|
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,
|
RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter,
|
||||||
AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor ]
|
AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor ]
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
|
|||||||
!authFilter.requiresAuthentication(new MockHttpServletRequest(servletPath : "/login", method: "GET"), new MockHttpServletResponse())
|
!authFilter.requiresAuthentication(new MockHttpServletRequest(servletPath : "/login", method: "GET"), new MockHttpServletResponse())
|
||||||
|
|
||||||
and: "SessionFixationProtectionStrategy is configured correctly"
|
and: "SessionFixationProtectionStrategy is configured correctly"
|
||||||
SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy")
|
SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy").delegateStrategies.find { SessionFixationProtectionStrategy }
|
||||||
sessionStrategy.migrateSessionAttributes
|
sessionStrategy.migrateSessionAttributes
|
||||||
|
|
||||||
and: "Exception handling is configured correctly"
|
and: "Exception handling is configured correctly"
|
||||||
@ -112,11 +113,13 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
|
|||||||
def "FormLogin.permitAll()"() {
|
def "FormLogin.permitAll()"() {
|
||||||
when: "load formLogin() with permitAll"
|
when: "load formLogin() with permitAll"
|
||||||
context = new AnnotationConfigApplicationContext(FormLoginConfigPermitAll)
|
context = new AnnotationConfigApplicationContext(FormLoginConfigPermitAll)
|
||||||
|
|
||||||
then: "the formLogin URLs are granted access"
|
|
||||||
FilterChainProxy filterChain = context.getBean(FilterChainProxy)
|
FilterChainProxy filterChain = context.getBean(FilterChainProxy)
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
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
|
response.redirectedUrl == redirectUrl
|
||||||
|
|
||||||
where:
|
where:
|
||||||
|
@ -47,8 +47,11 @@ class LogoutConfigurerTests extends BaseSpringSpec {
|
|||||||
def "invoke logout twice does not override"() {
|
def "invoke logout twice does not override"() {
|
||||||
when:
|
when:
|
||||||
loadConfig(InvokeTwiceDoesNotOverride)
|
loadConfig(InvokeTwiceDoesNotOverride)
|
||||||
|
request.method = "POST"
|
||||||
|
request.servletPath = "/custom/logout"
|
||||||
|
findFilter(LogoutFilter).doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
findFilter(LogoutFilter).filterProcessesUrl == "/custom/logout"
|
response.redirectedUrl == "/login?logout"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@ -39,35 +39,24 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class NamespaceHttpBasicTests extends BaseSpringSpec {
|
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"() {
|
def "http/http-basic"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(HttpBasicConfig)
|
loadConfig(HttpBasicConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
||||||
when: "fail to log in"
|
when: "fail to log in"
|
||||||
setup()
|
super.setup()
|
||||||
login("user","invalid")
|
basicLogin("user","invalid")
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then: "unauthorized"
|
then: "unauthorized"
|
||||||
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
||||||
response.getHeader("WWW-Authenticate") == 'Basic realm="Spring Security Application"'
|
response.getHeader("WWW-Authenticate") == 'Basic realm="Spring Security Application"'
|
||||||
when: "login success"
|
when: "login success"
|
||||||
setup()
|
super.setup()
|
||||||
login()
|
basicLogin()
|
||||||
then: "sent to default succes page"
|
then: "sent to default succes page"
|
||||||
!response.committed
|
!response.committed
|
||||||
}
|
}
|
||||||
@ -86,9 +75,8 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec {
|
|||||||
def "http@realm"() {
|
def "http@realm"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(CustomHttpBasicConfig)
|
loadConfig(CustomHttpBasicConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
login("user","invalid")
|
basicLogin("user","invalid")
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then: "unauthorized"
|
then: "unauthorized"
|
||||||
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
response.status == HttpServletResponse.SC_UNAUTHORIZED
|
||||||
@ -109,7 +97,6 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec {
|
|||||||
def "http-basic@authentication-details-source-ref"() {
|
def "http-basic@authentication-details-source-ref"() {
|
||||||
when:
|
when:
|
||||||
loadConfig(AuthenticationDetailsSourceHttpBasicConfig)
|
loadConfig(AuthenticationDetailsSourceHttpBasicConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
then:
|
then:
|
||||||
findFilter(BasicAuthenticationFilter).authenticationDetailsSource.class == CustomAuthenticationDetailsSource
|
findFilter(BasicAuthenticationFilter).authenticationDetailsSource.class == CustomAuthenticationDetailsSource
|
||||||
}
|
}
|
||||||
@ -128,20 +115,20 @@ public class NamespaceHttpBasicTests extends BaseSpringSpec {
|
|||||||
def "http-basic@entry-point-ref"() {
|
def "http-basic@entry-point-ref"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(EntryPointRefHttpBasicConfig)
|
loadConfig(EntryPointRefHttpBasicConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR
|
response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR
|
||||||
when: "fail to log in"
|
when: "fail to log in"
|
||||||
setup()
|
super.setup()
|
||||||
login("user","invalid")
|
basicLogin("user","invalid")
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then: "custom"
|
then: "custom"
|
||||||
response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR
|
response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR
|
||||||
when: "login success"
|
when: "login success"
|
||||||
setup()
|
super.setup()
|
||||||
login()
|
basicLogin()
|
||||||
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then: "sent to default succes page"
|
then: "sent to default succes page"
|
||||||
!response.committed
|
!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
|
def credentials = username + ":" + password
|
||||||
request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64())
|
request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64())
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import org.springframework.security.web.util.AnyRequestMatcher
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
||||||
|
|
||||||
def "http/headers"() {
|
def "http/headers"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(HeadersDefaultConfig)
|
loadConfig(HeadersDefaultConfig)
|
||||||
@ -48,7 +49,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
'Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
||||||
'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
|
'Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
|
||||||
'Pragma':'no-cache',
|
'Pragma':'no-cache',
|
||||||
'X-XSS-Protection' : '1; mode=block']
|
'X-XSS-Protection' : '1; mode=block',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -68,7 +70,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
responseHeaders == ['Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
|
responseHeaders == ['Cache-Control': 'no-cache,no-store,max-age=0,must-revalidate',
|
||||||
'Pragma':'no-cache']
|
'Pragma':'no-cache',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -88,7 +91,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains']
|
responseHeaders == ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -107,7 +111,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
responseHeaders == ['Strict-Transport-Security': 'max-age=15768000']
|
responseHeaders == ['Strict-Transport-Security': 'max-age=15768000',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -128,7 +133,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
responseHeaders == ['X-Frame-Options': 'SAMEORIGIN']
|
responseHeaders == ['X-Frame-Options': 'SAMEORIGIN',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -150,7 +156,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
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:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
responseHeaders == ['X-XSS-Protection': '1; mode=block']
|
responseHeaders == ['X-XSS-Protection': '1; mode=block',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -191,7 +199,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
responseHeaders == ['X-XSS-Protection': '1']
|
responseHeaders == ['X-XSS-Protection': '1',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -211,7 +220,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
responseHeaders == ['X-Content-Type-Options': 'nosniff']
|
responseHeaders == ['X-Content-Type-Options': 'nosniff',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -233,7 +243,8 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
responseHeaders == ['customHeaderName': 'customHeaderValue']
|
responseHeaders == ['customHeaderName': 'customHeaderValue',
|
||||||
|
'X-CSRF-TOKEN' : csrfToken.token]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -245,4 +256,5 @@ public class NamespaceHttpHeadersTests extends BaseSpringSpec {
|
|||||||
.addHeaderWriter(new StaticHeadersWriter("customHeaderName", "customHeaderValue"))
|
.addHeaderWriter(new StaticHeadersWriter("customHeaderName", "customHeaderValue"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -71,23 +71,13 @@ import org.springframework.security.web.util.RequestMatcher
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class NamespaceHttpLogoutTests extends BaseSpringSpec {
|
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"() {
|
def "http/logout"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(HttpLogoutConfig)
|
loadConfig(HttpLogoutConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
login()
|
login()
|
||||||
request.setRequestURI("/logout")
|
request.servletPath = "/logout"
|
||||||
|
request.method = "POST"
|
||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
@ -106,9 +96,9 @@ public class NamespaceHttpLogoutTests extends BaseSpringSpec {
|
|||||||
def "http/logout custom"() {
|
def "http/logout custom"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(CustomHttpLogoutConfig)
|
loadConfig(CustomHttpLogoutConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
login()
|
login()
|
||||||
request.setRequestURI("/custom-logout")
|
request.servletPath = "/custom-logout"
|
||||||
|
request.method = "POST"
|
||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
@ -135,9 +125,9 @@ public class NamespaceHttpLogoutTests extends BaseSpringSpec {
|
|||||||
def "http/logout@success-handler-ref"() {
|
def "http/logout@success-handler-ref"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(SuccessHandlerRefHttpLogoutConfig)
|
loadConfig(SuccessHandlerRefHttpLogoutConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
login()
|
login()
|
||||||
request.setRequestURI("/logout")
|
request.servletPath = "/logout"
|
||||||
|
request.method = "POST"
|
||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
|
@ -44,21 +44,9 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
|
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"() {
|
def "http/openid-login"() {
|
||||||
when:
|
when:
|
||||||
loadConfig(OpenIDLoginConfig)
|
loadConfig(OpenIDLoginConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
then:
|
then:
|
||||||
findFilter(OpenIDAuthenticationFilter).consumer.class == OpenID4JavaConsumer
|
findFilter(OpenIDAuthenticationFilter).consumer.class == OpenID4JavaConsumer
|
||||||
when:
|
when:
|
||||||
@ -66,7 +54,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
|
|||||||
then:
|
then:
|
||||||
response.getRedirectedUrl() == "http://localhost/login"
|
response.getRedirectedUrl() == "http://localhost/login"
|
||||||
when: "fail to log in"
|
when: "fail to log in"
|
||||||
setup()
|
super.setup()
|
||||||
request.servletPath = "/login/openid"
|
request.servletPath = "/login/openid"
|
||||||
request.method = "POST"
|
request.method = "POST"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
@ -89,7 +77,6 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
|
|||||||
def "http/openid-login/attribute-exchange"() {
|
def "http/openid-login/attribute-exchange"() {
|
||||||
when:
|
when:
|
||||||
loadConfig(OpenIDLoginAttributeExchangeConfig)
|
loadConfig(OpenIDLoginAttributeExchangeConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
OpenID4JavaConsumer consumer = findFilter(OpenIDAuthenticationFilter).consumer
|
OpenID4JavaConsumer consumer = findFilter(OpenIDAuthenticationFilter).consumer
|
||||||
then:
|
then:
|
||||||
consumer.class == OpenID4JavaConsumer
|
consumer.class == OpenID4JavaConsumer
|
||||||
@ -117,7 +104,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
|
|||||||
then:
|
then:
|
||||||
response.getRedirectedUrl() == "http://localhost/login"
|
response.getRedirectedUrl() == "http://localhost/login"
|
||||||
when: "fail to log in"
|
when: "fail to log in"
|
||||||
setup()
|
super.setup()
|
||||||
request.servletPath = "/login/openid"
|
request.servletPath = "/login/openid"
|
||||||
request.method = "POST"
|
request.method = "POST"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
@ -165,13 +152,12 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
|
|||||||
def "http/openid-login custom"() {
|
def "http/openid-login custom"() {
|
||||||
setup:
|
setup:
|
||||||
loadConfig(OpenIDLoginCustomConfig)
|
loadConfig(OpenIDLoginCustomConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
then:
|
then:
|
||||||
response.getRedirectedUrl() == "http://localhost/authentication/login"
|
response.getRedirectedUrl() == "http://localhost/authentication/login"
|
||||||
when: "fail to log in"
|
when: "fail to log in"
|
||||||
setup()
|
super.setup()
|
||||||
request.servletPath = "/authentication/login/process"
|
request.servletPath = "/authentication/login/process"
|
||||||
request.method = "POST"
|
request.method = "POST"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
@ -200,7 +186,6 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
OpenIDLoginCustomRefsConfig.AUDS = Mock(AuthenticationUserDetailsService)
|
OpenIDLoginCustomRefsConfig.AUDS = Mock(AuthenticationUserDetailsService)
|
||||||
loadConfig(OpenIDLoginCustomRefsConfig)
|
loadConfig(OpenIDLoginCustomRefsConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
then: "CustomWebAuthenticationDetailsSource is used"
|
then: "CustomWebAuthenticationDetailsSource is used"
|
||||||
findFilter(OpenIDAuthenticationFilter).authenticationDetailsSource.class == CustomWebAuthenticationDetailsSource
|
findFilter(OpenIDAuthenticationFilter).authenticationDetailsSource.class == CustomWebAuthenticationDetailsSource
|
||||||
findAuthenticationProvider(OpenIDAuthenticationProvider).userDetailsService == OpenIDLoginCustomRefsConfig.AUDS
|
findAuthenticationProvider(OpenIDAuthenticationProvider).userDetailsService == OpenIDLoginCustomRefsConfig.AUDS
|
||||||
|
@ -55,22 +55,10 @@ import org.springframework.test.util.ReflectionTestUtils
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class NamespaceHttpX509Tests extends BaseSpringSpec {
|
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"() {
|
def "http/x509 can authenticate"() {
|
||||||
setup:
|
setup:
|
||||||
X509Certificate certificate = loadCert("rod.cer")
|
X509Certificate certificate = loadCert("rod.cer")
|
||||||
loadConfig(X509Config)
|
loadConfig(X509Config)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
|
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
|
||||||
springSecurityFilterChain.doFilter(request, response, chain);
|
springSecurityFilterChain.doFilter(request, response, chain);
|
||||||
@ -148,7 +136,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec {
|
|||||||
setup:
|
setup:
|
||||||
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
|
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
|
||||||
loadConfig(SubjectPrincipalRegexConfig)
|
loadConfig(SubjectPrincipalRegexConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
|
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
|
||||||
springSecurityFilterChain.doFilter(request, response, chain);
|
springSecurityFilterChain.doFilter(request, response, chain);
|
||||||
@ -182,7 +169,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec {
|
|||||||
setup:
|
setup:
|
||||||
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
|
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
|
||||||
loadConfig(UserDetailsServiceRefConfig)
|
loadConfig(UserDetailsServiceRefConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
|
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
|
||||||
springSecurityFilterChain.doFilter(request, response, chain);
|
springSecurityFilterChain.doFilter(request, response, chain);
|
||||||
@ -216,7 +202,6 @@ public class NamespaceHttpX509Tests extends BaseSpringSpec {
|
|||||||
setup:
|
setup:
|
||||||
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
|
X509Certificate certificate = loadCert("rodatexampledotcom.cer")
|
||||||
loadConfig(AuthenticationUserDetailsServiceConfig)
|
loadConfig(AuthenticationUserDetailsServiceConfig)
|
||||||
springSecurityFilterChain = context.getBean(FilterChainProxy)
|
|
||||||
when:
|
when:
|
||||||
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
|
request.setAttribute("javax.servlet.request.X509Certificate", [certificate] as X509Certificate[] )
|
||||||
springSecurityFilterChain.doFilter(request, response, chain);
|
springSecurityFilterChain.doFilter(request, response, chain);
|
||||||
|
@ -81,8 +81,10 @@ public class NamespaceRememberMeTests extends BaseSpringSpec {
|
|||||||
when: "logout"
|
when: "logout"
|
||||||
super.setup()
|
super.setup()
|
||||||
request.setSession(session)
|
request.setSession(session)
|
||||||
|
super.setupCsrf()
|
||||||
request.setCookies(rememberMeCookie)
|
request.setCookies(rememberMeCookie)
|
||||||
request.requestURI = "/logout"
|
request.servletPath = "/logout"
|
||||||
|
request.method = "POST"
|
||||||
springSecurityFilterChain.doFilter(request,response,chain)
|
springSecurityFilterChain.doFilter(request,response,chain)
|
||||||
rememberMeCookie = getRememberMeCookie()
|
rememberMeCookie = getRememberMeCookie()
|
||||||
then: "logout cookie expired"
|
then: "logout cookie expired"
|
||||||
|
@ -41,7 +41,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
loadConfig(SessionManagementConfig)
|
loadConfig(SessionManagementConfig)
|
||||||
then:
|
then:
|
||||||
findFilter(SessionManagementFilter).sessionAuthenticationStrategy instanceof SessionFixationProtectionStrategy
|
findSessionAuthenticationStrategy(SessionFixationProtectionStrategy)
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@ -91,7 +91,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
loadConfig(RefsSessionManagementConfig)
|
loadConfig(RefsSessionManagementConfig)
|
||||||
then:
|
then:
|
||||||
findFilter(SessionManagementFilter).sessionAuthenticationStrategy == RefsSessionManagementConfig.SAS
|
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it == RefsSessionManagementConfig.SAS }
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@ -110,7 +110,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
loadConfig(SFPNoneSessionManagementConfig)
|
loadConfig(SFPNoneSessionManagementConfig)
|
||||||
then:
|
then:
|
||||||
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.class == NullAuthenticatedSessionStrategy
|
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it instanceof NullAuthenticatedSessionStrategy }
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@ -128,7 +128,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
loadConfig(SFPMigrateSessionManagementConfig)
|
loadConfig(SFPMigrateSessionManagementConfig)
|
||||||
then:
|
then:
|
||||||
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.migrateSessionAttributes
|
findSessionAuthenticationStrategy(SessionFixationProtectionStrategy).migrateSessionAttributes
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@ -145,7 +145,11 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
|
|||||||
when:
|
when:
|
||||||
loadConfig(SFPNewSessionSessionManagementConfig)
|
loadConfig(SFPNewSessionSessionManagementConfig)
|
||||||
then:
|
then:
|
||||||
!findFilter(SessionManagementFilter).sessionAuthenticationStrategy.migrateSessionAttributes
|
!findSessionAuthenticationStrategy(SessionFixationProtectionStrategy).migrateSessionAttributes
|
||||||
|
}
|
||||||
|
|
||||||
|
def findSessionAuthenticationStrategy(def c) {
|
||||||
|
findFilter(SessionManagementFilter).sessionAuthenticationStrategy.delegateStrategies.find { it.class.isAssignableFrom(c) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@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.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint
|
import org.springframework.security.web.AuthenticationEntryPoint
|
||||||
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
||||||
|
import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
||||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,7 +60,7 @@ class ServletApiConfigurerTests extends BaseSpringSpec {
|
|||||||
and: "requestFactory != null"
|
and: "requestFactory != null"
|
||||||
filter.requestFactory != null
|
filter.requestFactory != null
|
||||||
and: "logoutHandlers populated"
|
and: "logoutHandlers populated"
|
||||||
filter.logoutHandlers.collect { it.class } == [SecurityContextLogoutHandler]
|
filter.logoutHandlers.collect { it.class } == [CsrfLogoutHandler, SecurityContextLogoutHandler]
|
||||||
}
|
}
|
||||||
|
|
||||||
@CompileStatic
|
@CompileStatic
|
||||||
|
@ -57,8 +57,11 @@ abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List getFilters(String url) {
|
List getFilters(String url) {
|
||||||
def fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
|
springSecurityFilterChain.getFilters(url)
|
||||||
return fcp.getFilters(url)
|
}
|
||||||
|
|
||||||
|
Filter getSpringSecurityFilterChain() {
|
||||||
|
appContext.getBean(BeanIds.FILTER_CHAIN_PROXY)
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterInvocation createFilterinvocation(String path, String method) {
|
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 {
|
httpAutoConfig {
|
||||||
'session-management'('session-authentication-strategy-ref':'ss')
|
'session-management'('session-authentication-strategy-ref':'ss')
|
||||||
}
|
}
|
||||||
xml.'b:bean'(id: 'ss', 'class': Mockito.class.name, 'factory-method':'mock') {
|
mockBean(SessionAuthenticationStrategy,'ss')
|
||||||
'b:constructor-arg'(value : SessionAuthenticationStrategy.class.name)
|
|
||||||
}
|
|
||||||
createAppContext()
|
createAppContext()
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
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.core.context.SecurityContextImpl;
|
||||||
import org.springframework.security.web.context.HttpRequestResponseHolder;
|
import org.springframework.security.web.context.HttpRequestResponseHolder;
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
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;
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
|
||||||
@ -94,6 +96,8 @@ public class SessionManagementConfigurerServlet31Tests {
|
|||||||
request.setMethod("POST");
|
request.setMethod("POST");
|
||||||
request.setParameter("username", "user");
|
request.setParameter("username", "user");
|
||||||
request.setParameter("password", "password");
|
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);
|
when(ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId")).thenReturn(method);
|
||||||
|
|
||||||
loadConfig(SessionManagementDefaultSessionFixationServlet31Config.class);
|
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 form based authentication
|
||||||
* Allow the user with the *Username* _user_ and the *Password* _password_ to authenticate with HTTP basic authentication
|
* Allow the user with the *Username* _user_ and the *Password* _password_ to authenticate with HTTP basic authentication
|
||||||
* Allow the user to logout
|
* 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
|
* 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#getRemoteUser()[HttpServletRequest#getRemoteUser()]
|
||||||
** http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal()[HttpServletRequest.html#getUserPrincipal()]
|
** 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"]
|
[subs="verbatim,quotes"]
|
||||||
----
|
----
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<p class="navbar-text pull-right">
|
||||||
<c:out value="${pageContext.request.remoteUser}"/>
|
<c:out value="${pageContext.request.remoteUser}"/>
|
||||||
*<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<a href="${logoutUrl}">Log out</a>*
|
|
||||||
</p>
|
</p>
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<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>
|
</ul>
|
||||||
</div>
|
</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[]
|
include::hello-includes/basic-authentication.asc[]
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
= Hello Spring Security Java Config
|
= Hello Spring Security Java Config
|
||||||
:author: Rob Winch
|
:author: Rob Winch
|
||||||
:starter-appname: insecure
|
:starter-appname: insecure
|
||||||
:completed-appname: helloworld-jc
|
:completed-appname: helloworld-jc
|
||||||
:verify-starter-app-include: hello-includes/verify-insecure-app.asc
|
: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>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>This is secured!</h1>
|
<h1>This is secured!</h1>
|
||||||
|
<p>
|
||||||
|
Hello <b><c:out value="${pageContext.request.remoteUser}"/></b>
|
||||||
|
</p>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
<c:url var="logoutUrl" value="/logout"/>
|
||||||
<p>
|
<form class="form-inline" action="${logoutUrl}" method="post">
|
||||||
Hello <b><c:out value="${pageContext.request.remoteUser}"/></b>
|
<input type="submit" value="Log out" />
|
||||||
</p>
|
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
|
||||||
<p>
|
</form>
|
||||||
<a href="${logoutUrl}">Click here</a> to log out.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</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[]
|
include::hello-includes/basic-authentication.asc[]
|
||||||
|
|
||||||
|
@ -211,6 +211,7 @@
|
|||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
<listitem><link xlink:href="#nsa-access-denied-handler">access-denied-handler</link></listitem>
|
<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-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-custom-filter">custom-filter</link></listitem>
|
||||||
<listitem><link xlink:href="#nsa-expression-handler">expression-handler</link></listitem>
|
<listitem><link xlink:href="#nsa-expression-handler">expression-handler</link></listitem>
|
||||||
<listitem><link xlink:href="#nsa-form-login">form-login</link></listitem>
|
<listitem><link xlink:href="#nsa-form-login">form-login</link></listitem>
|
||||||
@ -518,6 +519,30 @@
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</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">
|
<section xml:id="nsa-custom-filter">
|
||||||
<title><literal><custom-filter></literal></title>
|
<title><literal><custom-filter></literal></title>
|
||||||
<para>This element is used to add a filter to the filter chain. It doesn't create any
|
<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>
|
||||||
<row>
|
<row>
|
||||||
<entry>HEADERS_FILTER</entry>
|
<entry>HEADERS_FILTER</entry>
|
||||||
<entry><literal>HeadersFilter</literal> </entry>
|
<entry><literal>HeaderWriterFilter</literal> </entry>
|
||||||
<entry><literal>http/headers</literal></entry>
|
<entry><literal>http/headers</literal></entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>CSRF_FILTER</entry>
|
||||||
|
<entry><literal>CsrfFilter</literal> </entry>
|
||||||
|
<entry><literal>http/csrf</literal></entry>
|
||||||
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry> LOGOUT_FILTER </entry>
|
<entry> LOGOUT_FILTER </entry>
|
||||||
<entry><literal>LogoutFilter</literal></entry>
|
<entry><literal>LogoutFilter</literal></entry>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,13 +103,16 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
<c:url var="composeUrl" value="/?form"/>
|
<c:url var="composeUrl" value="/?form"/>
|
||||||
<li><a href="${composeUrl}">Compose</a></li>
|
<li><a href="${composeUrl}">Compose</a></li>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<li><a href="${logoutUrl}">Log out</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,8 +22,7 @@ dependencies {
|
|||||||
"javax.servlet:jstl:$jstlVersion",
|
"javax.servlet:jstl:$jstlVersion",
|
||||||
"hsqldb:hsqldb:$hsqlVersion",
|
"hsqldb:hsqldb:$hsqlVersion",
|
||||||
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
|
"org.slf4j:jcl-over-slf4j:$slf4jVersion",
|
||||||
"ch.qos.logback:logback-classic:$logbackVersion"
|
"ch.qos.logback:logback-classic:$logbackVersion",
|
||||||
|
"net.sf.ehcache:ehcache:$ehcacheVersion"
|
||||||
optional "net.sf.ehcache:ehcache:$ehcacheVersion"
|
|
||||||
|
|
||||||
}
|
}
|
@ -123,13 +123,6 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>net.sf.ehcache</groupId>
|
|
||||||
<artifactId>ehcache</artifactId>
|
|
||||||
<version>1.6.2</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.tomcat</groupId>
|
<groupId>org.apache.tomcat</groupId>
|
||||||
<artifactId>tomcat-servlet-api</artifactId>
|
<artifactId>tomcat-servlet-api</artifactId>
|
||||||
@ -154,6 +147,12 @@
|
|||||||
<version>1.2</version>
|
<version>1.2</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sf.ehcache</groupId>
|
||||||
|
<artifactId>ehcache</artifactId>
|
||||||
|
<version>1.6.2</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>jcl-over-slf4j</artifactId>
|
<artifactId>jcl-over-slf4j</artifactId>
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
<logout logout-success-url="/index.jsp"/>
|
<logout logout-success-url="/index.jsp"/>
|
||||||
<remember-me />
|
<remember-me />
|
||||||
<headers/>
|
<headers/>
|
||||||
|
<csrf/>
|
||||||
<custom-filter ref="switchUserProcessingFilter" position="SWITCH_USER_FILTER"/>
|
<custom-filter ref="switchUserProcessingFilter" position="SWITCH_USER_FILTER"/>
|
||||||
</http>
|
</http>
|
||||||
|
|
||||||
|
@ -33,6 +33,8 @@
|
|||||||
<b>Please fix all errors!</b>
|
<b>Please fix all errors!</b>
|
||||||
</spring:hasBindErrors>
|
</spring:hasBindErrors>
|
||||||
<br><br>
|
<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">
|
<input name="execute" type="submit" alignment="center" value="Execute">
|
||||||
</form>
|
</form>
|
||||||
<a href="<c:url value="../hello.htm"/>">Home</a>
|
<a href="<c:url value="../hello.htm"/>">Home</a>
|
||||||
|
@ -13,12 +13,12 @@
|
|||||||
<td alignment="right" width="20%">Recipient:</td>
|
<td alignment="right" width="20%">Recipient:</td>
|
||||||
<spring:bind path="addPermission.recipient">
|
<spring:bind path="addPermission.recipient">
|
||||||
<td width="20%">
|
<td width="20%">
|
||||||
<select name="<c:out value="${status.expression}"/>">
|
<select name="<c:out value="${status.expression}"/>">
|
||||||
<c:forEach var="thisRecipient" items="${recipients}">
|
<c:forEach var="thisRecipient" items="${recipients}">
|
||||||
<option <c:if test="${thisRecipient.key == status.value}">selected</c:if> value="<c:out value="${thisRecipient.key}"/>">
|
<option <c:if test="${thisRecipient.key == status.value}">selected</c:if> value="<c:out value="${thisRecipient.key}"/>">
|
||||||
<c:out value="${thisRecipient.value}"/></option>
|
<c:out value="${thisRecipient.value}"/></option>
|
||||||
</c:forEach>
|
</c:forEach>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td width="60%">
|
<td width="60%">
|
||||||
<font color="red"><c:out value="${status.errorMessage}"/></font>
|
<font color="red"><c:out value="${status.errorMessage}"/></font>
|
||||||
@ -29,12 +29,12 @@
|
|||||||
<td alignment="right" width="20%">Permission:</td>
|
<td alignment="right" width="20%">Permission:</td>
|
||||||
<spring:bind path="addPermission.permission">
|
<spring:bind path="addPermission.permission">
|
||||||
<td width="20%">
|
<td width="20%">
|
||||||
<select name="<c:out value="${status.expression}"/>">
|
<select name="<c:out value="${status.expression}"/>">
|
||||||
<c:forEach var="thisPermission" items="${permissions}">
|
<c:forEach var="thisPermission" items="${permissions}">
|
||||||
<option <c:if test="${thisPermission.key == status.value}">selected</c:if> value="<c:out value="${thisPermission.key}"/>">
|
<option <c:if test="${thisPermission.key == status.value}">selected</c:if> value="<c:out value="${thisPermission.key}"/>">
|
||||||
<c:out value="${thisPermission.value}"/></option>
|
<c:out value="${thisPermission.value}"/></option>
|
||||||
</c:forEach>
|
</c:forEach>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td width="60%">
|
<td width="60%">
|
||||||
<font color="red"><c:out value="${status.errorMessage}"/></font>
|
<font color="red"><c:out value="${status.errorMessage}"/></font>
|
||||||
@ -47,6 +47,7 @@
|
|||||||
<b>Please fix all errors!</b>
|
<b>Please fix all errors!</b>
|
||||||
</spring:hasBindErrors>
|
</spring:hasBindErrors>
|
||||||
<br><br>
|
<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">
|
<input name="execute" type="submit" alignment="center" value="Execute">
|
||||||
</form>
|
</form>
|
||||||
<p>
|
<p>
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td colspan='2'><input name="exit" type="submit" value="Exit"></td></tr>
|
<tr><td colspan='2'><input name="exit" type="submit" value="Exit"></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
|
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
|
||||||
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
|
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<tr><td>User:</td><td><input type='text' name='j_username'></td></tr>
|
<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>
|
<tr><td colspan='2'><input name="switch" type="submit" value="Switch to User"></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
<input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,10 +103,10 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<p class="navbar-text pull-right">
|
||||||
<c:out value="${pageContext.request.remoteUser}"/>
|
<c:out value="${pageContext.request.remoteUser}"/>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<a href="${logoutUrl}">Log out</a>
|
|
||||||
</p>
|
</p>
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
|
@ -24,13 +24,14 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>This is secured!</h1>
|
<h1>This is secured!</h1>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<p>
|
<p>
|
||||||
Hello <b><c:out value="${pageContext.request.remoteUser}"/></b>
|
Hello <b><c:out value="${pageContext.request.remoteUser}"/></b>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<c:url var="logoutUrl" value="/logout"/>
|
||||||
<a href="${logoutUrl}">Click here</a> to log out.
|
<form class="form-inline" action="${logoutUrl}" method="post">
|
||||||
</p>
|
<input type="submit" value="Log out" />
|
||||||
|
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,13 +103,16 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
<c:url var="composeUrl" value="/?form"/>
|
<c:url var="composeUrl" value="/?form"/>
|
||||||
<li><a href="${composeUrl}">Compose</a></li>
|
<li><a href="${composeUrl}">Compose</a></li>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<li><a href="${logoutUrl}">Log out</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,6 +103,11 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,13 +103,16 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
<c:url var="composeUrl" value="/?form"/>
|
<c:url var="composeUrl" value="/?form"/>
|
||||||
<li><a href="${composeUrl}">Compose</a></li>
|
<li><a href="${composeUrl}">Compose</a></li>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<li><a href="${logoutUrl}">Log out</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,13 +103,16 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
<c:url var="composeUrl" value="/?form"/>
|
<c:url var="composeUrl" value="/?form"/>
|
||||||
<li><a href="${composeUrl}">Compose</a></li>
|
<li><a href="${composeUrl}">Compose</a></li>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<li><a href="${logoutUrl}">Log out</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,12 +127,6 @@
|
|||||||
<version>3.2.3.RELEASE</version>
|
<version>3.2.3.RELEASE</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-core</artifactId>
|
|
||||||
<version>3.2.3.RELEASE</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
<artifactId>spring-core</artifactId>
|
<artifactId>spring-core</artifactId>
|
||||||
@ -145,6 +139,12 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-core</artifactId>
|
||||||
|
<version>3.2.3.RELEASE</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
<artifactId>spring-instrument</artifactId>
|
<artifactId>spring-instrument</artifactId>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -104,6 +104,11 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
@ -111,8 +116,6 @@
|
|||||||
<li><a href="${composeUrl}">Compose</a></li>
|
<li><a href="${composeUrl}">Compose</a></li>
|
||||||
<c:url var="userUrl" value="/user/"/>
|
<c:url var="userUrl" value="/user/"/>
|
||||||
<li><a href="${userUrl}">User</a></li>
|
<li><a href="${userUrl}">User</a></li>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<li><a href="${logoutUrl}">Log out</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,13 +103,16 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
<c:url var="composeUrl" value="/?form"/>
|
<c:url var="composeUrl" value="/?form"/>
|
||||||
<li><a href="${composeUrl}">Compose</a></li>
|
<li><a href="${composeUrl}">Compose</a></li>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<li><a href="${logoutUrl}">Log out</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,13 +103,16 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
<c:url var="composeUrl" value="/?form"/>
|
<c:url var="composeUrl" value="/?form"/>
|
||||||
<li><a href="${composeUrl}">Compose</a></li>
|
<li><a href="${composeUrl}">Compose</a></li>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<li><a href="${logoutUrl}">Log out</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
a {
|
a {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
.navbar-text a {
|
.navbar-form {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -103,13 +103,16 @@
|
|||||||
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
<c:url var="logoUrl" value="/resources/img/logo.png"/>
|
||||||
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
<a class="brand" href="${homeUrl}"><img src="${logoUrl}" alt="Spring Security Sample"/></a>
|
||||||
<div class="nav-collapse collapse">
|
<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">
|
<ul class="nav">
|
||||||
<c:url var="inboxUrl" value="/"/>
|
<c:url var="inboxUrl" value="/"/>
|
||||||
<li><a href="${inboxUrl}">Inbox</a></li>
|
<li><a href="${inboxUrl}">Inbox</a></li>
|
||||||
<c:url var="composeUrl" value="/?form"/>
|
<c:url var="composeUrl" value="/?form"/>
|
||||||
<li><a href="${composeUrl}">Compose</a></li>
|
<li><a href="${composeUrl}">Compose</a></li>
|
||||||
<c:url var="logoutUrl" value="/logout"/>
|
|
||||||
<li><a href="${logoutUrl}">Log out</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -122,6 +122,13 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webmvc</artifactId>
|
||||||
|
<version>3.2.3.RELEASE</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.tomcat</groupId>
|
<groupId>org.apache.tomcat</groupId>
|
||||||
<artifactId>tomcat-servlet-api</artifactId>
|
<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.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.util.RequestMatcher;
|
||||||
import org.springframework.security.web.util.UrlUtils;
|
import org.springframework.security.web.util.UrlUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@ -49,7 +50,9 @@ public class LogoutFilter extends GenericFilterBean {
|
|||||||
|
|
||||||
//~ Instance fields ================================================================================================
|
//~ Instance fields ================================================================================================
|
||||||
|
|
||||||
private String filterProcessesUrl = "/j_spring_security_logout";
|
private String filterProcessesUrl;
|
||||||
|
private RequestMatcher logoutRequestMatcher;
|
||||||
|
|
||||||
private final List<LogoutHandler> handlers;
|
private final List<LogoutHandler> handlers;
|
||||||
private final LogoutSuccessHandler logoutSuccessHandler;
|
private final LogoutSuccessHandler logoutSuccessHandler;
|
||||||
|
|
||||||
@ -65,6 +68,7 @@ public class LogoutFilter extends GenericFilterBean {
|
|||||||
this.handlers = Arrays.asList(handlers);
|
this.handlers = Arrays.asList(handlers);
|
||||||
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
|
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
|
||||||
this.logoutSuccessHandler = logoutSuccessHandler;
|
this.logoutSuccessHandler = logoutSuccessHandler;
|
||||||
|
setFilterProcessesUrl("/j_spring_security_logout");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
|
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
|
||||||
@ -77,6 +81,7 @@ public class LogoutFilter extends GenericFilterBean {
|
|||||||
urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
|
urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
|
||||||
}
|
}
|
||||||
logoutSuccessHandler = urlLogoutSuccessHandler;
|
logoutSuccessHandler = urlLogoutSuccessHandler;
|
||||||
|
setFilterProcessesUrl("/j_spring_security_logout");
|
||||||
}
|
}
|
||||||
|
|
||||||
//~ Methods ========================================================================================================
|
//~ Methods ========================================================================================================
|
||||||
@ -114,35 +119,55 @@ public class LogoutFilter extends GenericFilterBean {
|
|||||||
* @return <code>true</code> if logout should occur, <code>false</code> otherwise
|
* @return <code>true</code> if logout should occur, <code>false</code> otherwise
|
||||||
*/
|
*/
|
||||||
protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {
|
protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {
|
||||||
String uri = request.getRequestURI();
|
return logoutRequestMatcher.matches(request);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
|
||||||
|
Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null");
|
||||||
|
this.logoutRequestMatcher = logoutRequestMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setFilterProcessesUrl(String filterProcessesUrl) {
|
public void setFilterProcessesUrl(String filterProcessesUrl) {
|
||||||
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid value for" +
|
this.logoutRequestMatcher = new FilterProcessUrlRequestMatcher(filterProcessesUrl);
|
||||||
" 'filterProcessesUrl'");
|
|
||||||
this.filterProcessesUrl = filterProcessesUrl;
|
this.filterProcessesUrl = filterProcessesUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
protected String getFilterProcessesUrl() {
|
protected String getFilterProcessesUrl() {
|
||||||
return filterProcessesUrl;
|
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);
|
throw new IllegalArgumentException("delegateStrategies cannot contain null entires. Got " + delegateStrategies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.delegateStrategies = new ArrayList<SessionAuthenticationStrategy>(delegateStrategies);
|
this.delegateStrategies = delegateStrategies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
@ -27,6 +27,7 @@ import javax.servlet.http.HttpSession;
|
|||||||
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.web.WebAttributes;
|
import org.springframework.security.web.WebAttributes;
|
||||||
|
import org.springframework.security.web.csrf.CsrfToken;
|
||||||
import org.springframework.web.filter.GenericFilterBean;
|
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");
|
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(" </table>\n");
|
||||||
sb.append("</form>");
|
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(" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n");
|
||||||
sb.append(" </table>\n");
|
sb.append(" </table>\n");
|
||||||
|
renderHiddenInputs(sb, request);
|
||||||
sb.append("</form>");
|
sb.append("</form>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +192,14 @@ public class DefaultLoginPageViewFilter extends GenericFilterBean {
|
|||||||
return sb.toString();
|
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) {
|
private boolean isLogoutSuccess(HttpServletRequest request) {
|
||||||
return logoutSuccessUrl != null && matches(request, logoutSuccessUrl);
|
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-tx:$springVersion",
|
||||||
"org.springframework:spring-web:$springVersion"
|
"org.springframework:spring-web:$springVersion"
|
||||||
|
|
||||||
|
optional "org.springframework:spring-webmvc:$springVersion"
|
||||||
|
|
||||||
provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
|
provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
|
||||||
|
|
||||||
testCompile project(':spring-security-core').sourceSets.test.output,
|
testCompile project(':spring-security-core').sourceSets.test.output,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user