mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-01 02:49:11 +00:00
Refactoring HTTP config tests to use spock and groovy MarkupBuilder
This commit is contained in:
parent
080430150a
commit
b0758dd8de
@ -14,6 +14,7 @@ allprojects {
|
||||
mavenRepo name: 'SpringSource Maven Snapshot Repo', urls: 'http://maven.springframework.org/snapshot/'
|
||||
mavenRepo name: 'SpringSource Enterprise Release', urls: 'http://repository.springsource.com/maven/bundles/release'
|
||||
mavenRepo name: 'SpringSource Enterprise External', urls: 'http://repository.springsource.com/maven/bundles/external'
|
||||
mavenRepo(name: 'Spock Snapshots', urls: 'http://m2repo.spockframework.org/snapshots')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
// Config Module build file
|
||||
|
||||
apply plugin: 'groovy'
|
||||
|
||||
compileTestJava.dependsOn(':spring-security-core:compileTestJava')
|
||||
|
||||
dependencies {
|
||||
@ -14,13 +16,17 @@ dependencies {
|
||||
|
||||
provided "javax.servlet:servlet-api:2.5"
|
||||
|
||||
groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.1'
|
||||
|
||||
testCompile project(':spring-security-ldap'),
|
||||
project(':spring-security-openid'),
|
||||
'org.openid4java:openid4java-nodeps:0.9.5',
|
||||
files(this.project(':spring-security-core').sourceSets.test.classesDir),
|
||||
'javax.annotation:jsr250-api:1.0',
|
||||
"org.springframework.ldap:spring-ldap-core:$springLdapVersion",
|
||||
"org.springframework:spring-jdbc:$springVersion",
|
||||
"org.springframework:spring-tx:$springVersion"
|
||||
"org.springframework:spring-tx:$springVersion",
|
||||
'org.spockframework:spock-core:0.4-groovy-1.7'
|
||||
|
||||
testRuntime "hsqldb:hsqldb:$hsqlVersion",
|
||||
"cglib:cglib-nodep:2.2"
|
||||
|
@ -0,0 +1,71 @@
|
||||
package org.springframework.security.config
|
||||
|
||||
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML;
|
||||
|
||||
import groovy.xml.MarkupBuilder
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.support.AbstractXmlApplicationContext;
|
||||
import org.springframework.security.config.util.InMemoryXmlApplicationContext
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
abstract class AbstractXmlConfigTests extends Specification {
|
||||
AbstractXmlApplicationContext appContext;
|
||||
Writer writer;
|
||||
MarkupBuilder xml;
|
||||
|
||||
def setup() {
|
||||
writer = new StringWriter()
|
||||
xml = new MarkupBuilder(writer)
|
||||
}
|
||||
|
||||
def cleanup() {
|
||||
if (appContext != null) {
|
||||
appContext.close();
|
||||
appContext = null;
|
||||
}
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
def bean(String name, Class clazz) {
|
||||
xml.'b:bean'(id: name, 'class': clazz.name)
|
||||
}
|
||||
|
||||
def bean(String name, String clazz) {
|
||||
xml.'b:bean'(id: name, 'class': clazz)
|
||||
}
|
||||
|
||||
def bean(String name, String clazz, List constructorArgs) {
|
||||
xml.'b:bean'(id: name, 'class': clazz) {
|
||||
constructorArgs.each { val ->
|
||||
'b:constructor-arg'(value: val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def bean(String name, String clazz, Map properties, Map refs) {
|
||||
xml.'b:bean'(id: name, 'class': clazz) {
|
||||
properties.each {key, val ->
|
||||
'b:property'(name: key, value: val)
|
||||
}
|
||||
refs.each {key, val ->
|
||||
'b:property'(name: key, ref: val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createAppContext() {
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
}
|
||||
|
||||
def createAppContext(String extraXml) {
|
||||
appContext = new InMemoryXmlApplicationContext(writer.toString() + extraXml);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groovy.xml.MarkupBuilder
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
import org.springframework.mock.web.MockFilterChain
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.security.config.AbstractXmlConfigTests
|
||||
import org.springframework.security.config.BeanIds
|
||||
import org.springframework.security.config.util.InMemoryXmlApplicationContext
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.web.FilterChainProxy
|
||||
import org.springframework.security.web.FilterInvocation
|
||||
|
||||
abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
|
||||
final int AUTO_CONFIG_FILTERS = 11;
|
||||
|
||||
def httpAutoConfig(Closure c) {
|
||||
xml.http('auto-config': 'true', c)
|
||||
}
|
||||
|
||||
def httpAutoConfig(String matcher, Closure c) {
|
||||
xml.http(['auto-config': 'true', 'request-matcher': matcher], c)
|
||||
}
|
||||
|
||||
def interceptUrl(String path, String authz) {
|
||||
xml.'intercept-url'(pattern: path, access: authz)
|
||||
}
|
||||
|
||||
def interceptUrl(String path, String httpMethod, String authz) {
|
||||
xml.'intercept-url'(pattern: path, method: httpMethod, access: authz)
|
||||
}
|
||||
|
||||
|
||||
def interceptUrlNoFilters(String path) {
|
||||
xml.'intercept-url'(pattern: path, filters: 'none')
|
||||
}
|
||||
|
||||
Filter getFilter(Class type) {
|
||||
List filters = getFilters("/any");
|
||||
|
||||
for (f in filters) {
|
||||
if (f.class.isAssignableFrom(type)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
List getFilters(String url) {
|
||||
FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
|
||||
return fcp.getFilters(url)
|
||||
}
|
||||
|
||||
FilterInvocation createFilterinvocation(String path, String method) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setMethod(method);
|
||||
request.setRequestURI(null);
|
||||
request.setServletPath(path);
|
||||
|
||||
return new FilterInvocation(request, new MockHttpServletResponse(), new MockFilterChain());
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
|
||||
import org.springframework.security.util.FieldUtils
|
||||
import org.springframework.security.web.access.AccessDeniedHandlerImpl
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
class AccessDeniedConfigTests extends AbstractHttpConfigTests {
|
||||
private static final String ACCESS_DENIED_PAGE = 'access-denied-page';
|
||||
|
||||
def accessDeniedPageAttributeIsSupported() {
|
||||
httpAccessDeniedPage ('/accessDenied') { }
|
||||
createAppContext();
|
||||
|
||||
expect:
|
||||
getFilter(ExceptionTranslationFilter.class).accessDeniedHandler.errorPage == '/accessDenied'
|
||||
|
||||
}
|
||||
|
||||
def invalidAccessDeniedUrlIsDetected() {
|
||||
when:
|
||||
httpAccessDeniedPage ('noLeadingSlash') { }
|
||||
createAppContext();
|
||||
then:
|
||||
BeanCreationException e = thrown()
|
||||
}
|
||||
|
||||
def accessDeniedHandlerIsSetCorectly() {
|
||||
httpAutoConfig() {
|
||||
'access-denied-handler'(ref: 'adh')
|
||||
}
|
||||
bean('adh', AccessDeniedHandlerImpl)
|
||||
createAppContext();
|
||||
|
||||
def filter = getFilter(ExceptionTranslationFilter.class);
|
||||
def adh = appContext.getBean("adh");
|
||||
|
||||
expect:
|
||||
filter.accessDeniedHandler == adh
|
||||
}
|
||||
|
||||
def void accessDeniedPageAndAccessDeniedHandlerAreMutuallyExclusive() {
|
||||
when:
|
||||
httpAccessDeniedPage ('/accessDenied') {
|
||||
'access-denied-handler'('error-page': '/go-away')
|
||||
}
|
||||
createAppContext();
|
||||
then:
|
||||
BeanDefinitionParsingException e = thrown()
|
||||
}
|
||||
|
||||
def void accessDeniedHandlerPageAndRefAreMutuallyExclusive() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'access-denied-handler'('error-page': '/go-away', ref: 'adh')
|
||||
}
|
||||
createAppContext();
|
||||
bean('adh', AccessDeniedHandlerImpl)
|
||||
then:
|
||||
BeanDefinitionParsingException e = thrown()
|
||||
}
|
||||
|
||||
def httpAccessDeniedPage(String page, Closure c) {
|
||||
xml.http(['auto-config': 'true', 'access-denied-page': page], c)
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException
|
||||
import org.springframework.security.util.FieldUtils
|
||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
class FormLoginConfigTests extends AbstractHttpConfigTests {
|
||||
|
||||
def formLoginWithNoLoginPageAddsDefaultLoginPageFilter() {
|
||||
httpAutoConfig('ant') {
|
||||
form-login()
|
||||
}
|
||||
createAppContext()
|
||||
filtersMatchExpectedAutoConfigList();
|
||||
}
|
||||
|
||||
def 'Form login alwaysUseDefaultTarget sets correct property'() {
|
||||
xml.http {
|
||||
'form-login'('default-target-url':'/default', 'always-use-default-target': 'true')
|
||||
}
|
||||
createAppContext()
|
||||
def filter = getFilter(UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
expect:
|
||||
FieldUtils.getFieldValue(filter, 'successHandler.defaultTargetUrl') == '/default';
|
||||
FieldUtils.getFieldValue(filter, 'successHandler.alwaysUseDefaultTargetUrl') == true;
|
||||
}
|
||||
|
||||
def invalidLoginPageIsDetected() {
|
||||
when:
|
||||
xml.http {
|
||||
'form-login'('login-page': 'noLeadingSlash')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
BeanCreationException e = thrown();
|
||||
}
|
||||
|
||||
def invalidDefaultTargetUrlIsDetected() {
|
||||
when:
|
||||
xml.http {
|
||||
'form-login'('default-target-url': 'noLeadingSlash')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
BeanCreationException e = thrown();
|
||||
}
|
||||
|
||||
def customSuccessAndFailureHandlersCanBeSetThroughTheNamespace() {
|
||||
xml.http {
|
||||
'form-login'('authentication-success-handler-ref': 'sh', 'authentication-failure-handler-ref':'fh')
|
||||
}
|
||||
bean('sh', SavedRequestAwareAuthenticationSuccessHandler.class.name)
|
||||
bean('fh', SimpleUrlAuthenticationFailureHandler.class.name)
|
||||
createAppContext()
|
||||
|
||||
def apf = getFilter(UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
expect:
|
||||
FieldUtils.getFieldValue(apf, "successHandler") == appContext.getBean("sh");
|
||||
FieldUtils.getFieldValue(apf, "failureHandler") == appContext.getBean("fh")
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
|
||||
import org.springframework.mock.web.MockFilterChain
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.security.config.BeanIds
|
||||
import org.springframework.security.openid.OpenIDAuthenticationFilter
|
||||
import org.springframework.security.openid.OpenIDAuthenticationToken
|
||||
import org.springframework.security.openid.OpenIDConsumer
|
||||
import org.springframework.security.openid.OpenIDConsumerException
|
||||
import org.springframework.security.web.FilterChainProxy
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter
|
||||
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
class OpenIDConfigTests extends AbstractHttpConfigTests {
|
||||
def openIDAndFormLoginWorkTogether() {
|
||||
xml.http() {
|
||||
'openid-login'()
|
||||
'form-login'()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def etf = getFilter(ExceptionTranslationFilter)
|
||||
def ap = etf.getAuthenticationEntryPoint();
|
||||
|
||||
expect:
|
||||
ap.loginFormUrl == "/spring_security_login"
|
||||
// Default login filter should be present since we haven't specified any login URLs
|
||||
getFilter(DefaultLoginPageGeneratingFilter) != null
|
||||
}
|
||||
|
||||
def formLoginEntryPointTakesPrecedenceIfLoginUrlIsSet() {
|
||||
xml.http() {
|
||||
'openid-login'()
|
||||
'form-login'('login-page': '/form-page')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == '/form-page'
|
||||
}
|
||||
|
||||
def openIDEntryPointTakesPrecedenceIfLoginUrlIsSet() {
|
||||
xml.http() {
|
||||
'openid-login'('login-page': '/openid-page')
|
||||
'form-login'()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == '/openid-page'
|
||||
}
|
||||
|
||||
def multipleLoginPagesCausesError() {
|
||||
when:
|
||||
xml.http() {
|
||||
'openid-login'('login-page': '/openid-page')
|
||||
'form-login'('login-page': '/form-page')
|
||||
}
|
||||
createAppContext()
|
||||
then:
|
||||
thrown(BeanDefinitionParsingException)
|
||||
}
|
||||
|
||||
def openIDAndRememberMeWorkTogether() {
|
||||
xml.http() {
|
||||
interceptUrl('/**', 'ROLE_NOBODY')
|
||||
'openid-login'()
|
||||
'remember-me'()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
// Default login filter should be present since we haven't specified any login URLs
|
||||
def loginFilter = getFilter(DefaultLoginPageGeneratingFilter)
|
||||
def openIDFilter = getFilter(OpenIDAuthenticationFilter)
|
||||
openIDFilter.setConsumer(new OpenIDConsumer() {
|
||||
public String beginConsumption(HttpServletRequest req, String claimedIdentity, String returnToUrl, String realm)
|
||||
throws OpenIDConsumerException {
|
||||
return "http://testopenid.com?openid.return_to=" + returnToUrl;
|
||||
}
|
||||
|
||||
public OpenIDAuthenticationToken endConsumption(HttpServletRequest req) throws OpenIDConsumerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
})
|
||||
Set<String> returnToUrlParameters = new HashSet<String>()
|
||||
returnToUrlParameters.add(AbstractRememberMeServices.DEFAULT_PARAMETER)
|
||||
openIDFilter.setReturnToUrlParameters(returnToUrlParameters)
|
||||
assert loginFilter.openIDrememberMeParameter != null
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
when: "Initial request is made"
|
||||
FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY)
|
||||
request.setServletPath("/something.html")
|
||||
fcp.doFilter(request, response, new MockFilterChain())
|
||||
then: "Redirected to login"
|
||||
response.getRedirectedUrl().endsWith("/spring_security_login")
|
||||
when: "Login page is requested"
|
||||
request.setServletPath("/spring_security_login")
|
||||
request.setRequestURI("/spring_security_login")
|
||||
response = new MockHttpServletResponse()
|
||||
fcp.doFilter(request, response, new MockFilterChain())
|
||||
then: "Remember-me choice is added to page"
|
||||
response.getContentAsString().contains(AbstractRememberMeServices.DEFAULT_PARAMETER)
|
||||
when: "Login is submitted with remember-me selected"
|
||||
request.setRequestURI("/j_spring_openid_security_check")
|
||||
request.setParameter(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, "http://hey.openid.com/")
|
||||
request.setParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "on")
|
||||
response = new MockHttpServletResponse();
|
||||
fcp.doFilter(request, response, new MockFilterChain());
|
||||
String expectedReturnTo = request.getRequestURL().append("?")
|
||||
.append(AbstractRememberMeServices.DEFAULT_PARAMETER)
|
||||
.append("=").append("on").toString();
|
||||
then: "return_to URL contains remember-me choice"
|
||||
response.getRedirectedUrl() == "http://testopenid.com?openid.return_to=" + expectedReturnTo
|
||||
}
|
||||
|
||||
def openIDWithAttributeExchangeConfigurationIsParsedCorrectly() {
|
||||
xml.http() {
|
||||
'openid-login'() {
|
||||
'attribute-exchange'() {
|
||||
'openid-attribute'(name: 'nickname', type: 'http://schema.openid.net/namePerson/friendly')
|
||||
'openid-attribute'(name: 'email', type: 'http://schema.openid.net/contact/email', required: 'true',
|
||||
'count': '2')
|
||||
}
|
||||
}
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
List attributes = getFilter(OpenIDAuthenticationFilter).consumer.attributesToFetchFactory.createAttributeList('http://someid')
|
||||
|
||||
expect:
|
||||
attributes.size() == 2
|
||||
attributes[0].name == 'nickname'
|
||||
attributes[0].type == 'http://schema.openid.net/namePerson/friendly'
|
||||
attributes[0].required == false
|
||||
attributes[1].required == true
|
||||
attributes[1].getCount() == 2
|
||||
}
|
||||
}
|
@ -0,0 +1,497 @@
|
||||
package org.springframework.security.config.http;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.servlet.Filter
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.BeansException
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.context.support.AbstractXmlApplicationContext
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.util.FieldUtils;
|
||||
import org.springframework.security.access.AccessDeniedException
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken
|
||||
import org.springframework.security.config.MockUserServiceBeanPostProcessor;
|
||||
import org.springframework.security.config.PostProcessedMockUserDetailsService;
|
||||
import org.springframework.security.web.*;
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
import org.springframework.security.web.authentication.*
|
||||
import org.springframework.security.web.authentication.logout.LogoutFilter
|
||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
|
||||
import org.springframework.security.web.context.*;
|
||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache
|
||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
||||
import org.springframework.security.web.session.SessionManagementFilter;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
|
||||
class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
||||
def 'Minimal configuration parses'() {
|
||||
setup:
|
||||
xml.http {
|
||||
'http-basic'()
|
||||
}
|
||||
createAppContext()
|
||||
}
|
||||
|
||||
def httpAutoConfigSetsUpCorrectFilterList() {
|
||||
when:
|
||||
xml.http('auto-config': 'true')
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
filtersMatchExpectedAutoConfigList('/anyurl');
|
||||
}
|
||||
|
||||
void filtersMatchExpectedAutoConfigList(String url) {
|
||||
def filterList = getFilters(url);
|
||||
Iterator<Filter> filters = filterList.iterator();
|
||||
|
||||
assert filters.next() instanceof SecurityContextPersistenceFilter
|
||||
assert filters.next() instanceof LogoutFilter
|
||||
Object authProcFilter = filters.next();
|
||||
assert authProcFilter instanceof UsernamePasswordAuthenticationFilter
|
||||
assert filters.next() instanceof DefaultLoginPageGeneratingFilter
|
||||
assert filters.next() instanceof BasicAuthenticationFilter
|
||||
assert filters.next() instanceof RequestCacheAwareFilter
|
||||
assert filters.next() instanceof SecurityContextHolderAwareRequestFilter
|
||||
assert filters.next() instanceof AnonymousAuthenticationFilter
|
||||
assert filters.next() instanceof SessionManagementFilter
|
||||
assert filters.next() instanceof ExceptionTranslationFilter
|
||||
Object fsiObj = filters.next();
|
||||
assert fsiObj instanceof FilterSecurityInterceptor
|
||||
def fsi = (FilterSecurityInterceptor) fsiObj;
|
||||
assert fsi.isObserveOncePerRequest()
|
||||
}
|
||||
|
||||
def duplicateElementCausesError() {
|
||||
when: "Two http blocks are defined"
|
||||
xml.http('auto-config': 'true')
|
||||
xml.http('auto-config': 'true')
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
BeanDefinitionParsingException e = thrown();
|
||||
}
|
||||
|
||||
def filterListShouldBeEmptyForPatternWithNoFilters() {
|
||||
httpAutoConfig() {
|
||||
interceptUrlNoFilters('/unprotected')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilters("/unprotected").size() == 0
|
||||
}
|
||||
|
||||
def regexPathsWorkCorrectly() {
|
||||
httpAutoConfig('regex') {
|
||||
interceptUrlNoFilters('\\A\\/[a-z]+')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilters('/imlowercase').size() == 0
|
||||
filtersMatchExpectedAutoConfigList('/MixedCase');
|
||||
}
|
||||
|
||||
def ciRegexPathsWorkCorrectly() {
|
||||
when:
|
||||
httpAutoConfig('ciRegex') {
|
||||
interceptUrlNoFilters('\\A\\/[a-z]+')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
getFilters('/imMixedCase').size() == 0
|
||||
filtersMatchExpectedAutoConfigList('/Im_caught_by_the_Universal_Match');
|
||||
}
|
||||
|
||||
// SEC-1152
|
||||
def anonymousFilterIsAddedByDefault() {
|
||||
xml.http {
|
||||
'form-login'()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilters("/anything")[5] instanceof AnonymousAuthenticationFilter
|
||||
}
|
||||
|
||||
def anonymousFilterIsRemovedIfDisabledFlagSet() {
|
||||
xml.http {
|
||||
'form-login'()
|
||||
'anonymous'(enabled: 'false')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
!(getFilters("/anything").get(5) instanceof AnonymousAuthenticationFilter)
|
||||
}
|
||||
|
||||
def anonymousCustomAttributesAreSetCorrectly() {
|
||||
xml.http {
|
||||
'form-login'()
|
||||
'anonymous'(username: 'joe', 'granted-authority':'anonymity', key: 'customKey')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
AnonymousAuthenticationFilter filter = getFilter(AnonymousAuthenticationFilter);
|
||||
|
||||
expect:
|
||||
'customKey' == filter.getKey()
|
||||
'joe' == filter.userAttribute.password
|
||||
'anonymity' == filter.userAttribute.authorities[0].authority
|
||||
}
|
||||
|
||||
def httpMethodMatchIsSupported() {
|
||||
httpAutoConfig {
|
||||
interceptUrl '/secure*', 'DELETE', 'ROLE_SUPERVISOR'
|
||||
interceptUrl '/secure*', 'POST', 'ROLE_A,ROLE_B'
|
||||
interceptUrl '/**', 'ROLE_C'
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def fids = getFilter(FilterSecurityInterceptor).getSecurityMetadataSource();
|
||||
def attrs = fids.getAttributes(createFilterinvocation("/secure", "POST"));
|
||||
|
||||
expect:
|
||||
attrs.size() == 2
|
||||
attrs.contains(new SecurityConfig("ROLE_A"))
|
||||
attrs.contains(new SecurityConfig("ROLE_B"))
|
||||
}
|
||||
|
||||
def oncePerRequestAttributeIsSupported() {
|
||||
xml.http('once-per-request': 'false') {
|
||||
'http-basic'()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
!getFilter(FilterSecurityInterceptor).isObserveOncePerRequest()
|
||||
}
|
||||
|
||||
def httpBasicSupportsSeparateEntryPoint() {
|
||||
xml.http() {
|
||||
'http-basic'('entry-point-ref': 'ep')
|
||||
}
|
||||
bean('ep', BasicAuthenticationEntryPoint.class.name, ['realmName':'whocares'],[:])
|
||||
createAppContext();
|
||||
|
||||
def baf = getFilter(BasicAuthenticationFilter)
|
||||
def etf = getFilter(ExceptionTranslationFilter)
|
||||
def ep = appContext.getBean("ep")
|
||||
|
||||
expect:
|
||||
baf.authenticationEntryPoint == ep
|
||||
// Since no other authentication system is in use, this should also end up on the ETF
|
||||
etf.authenticationEntryPoint == ep
|
||||
}
|
||||
|
||||
def interceptUrlWithRequiresChannelAddsChannelFilterToStack() {
|
||||
httpAutoConfig {
|
||||
'intercept-url'(pattern: '/**', 'requires-channel': 'https')
|
||||
}
|
||||
createAppContext();
|
||||
List filters = getFilters("/someurl");
|
||||
|
||||
expect:
|
||||
filters.size() == AUTO_CONFIG_FILTERS + 1
|
||||
filters[0] instanceof ChannelProcessingFilter
|
||||
}
|
||||
|
||||
def portMappingsAreParsedCorrectly() {
|
||||
httpAutoConfig {
|
||||
'port-mappings'() {
|
||||
'port-mapping'(http: '9080', https: '9443')
|
||||
}
|
||||
}
|
||||
createAppContext();
|
||||
|
||||
def pm = (appContext.getBeansOfType(PortMapperImpl).values() as List)[0];
|
||||
|
||||
expect:
|
||||
pm.getTranslatedPortMappings().size() == 1
|
||||
pm.lookupHttpPort(9443) == 9080
|
||||
pm.lookupHttpsPort(9080) == 9443
|
||||
}
|
||||
|
||||
def externalFiltersAreTreatedCorrectly() {
|
||||
httpAutoConfig {
|
||||
'custom-filter'(position: 'FIRST', ref: '${customFilterRef}')
|
||||
'custom-filter'(after: 'LOGOUT_FILTER', ref: 'userFilter')
|
||||
'custom-filter'(before: 'SECURITY_CONTEXT_FILTER', ref: 'userFilter1')
|
||||
}
|
||||
bean('phc', PropertyPlaceholderConfigurer)
|
||||
bean('userFilter', SecurityContextHolderAwareRequestFilter)
|
||||
bean('userFilter1', SecurityContextPersistenceFilter)
|
||||
|
||||
System.setProperty('customFilterRef', 'userFilter')
|
||||
createAppContext();
|
||||
|
||||
def filters = getFilters("/someurl");
|
||||
|
||||
expect:
|
||||
AUTO_CONFIG_FILTERS + 3 == filters.size();
|
||||
filters[0] instanceof SecurityContextHolderAwareRequestFilter
|
||||
filters[1] instanceof SecurityContextPersistenceFilter
|
||||
filters[4] instanceof SecurityContextHolderAwareRequestFilter
|
||||
filters[1] instanceof SecurityContextPersistenceFilter
|
||||
}
|
||||
|
||||
def twoFiltersWithSameOrderAreRejected() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'custom-filter'(position: 'LOGOUT_FILTER', ref: 'userFilter')
|
||||
}
|
||||
bean('userFilter', SecurityContextHolderAwareRequestFilter)
|
||||
createAppContext();
|
||||
|
||||
then:
|
||||
thrown(BeanDefinitionParsingException)
|
||||
}
|
||||
|
||||
def x509SupportAddsFilterAtExpectedPosition() {
|
||||
httpAutoConfig {
|
||||
x509()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def filters = getFilters("/someurl")
|
||||
|
||||
expect:
|
||||
getFilters("/someurl")[2] instanceof X509AuthenticationFilter
|
||||
}
|
||||
|
||||
def x509SubjectPrincipalRegexCanBeSetUsingPropertyPlaceholder() {
|
||||
httpAutoConfig {
|
||||
x509('subject-principal-regex':'${subject-principal-regex}')
|
||||
}
|
||||
bean('phc', PropertyPlaceholderConfigurer.class.name)
|
||||
System.setProperty("subject-principal-regex", "uid=(.*),");
|
||||
createAppContext()
|
||||
def filter = getFilter(X509AuthenticationFilter)
|
||||
|
||||
expect:
|
||||
filter.principalExtractor.subjectDnPattern.pattern() == "uid=(.*),"
|
||||
}
|
||||
|
||||
def invalidLogoutSuccessUrlIsDetected() {
|
||||
when:
|
||||
xml.http {
|
||||
'form-login'()
|
||||
'logout'('logout-success-url': 'noLeadingSlash')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
BeanCreationException e = thrown()
|
||||
}
|
||||
|
||||
def invalidLogoutUrlIsDetected() {
|
||||
when:
|
||||
xml.http {
|
||||
'logout'('logout-url': 'noLeadingSlash')
|
||||
'form-login'()
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
then:
|
||||
BeanCreationException e = thrown();
|
||||
}
|
||||
|
||||
def logoutSuccessHandlerIsSetCorrectly() {
|
||||
xml.http {
|
||||
'form-login'()
|
||||
'logout'('success-handler-ref': 'logoutHandler')
|
||||
}
|
||||
bean('logoutHandler', SimpleUrlLogoutSuccessHandler)
|
||||
createAppContext()
|
||||
|
||||
LogoutFilter filter = getFilter(LogoutFilter);
|
||||
|
||||
expect:
|
||||
FieldUtils.getFieldValue(filter, "logoutSuccessHandler") == appContext.getBean("logoutHandler")
|
||||
}
|
||||
|
||||
def externalRequestCacheIsConfiguredCorrectly() {
|
||||
httpAutoConfig {
|
||||
'request-cache'(ref: 'cache')
|
||||
}
|
||||
bean('cache', HttpSessionRequestCache.class.name)
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
appContext.getBean("cache") == getFilter(ExceptionTranslationFilter.class).requestCache
|
||||
}
|
||||
|
||||
def customEntryPointIsSupported() {
|
||||
xml.http('auto-config': 'true', 'entry-point-ref': 'entryPoint') {}
|
||||
bean('entryPoint', MockEntryPoint.class.name)
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilter(ExceptionTranslationFilter).getAuthenticationEntryPoint() instanceof MockEntryPoint
|
||||
}
|
||||
|
||||
def disablingSessionProtectionRemovesSessionManagementFilterIfNoInvalidSessionUrlSet() {
|
||||
httpAutoConfig {
|
||||
'session-management'('session-fixation-protection': 'none')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
!(getFilters("/someurl")[8] instanceof SessionManagementFilter)
|
||||
}
|
||||
|
||||
def disablingSessionProtectionRetainsSessionManagementFilterInvalidSessionUrlSet() {
|
||||
httpAutoConfig {
|
||||
'session-management'('session-fixation-protection': 'none', 'invalid-session-url': '/timeoutUrl')
|
||||
}
|
||||
createAppContext()
|
||||
def filter = getFilters("/someurl")[8]
|
||||
|
||||
expect:
|
||||
filter instanceof SessionManagementFilter
|
||||
filter.invalidSessionUrl == '/timeoutUrl'
|
||||
}
|
||||
|
||||
/**
|
||||
* See SEC-750. If the http security post processor causes beans to be instantiated too eagerly, they way miss
|
||||
* additional processing. In this method we have a UserDetailsService which is referenced from the namespace
|
||||
* and also has a post processor registered which will modify it.
|
||||
*/
|
||||
def httpElementDoesntInterfereWithBeanPostProcessing() {
|
||||
httpAutoConfig {}
|
||||
xml.'authentication-manager'() {
|
||||
'authentication-provider'('user-service-ref': 'myUserService')
|
||||
}
|
||||
bean('myUserService', PostProcessedMockUserDetailsService)
|
||||
bean('beanPostProcessor', MockUserServiceBeanPostProcessor)
|
||||
createAppContext("")
|
||||
|
||||
expect:
|
||||
appContext.getBean("myUserService").getPostProcessorWasHere() == "Hello from the post processor!"
|
||||
}
|
||||
|
||||
/* SEC-934 */
|
||||
def supportsTwoIdenticalInterceptUrls() {
|
||||
httpAutoConfig {
|
||||
interceptUrl ('/someUrl', 'ROLE_A')
|
||||
interceptUrl ('/someUrl', 'ROLE_B')
|
||||
}
|
||||
createAppContext()
|
||||
def fis = getFilter(FilterSecurityInterceptor)
|
||||
def fids = fis.securityMetadataSource
|
||||
Collection attrs = fids.getAttributes(createFilterinvocation("/someurl", null));
|
||||
|
||||
expect:
|
||||
attrs.size() == 1
|
||||
attrs.contains(new SecurityConfig("ROLE_B"))
|
||||
}
|
||||
|
||||
def supportsExternallyDefinedSecurityContextRepository() {
|
||||
xml.http('create-session': 'always', 'security-context-repository-ref': 'repo') {
|
||||
'http-basic'()
|
||||
}
|
||||
bean('repo', HttpSessionSecurityContextRepository)
|
||||
createAppContext()
|
||||
|
||||
def filter = getFilter(SecurityContextPersistenceFilter)
|
||||
|
||||
expect:
|
||||
filter.repo == appContext.getBean('repo')
|
||||
filter.forceEagerSessionCreation == true
|
||||
}
|
||||
|
||||
def expressionBasedAccessAllowsAndDeniesAccessAsExpected() {
|
||||
setup:
|
||||
xml.http('auto-config': 'true', 'use-expressions': 'true') {
|
||||
interceptUrl('/secure*', "hasAnyRole('ROLE_A','ROLE_C')")
|
||||
interceptUrl('/**', 'permitAll')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
def fis = getFilter(FilterSecurityInterceptor)
|
||||
def fids = fis.getSecurityMetadataSource()
|
||||
Collection attrs = fids.getAttributes(createFilterinvocation("/secure", null));
|
||||
assert 1 == attrs.size()
|
||||
|
||||
when: "Unprotected URL"
|
||||
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_A"));
|
||||
fis.invoke(createFilterinvocation("/permitallurl", null));
|
||||
then:
|
||||
notThrown(AccessDeniedException)
|
||||
|
||||
when: "Invoking secure Url as a valid user"
|
||||
fis.invoke(createFilterinvocation("/secure", null));
|
||||
then:
|
||||
notThrown(AccessDeniedException)
|
||||
|
||||
when: "User does not have the required role"
|
||||
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_B"));
|
||||
fis.invoke(createFilterinvocation("/secure", null));
|
||||
then:
|
||||
thrown(AccessDeniedException)
|
||||
}
|
||||
|
||||
def disablingUrlRewritingThroughTheNamespaceSetsCorrectPropertyOnContextRepo() {
|
||||
xml.http('auto-config': 'true', 'disable-url-rewriting': 'true')
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilter(SecurityContextPersistenceFilter).repo.disableUrlRewriting == true
|
||||
}
|
||||
|
||||
def userDetailsServiceInParentContextIsLocatedSuccessfully() {
|
||||
when:
|
||||
createAppContext()
|
||||
httpAutoConfig {
|
||||
'remember-me'
|
||||
}
|
||||
appContext = new InMemoryXmlApplicationContext(writer.toString(), appContext)
|
||||
|
||||
then:
|
||||
notThrown(BeansException)
|
||||
}
|
||||
|
||||
def httpConfigWithNoAuthProvidersWorksOk() {
|
||||
when: "Http config has no internal authentication providers"
|
||||
xml.http() {
|
||||
'form-login'()
|
||||
anonymous(enabled: 'false')
|
||||
}
|
||||
createAppContext()
|
||||
FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/j_spring_security_check");
|
||||
request.setServletPath("/j_spring_security_check");
|
||||
request.addParameter("j_username", "bob");
|
||||
request.addParameter("j_password", "bob");
|
||||
then: "App context creation and login request succeed"
|
||||
fcp.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
|
||||
}
|
||||
}
|
||||
|
||||
class MockEntryPoint extends LoginUrlAuthenticationEntryPoint {
|
||||
public MockEntryPoint() {
|
||||
super.setLoginFormUrl("/notused");
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
|
||||
import org.springframework.mock.web.MockFilterChain
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.security.access.ConfigAttribute
|
||||
import org.springframework.security.access.SecurityConfig
|
||||
import org.springframework.security.util.FieldUtils
|
||||
import org.springframework.security.web.PortMapperImpl
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
|
||||
class PlaceHolderAndELConfigTests extends AbstractHttpConfigTests {
|
||||
|
||||
void setup() {
|
||||
// Add a PropertyPlaceholderConfigurer to the context for all the tests
|
||||
xml.'b:bean'('class': PropertyPlaceholderConfigurer.class.name)
|
||||
}
|
||||
|
||||
def filtersEqualsNoneSupportsPlaceholderForPattern() {
|
||||
System.setProperty("pattern.nofilters", "/unprotected");
|
||||
|
||||
httpAutoConfig() {
|
||||
interceptUrlNoFilters('${pattern.nofilters}')
|
||||
interceptUrl('/**', 'ROLE_A')
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
List filters = getFilters("/unprotected");
|
||||
|
||||
expect:
|
||||
filters.size() == 0
|
||||
}
|
||||
|
||||
// SEC-1201
|
||||
def interceptUrlsAndFormLoginSupportPropertyPlaceholders() {
|
||||
System.setProperty("secure.Url", "/Secure");
|
||||
System.setProperty("secure.role", "ROLE_A");
|
||||
System.setProperty("login.page", "/loginPage");
|
||||
System.setProperty("default.target", "/defaultTarget");
|
||||
System.setProperty("auth.failure", "/authFailure");
|
||||
|
||||
xml.http {
|
||||
interceptUrl('${secure.Url}', '${secure.role}')
|
||||
interceptUrlNoFilters('${login.page}');
|
||||
'form-login'('login-page':'${login.page}', 'default-target-url': '${default.target}',
|
||||
'authentication-failure-url':'${auth.failure}');
|
||||
}
|
||||
createAppContext();
|
||||
|
||||
expect:
|
||||
propertyValuesMatchPlaceholders()
|
||||
getFilters("/loginPage").size() == 0
|
||||
}
|
||||
|
||||
// SEC-1309
|
||||
def interceptUrlsAndFormLoginSupportEL() {
|
||||
System.setProperty("secure.url", "/Secure");
|
||||
System.setProperty("secure.role", "ROLE_A");
|
||||
System.setProperty("login.page", "/loginPage");
|
||||
System.setProperty("default.target", "/defaultTarget");
|
||||
System.setProperty("auth.failure", "/authFailure");
|
||||
|
||||
xml.http {
|
||||
interceptUrl("#{systemProperties['secure.url']}", "#{systemProperties['secure.role']}")
|
||||
'form-login'('login-page':"#{systemProperties['login.page']}", 'default-target-url': "#{systemProperties['default.target']}",
|
||||
'authentication-failure-url':"#{systemProperties['auth.failure']}");
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
propertyValuesMatchPlaceholders()
|
||||
}
|
||||
|
||||
private void propertyValuesMatchPlaceholders() {
|
||||
// Check the security attribute
|
||||
def fis = getFilter(FilterSecurityInterceptor);
|
||||
def fids = fis.getSecurityMetadataSource();
|
||||
Collection attrs = fids.getAttributes(createFilterinvocation("/secure", null));
|
||||
assert attrs.size() == 1
|
||||
assert attrs.contains(new SecurityConfig("ROLE_A"))
|
||||
|
||||
// Check the form login properties are set
|
||||
def apf = getFilter(UsernamePasswordAuthenticationFilter)
|
||||
assert FieldUtils.getFieldValue(apf, "successHandler.defaultTargetUrl") == '/defaultTarget'
|
||||
assert "/authFailure" == FieldUtils.getFieldValue(apf, "failureHandler.defaultFailureUrl")
|
||||
|
||||
def etf = getFilter(ExceptionTranslationFilter)
|
||||
assert "/loginPage"== etf.authenticationEntryPoint.loginFormUrl
|
||||
}
|
||||
|
||||
def portMappingsWorkWithPlaceholdersAndEL() {
|
||||
System.setProperty("http", "9080");
|
||||
System.setProperty("https", "9443");
|
||||
|
||||
httpAutoConfig {
|
||||
'port-mappings'() {
|
||||
'port-mapping'(http: '#{systemProperties.http}', https: '${https}')
|
||||
}
|
||||
}
|
||||
createAppContext();
|
||||
|
||||
def pm = (appContext.getBeansOfType(PortMapperImpl).values() as List)[0];
|
||||
|
||||
expect:
|
||||
pm.getTranslatedPortMappings().size() == 1
|
||||
pm.lookupHttpPort(9443) == 9080
|
||||
pm.lookupHttpsPort(9080) == 9443
|
||||
}
|
||||
|
||||
def requiresChannelSupportsPlaceholder() {
|
||||
System.setProperty("secure.url", "/secure");
|
||||
System.setProperty("required.channel", "https");
|
||||
|
||||
httpAutoConfig {
|
||||
'intercept-url'(pattern: '${secure.url}', 'requires-channel': '${required.channel}')
|
||||
}
|
||||
createAppContext();
|
||||
List filters = getFilters("/secure");
|
||||
|
||||
expect:
|
||||
filters.size() == AUTO_CONFIG_FILTERS + 1
|
||||
filters[0] instanceof ChannelProcessingFilter
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setServletPath("/secure");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filters[0].doFilter(request, response, new MockFilterChain());
|
||||
response.getRedirectedUrl().startsWith("https")
|
||||
}
|
||||
|
||||
def accessDeniedPageWorksWithPlaceholders() {
|
||||
System.setProperty("accessDenied", "/go-away");
|
||||
xml.http('auto-config': 'true', 'access-denied-page': '${accessDenied}')
|
||||
createAppContext();
|
||||
|
||||
expect:
|
||||
FieldUtils.getFieldValue(getFilter(ExceptionTranslationFilter.class), "accessDeniedHandler.errorPage") == '/go-away'
|
||||
}
|
||||
|
||||
def accessDeniedHandlerPageWorksWithEL() {
|
||||
httpAutoConfig {
|
||||
'access-denied-handler'('error-page': "#{'/go' + '-away'}")
|
||||
}
|
||||
createAppContext()
|
||||
|
||||
expect:
|
||||
getFilter(ExceptionTranslationFilter).accessDeniedHandler.errorPage == '/go-away'
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML;
|
||||
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
|
||||
import org.springframework.security.TestDataSource;
|
||||
import org.springframework.security.authentication.ProviderManager
|
||||
import org.springframework.security.authentication.RememberMeAuthenticationProvider
|
||||
import org.springframework.security.config.BeanIds
|
||||
import org.springframework.security.core.userdetails.MockUserDetailsService;
|
||||
import org.springframework.security.util.FieldUtils
|
||||
import org.springframework.security.web.authentication.logout.LogoutFilter
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler
|
||||
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices
|
||||
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
|
||||
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
class RememberMeConfigTests extends AbstractHttpConfigTests {
|
||||
|
||||
def rememberMeServiceWorksWithTokenRepoRef() {
|
||||
httpAutoConfig () {
|
||||
'remember-me'('token-repository-ref': 'tokenRepo')
|
||||
}
|
||||
bean('tokenRepo', InMemoryTokenRepositoryImpl.class.name)
|
||||
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
expect:
|
||||
rememberMeServices() instanceof PersistentTokenBasedRememberMeServices
|
||||
FieldUtils.getFieldValue(rememberMeServices(), "useSecureCookie") == false
|
||||
}
|
||||
|
||||
def rememberMeServiceWorksWithDataSourceRef() {
|
||||
httpAutoConfig () {
|
||||
'remember-me'('data-source-ref': 'ds')
|
||||
}
|
||||
bean('ds', TestDataSource.class.name, ['tokendb'])
|
||||
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
expect:
|
||||
rememberMeServices() instanceof PersistentTokenBasedRememberMeServices
|
||||
}
|
||||
|
||||
def rememberMeServiceWorksWithExternalServicesImpl() {
|
||||
httpAutoConfig () {
|
||||
'remember-me'('key': "#{'our' + 'key'}", 'services-ref': 'rms')
|
||||
}
|
||||
bean('rms', TokenBasedRememberMeServices.class.name,
|
||||
['key':'ourKey', 'tokenValiditySeconds':'5000'], ['userDetailsService':'us'])
|
||||
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
List logoutHandlers = FieldUtils.getFieldValue(getFilter(LogoutFilter.class), "handlers");
|
||||
Map ams = appContext.getBeansOfType(ProviderManager.class);
|
||||
ams.remove(BeanIds.AUTHENTICATION_MANAGER);
|
||||
RememberMeAuthenticationProvider rmp = (ams.values() as List)[0].providers[1];
|
||||
|
||||
expect:
|
||||
5000 == FieldUtils.getFieldValue(rememberMeServices(), "tokenValiditySeconds")
|
||||
// SEC-909
|
||||
logoutHandlers.size() == 2
|
||||
logoutHandlers.get(1) == rememberMeServices()
|
||||
// SEC-1281
|
||||
rmp.key == "ourkey"
|
||||
}
|
||||
|
||||
def rememberMeTokenValidityIsParsedCorrectly() {
|
||||
httpAutoConfig () {
|
||||
'remember-me'('key': 'ourkey', 'token-validity-seconds':'10000')
|
||||
}
|
||||
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
expect:
|
||||
rememberMeServices().tokenValiditySeconds == 10000
|
||||
}
|
||||
|
||||
def 'Remember-me token validity allows negative value for non-persistent implementation'() {
|
||||
httpAutoConfig () {
|
||||
'remember-me'('key': 'ourkey', 'token-validity-seconds':'-1')
|
||||
}
|
||||
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
expect:
|
||||
rememberMeServices().tokenValiditySeconds == -1
|
||||
}
|
||||
|
||||
def rememberMeSecureCookieAttributeIsSetCorrectly() {
|
||||
httpAutoConfig () {
|
||||
'remember-me'('key': 'ourkey', 'use-secure-cookie':'true')
|
||||
}
|
||||
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
expect:
|
||||
FieldUtils.getFieldValue(rememberMeServices(), "useSecureCookie") == true
|
||||
}
|
||||
|
||||
def 'Negative token-validity is rejected with persistent implementation'() {
|
||||
when:
|
||||
httpAutoConfig () {
|
||||
'remember-me'('key': 'ourkey', 'token-validity-seconds':'-1', 'token-repository-ref': 'tokenRepo')
|
||||
}
|
||||
bean('tokenRepo', InMemoryTokenRepositoryImpl.class.name)
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
then:
|
||||
BeanDefinitionParsingException e = thrown()
|
||||
}
|
||||
|
||||
def 'Custom user service is supported'() {
|
||||
when:
|
||||
httpAutoConfig () {
|
||||
'remember-me'('key': 'ourkey', 'token-validity-seconds':'-1', 'user-service-ref': 'userService')
|
||||
}
|
||||
bean('userService', MockUserDetailsService.class.name)
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
then: "Parses OK"
|
||||
notThrown BeanDefinitionParsingException
|
||||
}
|
||||
|
||||
// SEC-742
|
||||
def rememberMeWorksWithoutBasicProcessingFilter() {
|
||||
when:
|
||||
xml.http () {
|
||||
'form-login'('login-page': '/login.jsp', 'default-target-url': '/messageList.html' )
|
||||
logout('logout-success-url': '/login.jsp')
|
||||
anonymous(username: 'guest', 'granted-authority': 'guest')
|
||||
'remember-me'()
|
||||
}
|
||||
createAppContext(AUTH_PROVIDER_XML)
|
||||
|
||||
then: "Parses OK"
|
||||
notThrown BeanDefinitionParsingException
|
||||
}
|
||||
|
||||
def rememberMeServices() {
|
||||
getFilter(RememberMeAuthenticationFilter.class).getRememberMeServices()
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
package org.springframework.security.config.http
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import org.springframework.security.web.FilterChainProxy
|
||||
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
|
||||
|
||||
import org.springframework.mock.web.MockFilterChain
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.config.BeanIds
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.core.session.SessionRegistryImpl
|
||||
import org.springframework.security.util.FieldUtils
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
import org.springframework.security.web.context.NullSecurityContextRepository
|
||||
import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter
|
||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
|
||||
import org.springframework.security.web.session.ConcurrentSessionFilter
|
||||
import org.springframework.security.web.session.SessionManagementFilter
|
||||
|
||||
|
||||
/**
|
||||
* Tests session-related functionality for the <http> namespace element and <session-management>
|
||||
*
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
||||
|
||||
def settingCreateSessionToAlwaysSetsFilterPropertiesCorrectly() {
|
||||
httpCreateSession('always') { }
|
||||
createAppContext();
|
||||
|
||||
def filter = getFilter(SecurityContextPersistenceFilter.class);
|
||||
|
||||
expect:
|
||||
filter.forceEagerSessionCreation == true
|
||||
filter.repo.allowSessionCreation == true
|
||||
filter.repo.disableUrlRewriting == false
|
||||
}
|
||||
|
||||
def settingCreateSessionToNeverSetsFilterPropertiesCorrectly() {
|
||||
httpCreateSession('never') { }
|
||||
createAppContext();
|
||||
|
||||
def filter = getFilter(SecurityContextPersistenceFilter.class);
|
||||
|
||||
expect:
|
||||
filter.forceEagerSessionCreation == false
|
||||
filter.repo.allowSessionCreation == false
|
||||
}
|
||||
|
||||
def settingCreateSessionToStatelessSetsFilterPropertiesCorrectly() {
|
||||
httpCreateSession('stateless') { }
|
||||
createAppContext();
|
||||
|
||||
def filter = getFilter(SecurityContextPersistenceFilter.class);
|
||||
|
||||
expect:
|
||||
filter.forceEagerSessionCreation == false
|
||||
filter.repo instanceof NullSecurityContextRepository
|
||||
getFilter(SessionManagementFilter.class) == null
|
||||
getFilter(RequestCacheAwareFilter.class) == null
|
||||
}
|
||||
|
||||
def settingCreateSessionToIfRequiredDoesntCreateASessionForPublicInvocation() {
|
||||
httpCreateSession('ifRequired') { }
|
||||
createAppContext();
|
||||
|
||||
def filter = getFilter(SecurityContextPersistenceFilter.class);
|
||||
|
||||
expect:
|
||||
filter.forceEagerSessionCreation == false
|
||||
filter.repo.allowSessionCreation == true
|
||||
}
|
||||
|
||||
def httpCreateSession(String create, Closure c) {
|
||||
xml.http(['auto-config': 'true', 'create-session': create], c)
|
||||
}
|
||||
|
||||
def concurrentSessionSupportAddsFilterAndExpectedBeans() {
|
||||
httpAutoConfig {
|
||||
'session-management'() {
|
||||
'concurrency-control'('session-registry-alias':'sr', 'expired-url': '/expired')
|
||||
}
|
||||
}
|
||||
createAppContext();
|
||||
List filters = getFilters("/someurl");
|
||||
|
||||
expect:
|
||||
filters.get(0) instanceof ConcurrentSessionFilter
|
||||
appContext.getBean("sr") != null
|
||||
getFilter(SessionManagementFilter.class) != null
|
||||
sessionRegistryIsValid();
|
||||
}
|
||||
|
||||
def externalSessionStrategyIsSupported() {
|
||||
when:
|
||||
httpAutoConfig {
|
||||
'session-management'('session-authentication-strategy-ref':'ss')
|
||||
}
|
||||
bean('ss', SessionFixationProtectionStrategy.class.name)
|
||||
createAppContext();
|
||||
|
||||
then:
|
||||
notThrown(Exception.class)
|
||||
}
|
||||
|
||||
def externalSessionRegistryBeanIsConfiguredCorrectly() {
|
||||
httpAutoConfig {
|
||||
'session-management'() {
|
||||
'concurrency-control'('session-registry-ref':'sr')
|
||||
}
|
||||
}
|
||||
bean('sr', SessionRegistryImpl.class.name)
|
||||
createAppContext();
|
||||
|
||||
expect:
|
||||
sessionRegistryIsValid();
|
||||
}
|
||||
|
||||
def sessionRegistryIsValid() {
|
||||
Object sessionRegistry = appContext.getBean("sr");
|
||||
Object sessionRegistryFromConcurrencyFilter = FieldUtils.getFieldValue(
|
||||
getFilter(ConcurrentSessionFilter.class), "sessionRegistry");
|
||||
Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(
|
||||
getFilter(UsernamePasswordAuthenticationFilter.class),"sessionStrategy.sessionRegistry");
|
||||
Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue(
|
||||
getFilter(SessionManagementFilter.class),"sessionStrategy.sessionRegistry");
|
||||
|
||||
assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter);
|
||||
assertSame(sessionRegistry, sessionRegistryFromMgmtFilter);
|
||||
// SEC-1143
|
||||
assertSame(sessionRegistry, sessionRegistryFromFormLoginFilter);
|
||||
true;
|
||||
}
|
||||
|
||||
def concurrentSessionMaxSessionsIsCorrectlyConfigured() {
|
||||
setup:
|
||||
httpAutoConfig {
|
||||
'session-management'('session-authentication-error-url':'/max-exceeded') {
|
||||
'concurrency-control'('max-sessions': '2', 'error-if-maximum-exceeded':'true')
|
||||
}
|
||||
}
|
||||
createAppContext();
|
||||
|
||||
def seshFilter = getFilter(SessionManagementFilter.class);
|
||||
def auth = new UsernamePasswordAuthenticationToken("bob", "pass");
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
||||
def response = new SaveContextOnUpdateOrErrorResponseWrapper(mockResponse, false) {
|
||||
protected void saveContext(SecurityContext context) {
|
||||
}
|
||||
};
|
||||
when: "First session is established"
|
||||
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||
then: "ok"
|
||||
mockResponse.redirectedUrl == null
|
||||
when: "Second session is established"
|
||||
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||
then: "ok"
|
||||
mockResponse.redirectedUrl == null
|
||||
when: "Third session is established"
|
||||
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||
then: "Rejected"
|
||||
mockResponse.redirectedUrl == "/max-exceeded";
|
||||
}
|
||||
|
||||
}
|
@ -128,11 +128,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||
checkAutoConfigFilters(filterList);
|
||||
}
|
||||
|
||||
@Test(expected=BeanDefinitionParsingException.class)
|
||||
public void duplicateElementCausesError() throws Exception {
|
||||
setContext("<http auto-config='true' /><http auto-config='true' />" + AUTH_PROVIDER_XML);
|
||||
}
|
||||
|
||||
private void checkAutoConfigFilters(List<Filter> filterList) throws Exception {
|
||||
Iterator<Filter> filters = filterList.iterator();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user