mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-30 22:28:46 +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