SEC-2015: Add spring-security-test

This commit is contained in:
Rob Winch
2014-04-22 16:47:48 -05:00
parent 1c75d33adb
commit 8baf82532c
48 changed files with 4647 additions and 10 deletions

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2002-2014 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.test.context;
import javax.servlet.FilterChain;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.support.WithSecurityContextTestExcecutionListener;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.Assert;
/**
* The {@link TestSecurityContextHolder} is very similar to
* {@link SecurityContextHolder}, but is necessary for testing. For example, we
* cannot populate the desired {@link SecurityContext} in
* {@link SecurityContextHolder} for web based testing. In a web request, the
* {@link SecurityContextPersistenceFilter} will override the
* {@link SecurityContextHolder} with the value returned by the
* {@link SecurityContextRepository}. At the end of the {@link FilterChain} the
* {@link SecurityContextPersistenceFilter} will clear out the
* {@link SecurityContextHolder}. This means if we make multiple web requests,
* we will not know which {@link SecurityContext} to use on subsequent requests.
*
* Typical usage is as follows:
*
* <ul>
* <li>Before a test is executed, the {@link TestSecurityContextHolder} is
* populated. Typically this is done using the
* {@link WithSecurityContextTestExcecutionListener}</li>
* <li>The test is ran. When used with {@link MockMvc} it is typically used with
* {@link SecurityMockMvcRequestPostProcessors#testSecurityContext()}. Which ensures
* the {@link SecurityContext} from {@link TestSecurityContextHolder} is
* properly populated.</li>
* <li>After the test is executed, the {@link TestSecurityContextHolder} and the
* {@link SecurityContextHolder} are cleared out</li>
* </ul>
*
* @author Rob Winch
* @since 4.0
*
*/
public final class TestSecurityContextHolder {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();
/**
* Clears the {@link SecurityContext} from {@link TestSecurityContextHolder}
* and {@link SecurityContextHolder}.
*/
public static void clearContext() {
contextHolder.remove();
SecurityContextHolder.clearContext();
}
/**
* Gets the {@link SecurityContext} from {@link TestSecurityContextHolder}.
*
* @return the {@link SecurityContext} from {@link TestSecurityContextHolder}.
*/
public static SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = getDefaultContext();
contextHolder.set(ctx);
}
return ctx;
}
/**
* Sets the {@link SecurityContext} on {@link TestSecurityContextHolder} and {@link SecurityContextHolder}.
* @param context the {@link SecurityContext} to use
*/
public static void setContext(SecurityContext context) {
Assert.notNull(context,
"Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
SecurityContextHolder.setContext(context);
}
/**
* Gets the default {@link SecurityContext} by delegating to the {@link SecurityContextHolder}
*
* @return the default {@link SecurityContext}
*/
private static SecurityContext getDefaultContext() {
return SecurityContextHolder.getContext();
}
private TestSecurityContextHolder() {
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2002-2014 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.test.context.support;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.web.servlet.MockMvc;
/**
* When used with {@link WithSecurityContextTestExcecutionListener} this annotation can be
* added to a test method to emulate running with a mocked user. In order to work with {@link MockMvc} The
* {@link SecurityContext} that is used will have the following properties:
*
* <ul>
* <li>The {@link SecurityContext} created with be that of
* {@link SecurityContextHolder#createEmptyContext()}</li>
* <li>It will be populated with an {@link UsernamePasswordAuthenticationToken}
* that uses the username of either {@link #value()} or {@link #username()},
* {@link GrantedAuthority} that are specified by {@link #roles()}, and a
* password specified by {@link #password()}.
* </ul>
*
* @see WithUserDetails
*
* @author Rob Winch
* @since 4.0
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory = WithMockUserSecurityContextFactory.class)
public @interface WithMockUser {
/**
* Convenience mechanism for specifying the username. The default is "user". If {@link #username()} is specified it will be used instead of {@link #value()}
* @return
*/
String value() default "user";
/**
* The username to be used. Note that {@link #value()} is a synonym for {@link #username()}, but if {@link #username()} is specified it will take precedence.
* @return
*/
String username() default "";
/**
* The roles to use. The default is "USER". A {@link GrantedAuthority} will
* be created for each value within roles. Each value in roles will
* automatically be prefixed with "ROLE_". For example, the default will
* result in "ROLE_USER" being used.
*
* @return
*/
String[] roles() default { "USER" };
/**
* The password to be used. The default is "password".
* @return
*/
String password() default "password";
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2014 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.test.context.support;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.util.StringUtils;
/**
* A {@link WithUserDetailsSecurityContextFactory} that works with {@link WithMockUser}.
*
* @author Rob Winch
* @since 4.0
* @see WithMockUser
*/
final class WithMockUserSecurityContextFactory implements WithSecurityContextFactory<WithMockUser> {
public SecurityContext createSecurityContext(WithMockUser withUser) {
String username = StringUtils.hasLength(withUser.username()) ? withUser.username() : withUser.value();
if(username == null) {
throw new IllegalArgumentException(withUser + " cannot have null username on both username and value properites");
}
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for(String role : withUser.roles()) {
if(role.startsWith("ROLE_")) {
throw new IllegalArgumentException("roles cannot start with ROLE_ Got " + role);
}
authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
}
User principal = new User(username, withUser.password(), true, true, true, true, authorities);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2002-2014 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.test.context.support;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext;
/**
* <p>
* An annotation to determine what {@link SecurityContext} to use. The
* {@link #factory()} attribute must be provided with an instance of
* {@link WithUserDetailsSecurityContextFactory}.
* </p>
*
* <p>
* Typically this annotation will be used as an meta-annotation as done with
* {@link WithMockUser} and {@link WithUserDetails}.
* </p>
*
* <p>
* If you would like to create your own implementation of
* {@link WithSecurityContextFactory} you can do so by implementing the
* interface. You can also use {@link Autowired} and other Spring semantics on
* the {@link WithSecurityContextFactory} implementation.
* </p>
*
* @author Rob Winch
* @since 4.0
*/
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface WithSecurityContext {
/**
* The {@link WithUserDetailsSecurityContextFactory} to use to create the {@link SecurityContext}. It can contain {@link Autowired} and other Spring annotations.
*
* @return
*/
Class<? extends WithSecurityContextFactory<? extends Annotation>> factory();
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2014 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.test.context.support;
import java.lang.annotation.Annotation;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.test.context.TestSecurityContextHolder;
/**
* An API that works with WithUserTestExcecutionListener for creating a
* {@link SecurityContext} that is populated in the
* {@link TestSecurityContextHolder}.
*
* @author Rob Winch
*
* @param <A>
* @see WithSecurityContext
* @see WithMockUser
* @see WithUserDetails
* @since 4.0
*/
public interface WithSecurityContextFactory<A extends Annotation> {
/**
* Create a {@link SecurityContext} given an Annotation.
*
* @param annotation
* the {@link Annotation} to create the {@link SecurityContext}
* from. Cannot be null.
* @return the {@link SecurityContext} to use. Cannot be null.
*/
SecurityContext createSecurityContext(A annotation);
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2002-2014 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.test.context.support;
import java.lang.annotation.Annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.TestSecurityContextHolder;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.web.servlet.MockMvc;
/**
* A {@link TestExecutionListener} that will find annotations that are annotated
* with {@link WithSecurityContext} on a test method or at the class level. If found, the
* {@link WithSecurityContext#factory()} is used to create a {@link SecurityContext} that
* will be used with this test. If using with {@link MockMvc} the
* {@link SecurityMockMvcRequestPostProcessors#testSecurityContext()} needs to be used
* too.
*
* @author Rob Winch
* @since 4.0
*/
public class WithSecurityContextTestExcecutionListener extends
AbstractTestExecutionListener {
/**
* Sets up the {@link SecurityContext} for each test method. First the
* specific method is inspected for a {@link WithSecurityContext} or {@link Annotation}
* that has {@link WithSecurityContext} on it. If that is not found, the class is
* inspected. If still not found, then no {@link SecurityContext} is
* populated.
*/
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
Annotation[] methodAnnotations = AnnotationUtils
.getAnnotations(testContext.getTestMethod());
ApplicationContext context = testContext.getApplicationContext();
SecurityContext securityContext = createSecurityContext(
methodAnnotations, context);
if (securityContext == null) {
Annotation[] classAnnotations = testContext.getTestClass()
.getAnnotations();
securityContext = createSecurityContext(classAnnotations, context);
}
if (securityContext != null) {
TestSecurityContextHolder.setContext(securityContext);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private SecurityContext createSecurityContext(Annotation[] annotations,
ApplicationContext context) {
for (Annotation a : annotations) {
WithSecurityContext withUser = AnnotationUtils.findAnnotation(
a.annotationType(), WithSecurityContext.class);
if (withUser != null) {
WithSecurityContextFactory factory = createFactory(
withUser, context);
try {
return factory.createSecurityContext(a);
} catch (RuntimeException e) {
throw new IllegalStateException("Unable to create SecurityContext using "+ a, e);
}
}
}
return null;
}
private WithSecurityContextFactory<? extends Annotation> createFactory(
WithSecurityContext withUser, ApplicationContext context) {
Class<? extends WithSecurityContextFactory<? extends Annotation>> clazz = withUser
.factory();
try {
return context.getAutowireCapableBeanFactory().createBean(clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Clears out the {@link TestSecurityContextHolder} and the
* {@link SecurityContextHolder} after each test method.
*/
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
TestSecurityContextHolder.clearContext();
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2002-2014 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.test.context.support;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.test.web.servlet.MockMvc;
/**
* When used with {@link WithSecurityContextTestExcecutionListener} this annotation can be
* added to a test method to emulate running with a {@link UserDetails} returned
* from the {@link UserDetailsService}. In order to work with {@link MockMvc}
* The {@link SecurityContext} that is used will have the following properties:
*
* <ul>
* <li>The {@link SecurityContext} created with be that of
* {@link SecurityContextHolder#createEmptyContext()}</li>
* <li>It will be populated with an {@link UsernamePasswordAuthenticationToken}
* that uses the username of either {@link #value()} or {@link #username()},
* {@link GrantedAuthority} that are specified by {@link #roles()}, and a
* password specified by {@link #password()}.
* </ul>
*
* @see WithMockUser
*
* @author Rob Winch
* @since 4.0
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory=WithUserDetailsSecurityContextFactory.class)
public @interface WithUserDetails {
/**
* The username to look up in the {@link UserDetailsService}
*
* @return
*/
String value() default "user";
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2014 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.test.context.support;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;
/**
* A {@link WithUserDetailsSecurityContextFactory} that works with {@link WithUserDetails}.
*
* @see WithUserDetails
*
* @author Rob Winch
* @since 4.0
*/
final class WithUserDetailsSecurityContextFactory implements WithSecurityContextFactory<WithUserDetails> {
private UserDetailsService userDetailsService;
@Autowired
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public SecurityContext createSecurityContext(WithUserDetails withUser) {
String username = withUser.value();
Assert.hasLength(username, "value() must be non empty String");
UserDetails principal = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright 2002-2014 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.test.web.servlet.request;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import javax.servlet.ServletContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
/**
* Contains Spring Security related {@link MockMvc} {@link RequestBuilder}s.
*
* @author Rob Winch
* @since 4.0
*
*/
public final class SecurityMockMvcRequestBuilders {
/**
* Creates a request (including any necessary {@link CsrfToken}) that will
* submit a form based login to POST "/login".
*
* @return the FormLoginRequestBuilder for further customizations
*/
public static FormLoginRequestBuilder formLogin() {
return new FormLoginRequestBuilder();
}
/**
* Creates a request (including any necessary {@link CsrfToken}) that will
* submit a form based login to POST {@code loginProcessingUrl}.
*
* @param loginProcessingUrl
* the URL to POST to
*
* @return the FormLoginRequestBuilder for further customizations
*/
public static FormLoginRequestBuilder formLogin(String loginProcessingUrl) {
return formLogin().loginProcessingUrl(loginProcessingUrl);
}
/**
* Creates a logout request.
*
* @return the LogoutRequestBuilder for additional customizations
*/
public static LogoutRequestBuilder logout() {
return new LogoutRequestBuilder();
}
/**
* Creates a logout request (including any necessary {@link CsrfToken}) to
* the specified {@code logoutUrl}
*
* @return the LogoutRequestBuilder for additional customizations
*/
public static LogoutRequestBuilder logout(String logoutUrl) {
return new LogoutRequestBuilder().logoutUrl(logoutUrl);
}
/**
* Creates a logout request (including any necessary {@link CsrfToken})
*
* @author Rob Winch
* @since 4.0
*/
public static final class LogoutRequestBuilder implements RequestBuilder {
private String logoutUrl = "/logout";
private RequestPostProcessor postProcessor = csrf();
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
MockHttpServletRequest request = post(logoutUrl)
.buildRequest(servletContext);
return postProcessor.postProcessRequest(request);
}
/**
* Specifies the logout URL to POST to. Defaults to "/logout".
*
* @param logoutUrl the logout URL to POST to. Defaults to "/logout".
* @return the {@link LogoutRequestBuilder} for additional customizations
*/
public LogoutRequestBuilder logoutUrl(String logoutUrl) {
this.logoutUrl = logoutUrl;
return this;
}
private LogoutRequestBuilder() {}
}
/**
* Creates a form based login request including any necessary {@link CsrfToken}.
*
* @author Rob Winch
* @since 4.0
*/
public static final class FormLoginRequestBuilder implements RequestBuilder {
private String usernameParam = "username";
private String passwordParam = "password";
private String username = "user";
private String password = "password";
private String loginProcessingUrl = "/login";
private RequestPostProcessor postProcessor = csrf();
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
MockHttpServletRequest request = post(loginProcessingUrl)
.param(usernameParam,username)
.param(passwordParam, password)
.buildRequest(servletContext);
return postProcessor.postProcessRequest(request);
}
/**
* Specifies the URL to POST to. Default is "/login"
*
* @param loginProcessingUrl the URL to POST to. Default is "/login"
* @return
*/
public FormLoginRequestBuilder loginProcessingUrl(String loginProcessingUrl) {
this.loginProcessingUrl = loginProcessingUrl;
return this;
}
/**
* The HTTP parameter to place the username. Default is "username".
* @param usernameParameter the HTTP parameter to place the username. Default is "username".
* @return the {@link FormLoginRequestBuilder} for additional customizations
*/
public FormLoginRequestBuilder userParameter(String usernameParameter) {
this.usernameParam = usernameParameter;
return this;
}
/**
* The HTTP parameter to place the password. Default is "password".
* @param passwordParameter the HTTP parameter to place the password. Default is "password".
* @return the {@link FormLoginRequestBuilder} for additional customizations
*/
public FormLoginRequestBuilder passwordParam(String passwordParameter) {
this.passwordParam = passwordParameter;
return this;
}
/**
* The value of the password parameter. Default is "password".
* @param password the value of the password parameter. Default is "password".
* @return the {@link FormLoginRequestBuilder} for additional customizations
*/
public FormLoginRequestBuilder password(String password) {
this.password = password;
return this;
}
/**
* The value of the username parameter. Default is "user".
* @param username the value of the username parameter. Default is "user".
* @return the {@link FormLoginRequestBuilder} for additional customizations
*/
public FormLoginRequestBuilder user(String username) {
this.username = username;
return this;
}
/**
* Specify both the password parameter name and the password.
*
* @param passwordParameter the HTTP parameter to place the password. Default is "password".
* @param password the value of the password parameter. Default is "password".
* @return the {@link FormLoginRequestBuilder} for additional customizations
*/
public FormLoginRequestBuilder password(String passwordParameter, String password) {
passwordParam(passwordParameter);
this.password = password;
return this;
}
/**
* Specify both the password parameter name and the password.
*
* @param usernameParameter the HTTP parameter to place the username. Default is "username".
* @param username the value of the username parameter. Default is "user".
* @return the {@link FormLoginRequestBuilder} for additional customizations
*/
public FormLoginRequestBuilder user(String usernameParameter, String username) {
userParameter(usernameParameter);
this.username = username;
return this;
}
private FormLoginRequestBuilder() {}
}
private SecurityMockMvcRequestBuilders() {}
}

View File

@@ -0,0 +1,491 @@
/*
* Copyright 2002-2014 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.test.web.servlet.request;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.test.context.TestSecurityContextHolder;
import org.springframework.security.test.web.support.WebTestUtils;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
/**
* Contains {@link MockMvc} {@link RequestPostProcessor} implementations for
* Spring Security.
*
* @author Rob Winch
* @since 4.0
*/
public final class SecurityMockMvcRequestPostProcessors {
/**
* Creates a {@link RequestPostProcessor} that will automatically populate a
* valid {@link CsrfToken} in the request.
*
* @return the {@link CsrfRequestPostProcessor} for further customizations.
*/
public static CsrfRequestPostProcessor csrf() {
return new CsrfRequestPostProcessor();
}
/**
* Creates a {@link RequestPostProcessor} that can be used to ensure that
* the resulting request is ran with the user in the
* {@link TestSecurityContextHolder}.
*
* @return the {@link RequestPostProcessor} to sue
*/
public static RequestPostProcessor testSecurityContext() {
return new TestSecurityContextHolderPostProcessor();
}
/**
* Establish a {@link SecurityContext} that has a
* {@link UsernamePasswordAuthenticationToken} for the
* {@link Authentication#getPrincipal()} and a {@link User} for the
* {@link UsernamePasswordAuthenticationToken#getPrincipal()}. All details
* are declarative and do not require that the user actually exists.
*
* @param username
* the username to populate
* @return the {@link UserRequestPostProcessor} for additional customization
*/
public static UserRequestPostProcessor user(String username) {
return new UserRequestPostProcessor(username);
}
/**
* Establish a {@link SecurityContext} that has a
* {@link UsernamePasswordAuthenticationToken} for the
* {@link Authentication#getPrincipal()} and a custom {@link UserDetails}
* for the {@link UsernamePasswordAuthenticationToken#getPrincipal()}. All
* details are declarative and do not require that the user actually exists.
*
* @param user
* the UserDetails to populate
* @return the {@link RequestPostProcessor} to use
*/
public static RequestPostProcessor user(UserDetails user) {
return new UserDetailsRequestPostProcessor(user);
}
/**
* Establish a {@link SecurityContext} that uses the specified {@link Authentication} for the
* {@link Authentication#getPrincipal()} and a custom {@link UserDetails}. All
* details are declarative and do not require that the user actually exists.
*
* @param user
* the UserDetails to populate
* @return the {@link RequestPostProcessor} to use
*/
public static RequestPostProcessor authentication(
Authentication authentication) {
return new AuthenticationRequestPostProcessor(authentication);
}
/**
* Establish the specified {@link SecurityContext} to be used.
*/
public static RequestPostProcessor securityContext(
SecurityContext securityContext) {
return new SecurityContextRequestPostProcessor(securityContext);
}
/**
* Convenience mechanism for setting the Authorization header to use HTTP
* Basic with the given username and password. This method will
* automatically perform the necessary Base64 encoding.
*
* @param username
* the username to include in the Authorization header.
* @param password the password to include in the Authorization header.
* @return the {@link RequestPostProcessor} to use
*/
public static RequestPostProcessor httpBasic(String username, String password) {
return new HttpBasicRequestPostProcessor(username, password);
}
/**
* Populates a valid {@link CsrfToken} into the request.
*
* @author Rob Winch
* @since 4.0
*/
public static class CsrfRequestPostProcessor implements
RequestPostProcessor {
private boolean asHeader;
private boolean useInvalidToken;
/*
* (non-Javadoc)
*
* @see
* org.springframework.test.web.servlet.request.RequestPostProcessor
* #postProcessRequest
* (org.springframework.mock.web.MockHttpServletRequest)
*/
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
CsrfTokenRepository repository = WebTestUtils
.getCsrfTokenRepository(request);
CsrfToken token = repository.generateToken(request);
repository.saveToken(token, request, new MockHttpServletResponse());
String tokenValue = useInvalidToken ? "invalid" + token.getToken() : token.getToken();
if(asHeader) {
request.addHeader(token.getHeaderName(), tokenValue);
} else {
request.setParameter(token.getParameterName(), tokenValue);
}
return request;
}
/**
* Instead of using the {@link CsrfToken} as a request parameter
* (default) will populate the {@link CsrfToken} as a header.
*
* @return the {@link CsrfRequestPostProcessor} for additional customizations
*/
public CsrfRequestPostProcessor asHeader() {
this.asHeader = true;
return this;
}
/**
* Populates an invalid token value on the request.
*
* @return the {@link CsrfRequestPostProcessor} for additional customizations
*/
public CsrfRequestPostProcessor useInvalidToken() {
this.useInvalidToken = true;
return this;
}
private CsrfRequestPostProcessor() {}
}
/**
* Support class for {@link RequestPostProcessor}'s that establish a Spring
* Security context
*/
private static abstract class SecurityContextRequestPostProcessorSupport {
/**
* Saves the specified {@link Authentication} into an empty
* {@link SecurityContext} using the {@link SecurityContextRepository}.
*
* @param authentication the {@link Authentication} to save
* @param request the {@link HttpServletRequest} to use
*/
final void save(Authentication authentication,
HttpServletRequest request) {
SecurityContext securityContext = SecurityContextHolder
.createEmptyContext();
securityContext.setAuthentication(authentication);
save(securityContext, request);
}
/**
* Saves the {@link SecurityContext} using the
* {@link SecurityContextRepository}
*
* @param securityContext the {@link SecurityContext} to save
* @param request the {@link HttpServletRequest} to use
*/
final void save(SecurityContext securityContext,
HttpServletRequest request) {
HttpServletResponse response = new MockHttpServletResponse();
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(
request, response);
SecurityContextRepository securityContextRepository = WebTestUtils
.getSecurityContextRepository(request);
securityContextRepository.loadContext(requestResponseHolder);
request = requestResponseHolder.getRequest();
response = requestResponseHolder.getResponse();
securityContextRepository.saveContext(securityContext, request,
response);
}
}
/**
* Associates the {@link SecurityContext} found in
* {@link TestSecurityContextHolder#getContext()} with the
* {@link MockHttpServletRequest}.
*
* @author Rob Winch
* @since 4.0
*/
private final static class TestSecurityContextHolderPostProcessor extends
SecurityContextRequestPostProcessorSupport implements
RequestPostProcessor {
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
save(TestSecurityContextHolder.getContext(), request);
return request;
}
}
/**
* Associates the specified {@link SecurityContext} with the
* {@link MockHttpServletRequest}.
*
* @author Rob Winch
* @since 4.0
*/
private final static class SecurityContextRequestPostProcessor extends
SecurityContextRequestPostProcessorSupport implements
RequestPostProcessor {
private final SecurityContext securityContext;
private SecurityContextRequestPostProcessor(
SecurityContext securityContext) {
this.securityContext = securityContext;
}
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
save(this.securityContext, request);
return request;
}
}
/**
* Sets the specified {@link Authentication} on an empty
* {@link SecurityContext} and associates it to the
* {@link MockHttpServletRequest}
*
* @author Rob Winch
* @since 4.0
*
*/
private final static class AuthenticationRequestPostProcessor extends
SecurityContextRequestPostProcessorSupport implements
RequestPostProcessor {
private final Authentication authentication;
private AuthenticationRequestPostProcessor(Authentication authentication) {
this.authentication = authentication;
}
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
save(authentication, request);
return request;
}
}
/**
* Creates a {@link UsernamePasswordAuthenticationToken} and sets the
* {@link UserDetails} as the principal and associates it to the
* {@link MockHttpServletRequest}.
*
* @author Rob Winch
* @since 4.0
*/
private final static class UserDetailsRequestPostProcessor implements
RequestPostProcessor {
private final RequestPostProcessor delegate;
public UserDetailsRequestPostProcessor(UserDetails user) {
Authentication token = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
delegate = new AuthenticationRequestPostProcessor(token);
}
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
return delegate.postProcessRequest(request);
}
}
/**
* Creates a {@link UsernamePasswordAuthenticationToken} and sets the
* principal to be a {@link User} and associates it to the
* {@link MockHttpServletRequest}.
*
* @author Rob Winch
* @since 4.0
*/
public final static class UserRequestPostProcessor extends
SecurityContextRequestPostProcessorSupport implements
RequestPostProcessor {
private String username;
private String password = "password";
private static final String ROLE_PREFIX = "ROLE_";
private Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.createAuthorityList("ROLE_USER");
private boolean enabled = true;
private boolean accountNonExpired = true;
private boolean credentialsNonExpired = true;
private boolean accountNonLocked = true;
/**
* Creates a new instance with the given username
* @param username the username to use
*/
private UserRequestPostProcessor(String username) {
Assert.notNull(username, "username cannot be null");
this.username = username;
}
/**
* Specify the roles of the user to authenticate as. This method is
* similar to {@link #authorities(GrantedAuthority...)}, but just not as
* flexible.
*
* @param roles
* The roles to populate. Note that if the role does not
* start with {@link #rolePrefix(String)} it will
* automatically be prepended. This means by default
* {@code roles("ROLE_USER")} and {@code roles("USER")} are
* equivalent.
* @see #authorities(GrantedAuthority...)
* @see #rolePrefix(String)
* @return the UserRequestPostProcessor for further customizations
*/
public UserRequestPostProcessor roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(
roles.length);
for (String role : roles) {
if (role.startsWith(ROLE_PREFIX)) {
throw new IllegalArgumentException("Role should not start with "+ROLE_PREFIX + " since this method automatically prefixes with this value. Got "+ role);
} else {
authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX
+ role));
}
}
this.authorities = authorities;
return this;
}
/**
* Populates the user's {@link GrantedAuthority}'s. The default is
* ROLE_USER.
*
* @param authorities
* @see #roles(String...)
* @return the UserRequestPostProcessor for further customizations
*/
public UserRequestPostProcessor authorities(
GrantedAuthority... authorities) {
return authorities(Arrays.asList(authorities));
}
/**
* Populates the user's {@link GrantedAuthority}'s. The default is
* ROLE_USER.
*
* @param authorities
* @see #roles(String...)
* @return the UserRequestPostProcessor for further customizations
*/
public UserRequestPostProcessor authorities(
Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
return this;
}
/**
* Populates the user's password. The default is "password"
*
* @param password
* the user's password
* @return the UserRequestPostProcessor for further customizations
*/
public UserRequestPostProcessor password(String password) {
this.password = password;
return this;
}
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
UserDetailsRequestPostProcessor delegate = new UserDetailsRequestPostProcessor(createUser());
return delegate.postProcessRequest(request);
}
/**
* Creates a new {@link User}
* @return the {@link User} for the principal
*/
private User createUser() {
return new User(username, password, enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities);
}
}
private static class HttpBasicRequestPostProcessor implements RequestPostProcessor {
private String headerValue;
private HttpBasicRequestPostProcessor(String username, String password) {
byte[] toEncode;
try {
toEncode = (username + ":" + password).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
this.headerValue = "Basic " + new String(Base64.encode(toEncode));
}
public MockHttpServletRequest postProcessRequest(
MockHttpServletRequest request) {
request.addHeader("Authorization", headerValue);
return request;
}
}
private SecurityMockMvcRequestPostProcessors() { }
}

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2002-2014 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.test.web.servlet.response;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import static org.springframework.test.util.AssertionErrors.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.test.web.support.WebTestUtils;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
/**
* Security related {@link MockMvc} {@link ResultMatcher}s.
*
* @author Rob Winch
* @since 4.0
*/
public final class SecurityMockMvcResultMatchers {
/**
* {@link ResultMatcher} that verifies that a specified user is
* authenticated.
*
* @return the {@link AuthenticatedMatcher} to use
*/
public static AuthenticatedMatcher authenticated() {
return new AuthenticatedMatcher();
}
/**
* {@link ResultMatcher} that verifies that no user is authenticated.
*
* @return the {@link AuthenticatedMatcher} to use
*/
public static ResultMatcher unauthenticated() {
return new UnAuthenticatedMatcher();
}
private static abstract class AuthenticationMatcher<T extends AuthenticationMatcher<T>> implements ResultMatcher {
protected SecurityContext load(MvcResult result) {
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(result.getRequest(), result.getResponse());
SecurityContextRepository repository = WebTestUtils.getSecurityContextRepository(result.getRequest());
return repository.loadContext(holder);
}
}
/**
* A {@link MockMvc} {@link ResultMatcher} that verifies a specific user is
* associated to the {@link MvcResult}.
*
* @author Rob Winch
* @since 4.0
*/
public static final class AuthenticatedMatcher extends AuthenticationMatcher<AuthenticatedMatcher> {
private SecurityContext expectedContext;
private Authentication expectedAuthentication;
private Object expectedAuthenticationPrincipal;
private String expectedAuthenticationName;
private Collection<GrantedAuthority> expectedGrantedAuthorities;
public void match(MvcResult result) throws Exception {
SecurityContext context = load(result);
Authentication auth = context.getAuthentication();
assertTrue("Authentication should not be null", auth != null);
if(this.expectedContext != null) {
assertEquals(this.expectedContext + " does not equal " + context, this.expectedContext, context);
}
if(this.expectedAuthentication != null) {
assertEquals(this.expectedAuthentication + " does not equal " + context.getAuthentication(), this.expectedAuthentication, context.getAuthentication());
}
if(this.expectedAuthenticationPrincipal != null) {
assertTrue("Authentication cannot be null", context.getAuthentication() != null);
assertEquals(this.expectedAuthenticationPrincipal + " does not equal " + context.getAuthentication().getPrincipal(), this.expectedAuthenticationPrincipal, context.getAuthentication().getPrincipal());
}
if(this.expectedAuthenticationName != null) {
assertTrue("Authentication cannot be null", auth != null);
String name = auth.getName();
assertEquals(this.expectedAuthenticationName + " does not equal " + name, this.expectedAuthenticationName, name);
}
if(this.expectedGrantedAuthorities != null) {
assertTrue("Authentication cannot be null", auth != null);
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
assertEquals(this.expectedGrantedAuthorities + " does not equal " + authorities, this.expectedGrantedAuthorities, authorities);
}
}
/**
* Specifies the expected username
*
* @param expected
* the expected username
* @return the {@link AuthenticatedMatcher} for further customization
*/
public AuthenticatedMatcher withUsername(String expected) {
return withAuthenticationName(expected);
}
/**
* Specifies the expected {@link SecurityContext}
*
* @param expected
* the expected {@link SecurityContext}
* @return the {@link AuthenticatedMatcher} for further customization
*/
public AuthenticatedMatcher withSecurityContext(SecurityContext expected) {
this.expectedContext = expected;
return this;
}
/**
* Specifies the expected {@link Authentication}
*
* @param expected
* the expected {@link Authentication}
* @return the {@link AuthenticatedMatcher} for further customization
*/
public AuthenticatedMatcher withAuthentication(Authentication expected) {
this.expectedAuthentication = expected;
return this;
}
/**
* Specifies the expected principal
*
* @param expected
* the expected principal
* @return the {@link AuthenticatedMatcher} for further customization
*/
public AuthenticatedMatcher withAuthenticationPrincipal(Object expected) {
this.expectedAuthenticationPrincipal = expected;
return this;
}
/**
* Specifies the expected {@link Authentication#getName()}
*
* @param expected
* the expected {@link Authentication#getName()}
* @return the {@link AuthenticatedMatcher} for further customization
*/
public AuthenticatedMatcher withAuthenticationName(String expected) {
this.expectedAuthenticationName = expected;
return this;
}
/**
* Specifies the {@link Authentication#getAuthorities()}
*
* @param expected the {@link Authentication#getAuthorities()}
* @return the {@link AuthenticatedMatcher} for further customization
*/
public AuthenticatedMatcher withAuthorities(Collection<GrantedAuthority> expected) {
this.expectedGrantedAuthorities = expected;
return this;
}
/**
* Specifies the {@link Authentication#getAuthorities()}
*
* @param expected the roles. Each value is automatically prefixed with "ROLE_"
* @return the {@link AuthenticatedMatcher} for further customization
*/
public AuthenticatedMatcher withRoles(String... roles) {
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for(String role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
}
return withAuthorities(authorities);
}
AuthenticatedMatcher() {}
}
/**
* A {@link MockMvc} {@link ResultMatcher} that verifies no
* {@link Authentication} is associated with the {@link MvcResult}.
*
* @author Rob Winch
* @since 4.0
*/
private static final class UnAuthenticatedMatcher extends AuthenticationMatcher<UnAuthenticatedMatcher>{
public void match(MvcResult result) throws Exception {
SecurityContext context = load(result);
assertEquals("",null,context.getAuthentication());
}
private UnAuthenticatedMatcher() {}
}
private SecurityMockMvcResultMatchers() {}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2014 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.test.web.support;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* A utility class for testing spring security
*
* @author Rob Winch
* @since 4.0
*/
public abstract class WebTestUtils {
private static final SecurityContextRepository DEFAULT_CONTEXT_REPO = new HttpSessionSecurityContextRepository();
private static final CsrfTokenRepository DEFAULT_TOKEN_REPO = new HttpSessionCsrfTokenRepository();
/**
* Gets the {@link SecurityContextRepository} for the specified
* {@link HttpServletRequest}. If one is not found, a default
* {@link HttpSessionSecurityContextRepository} is used.
*
* @param request
* the {@link HttpServletRequest} to obtain the
* {@link SecurityContextRepository}
* @return the {@link SecurityContextRepository} for the specified
* {@link HttpServletRequest}
*/
public static SecurityContextRepository getSecurityContextRepository(HttpServletRequest request) {
SecurityContextPersistenceFilter filter = findFilter(request, SecurityContextPersistenceFilter.class);
if(filter == null) {
return DEFAULT_CONTEXT_REPO;
}
return (SecurityContextRepository) ReflectionTestUtils.getField(filter, "repo");
}
/**
* Gets the {@link CsrfTokenRepository} for the specified
* {@link HttpServletRequest}. If one is not found, the default
* {@link HttpSessionCsrfTokenRepository} is used.
*
* @param request
* the {@link HttpServletRequest} to obtain the
* {@link CsrfTokenRepository}
* @return the {@link CsrfTokenRepository} for the specified
* {@link HttpServletRequest}
*/
public static CsrfTokenRepository getCsrfTokenRepository(HttpServletRequest request) {
CsrfFilter filter = findFilter(request, CsrfFilter.class);
if(filter == null) {
return DEFAULT_TOKEN_REPO;
}
return (CsrfTokenRepository) ReflectionTestUtils.getField(filter, "tokenRepository");
}
@SuppressWarnings("unchecked")
private static <T extends Filter> T findFilter(HttpServletRequest request, Class<T> filterClass) {
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
if(webApplicationContext == null) {
return null;
}
FilterChainProxy springSecurityFilterChain = null;
try {
springSecurityFilterChain = webApplicationContext.getBean(FilterChainProxy.class);
} catch(NoSuchBeanDefinitionException notFound) {
return null;
}
List<Filter> filters = (List<Filter>) ReflectionTestUtils.invokeMethod(springSecurityFilterChain,"getFilters", request);
for(Filter filter : filters) {
if(filterClass.isAssignableFrom(filter.getClass())) {
return (T) filter;
}
}
return null;
}
private WebTestUtils() {}
}