From 90bd241ce2c6e806c0b9c11d35a0c828d377bd5b Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Fri, 19 Jul 2013 16:27:06 -0500 Subject: [PATCH] SEC-2199: Support multiple AuthenticationEntryPoint defaults --- .../WebSecurityConfigurerAdapter.java | 10 ++ ...bstractAuthenticationFilterConfigurer.java | 22 ++- .../ExceptionHandlingConfigurer.java | 75 +++++++-- .../web/configurers/HttpBasicConfigurer.java | 25 ++- .../web/configurers/ServletApiConfigurer.java | 2 +- .../WebSecurityConfigurerAdapterTests.groovy | 33 ++++ .../DefaultLoginPageConfigurerTests.groovy | 4 + .../ExceptionHandlingConfigurerTests.groovy | 142 +++++++++++++++++- .../FormLoginConfigurerTests.groovy | 8 +- 9 files changed, 296 insertions(+), 25 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java index 8d8bee0d42..3180b9c4f1 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java @@ -37,6 +37,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; /** * Provides a convenient base class for creating a {@link WebSecurityConfigurer} @@ -51,6 +53,8 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer private ApplicationContext context; + private ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); + private ObjectPostProcessor objectPostProcessor = new ObjectPostProcessor() { @Override public T postProcess(T object) { @@ -145,6 +149,7 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer authenticationBuilder.parentAuthenticationManager(authenticationManager); http = new HttpSecurity(objectPostProcessor,authenticationBuilder, parentAuthenticationBuilder.getSharedObjects()); http.setSharedObject(UserDetailsService.class, userDetailsService()); + http.setSharedObject(ContentNegotiationStrategy.class, contentNegotiationStrategy); if(!disableDefaults) { http .exceptionHandling().and() @@ -304,6 +309,11 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer this.context = context; } + @Autowired(required=false) + public void setContentNegotationStrategy(ContentNegotiationStrategy contentNegotiationStrategy) { + this.contentNegotiationStrategy = contentNegotiationStrategy; + } + @Autowired(required=false) public void setObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { this.objectPostProcessor = objectPostProcessor; diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java index 92d6881cf1..ade9c4e3b3 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java @@ -17,11 +17,11 @@ package org.springframework.security.config.annotation.web.configurers; import javax.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer; -import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.PortMapper; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @@ -33,6 +33,10 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.util.MediaTypeRequestMatcher; +import org.springframework.security.web.util.RequestMatcher; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; /** * Base class for confuring {@link AbstractAuthenticationFilterConfigurer}. This is intended for internal use only. @@ -221,7 +225,21 @@ public abstract class AbstractAuthenticationFilterConfigurer exceptionHandling = http.getConfigurer(ExceptionHandlingConfigurer.class); + if(exceptionHandling == null) { + return; + } + ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class); + if(contentNegotiationStrategy == null) { + contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); + } + RequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy, MediaType.APPLICATION_XHTML_XML, new MediaType("image","*"), MediaType.TEXT_HTML, MediaType.TEXT_PLAIN); + exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher); } @Override diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java index 4bcab960d5..0bc492d876 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java @@ -15,15 +15,19 @@ */ package org.springframework.security.config.annotation.web.configurers; +import java.util.LinkedHashMap; + import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.util.RequestMatcher; /** * Adds exception handling for Spring Security related exceptions to an application. All properties have reasonable @@ -47,8 +51,6 @@ import org.springframework.security.web.savedrequest.RequestCache; * The following shared objects are used: * *
    - *
  • {@link HttpSecurity#authenticationEntryPoint()} is used to process requests that require - * authentication
  • *
  • If no explicit {@link RequestCache}, is provided a {@link RequestCache} shared object is used to replay * the request after authentication is successful
  • *
  • {@link AuthenticationEntryPoint} - see {@link #authenticationEntryPoint(AuthenticationEntryPoint)}
  • @@ -63,6 +65,8 @@ public final class ExceptionHandlingConfigurer> private AccessDeniedHandler accessDeniedHandler; + private LinkedHashMap defaultEntryPointMappings = new LinkedHashMap(); + /** * Creates a new instance * @see HttpSecurity#exceptionHandling() @@ -96,18 +100,51 @@ public final class ExceptionHandlingConfigurer> } /** - * Sets the {@link AuthenticationEntryPoint} to be used. Defaults to the - * {@link HttpSecurity#getSharedObject(Class)} value. If that is not - * provided defaults to {@link Http403ForbiddenEntryPoint}. + * Sets the {@link AuthenticationEntryPoint} to be used. * - * @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use - * @return the {@link ExceptionHandlingConfigurer} for further customizations + *

    + * If no {@link #authenticationEntryPoint(AuthenticationEntryPoint)} is + * specified, then + * {@link #defaultAuthenticationEntryPointFor(AuthenticationEntryPoint, RequestMatcher)} + * will be used. The first {@link AuthenticationEntryPoint} will be used as + * the default is no matches were found. + *

    + * + *

    + * If that is not provided defaults to {@link Http403ForbiddenEntryPoint}. + *

    + * + * @param authenticationEntryPoint + * the {@link AuthenticationEntryPoint} to use + * @return the {@link ExceptionHandlingConfigurer} for further + * customizations */ public ExceptionHandlingConfigurer authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { this.authenticationEntryPoint = authenticationEntryPoint; return this; } + /** + * Sets a default {@link AuthenticationEntryPoint} to be used which prefers + * being invoked for the provided {@link RequestMatcher}. If only a single + * default {@link AuthenticationEntryPoint} is specified, it will be what is + * used for the default {@link AuthenticationEntryPoint}. If multiple + * default {@link AuthenticationEntryPoint} instances are configured, then a + * {@link DelegatingAuthenticationEntryPoint} will be used. + * + * @param entryPoint + * the {@link AuthenticationEntryPoint} to use + * @param preferredMatcher + * the {@link RequestMatcher} for this default + * {@link AuthenticationEntryPoint} + * @return the {@link ExceptionHandlingConfigurer} for further + * customizations + */ + public ExceptionHandlingConfigurer defaultAuthenticationEntryPointFor(AuthenticationEntryPoint entryPoint, RequestMatcher preferredMatcher) { + this.defaultEntryPointMappings.put(preferredMatcher, entryPoint); + return this; + } + /** * Gets any explicitly configured {@link AuthenticationEntryPoint} * @return @@ -118,7 +155,7 @@ public final class ExceptionHandlingConfigurer> @Override public void configure(H http) throws Exception { - AuthenticationEntryPoint entryPoint = getEntryPoint(http); + AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http); ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http)); if(accessDeniedHandler != null) { exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler); @@ -126,25 +163,31 @@ public final class ExceptionHandlingConfigurer> exceptionTranslationFilter = postProcess(exceptionTranslationFilter); http.addFilter(exceptionTranslationFilter); } - /** * Gets the {@link AuthenticationEntryPoint} according to the rules specified by {@link #authenticationEntryPoint(AuthenticationEntryPoint)} * @param http the {@link HttpSecurity} used to look up shared {@link AuthenticationEntryPoint} * @return the {@link AuthenticationEntryPoint} to use */ - AuthenticationEntryPoint getEntryPoint(H http) { + AuthenticationEntryPoint getAuthenticationEntryPoint(H http) { AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint; if(entryPoint == null) { - AuthenticationEntryPoint sharedEntryPoint = http.getSharedObject(AuthenticationEntryPoint.class); - if(sharedEntryPoint != null) { - entryPoint = sharedEntryPoint; - } else { - entryPoint = new Http403ForbiddenEntryPoint(); - } + entryPoint = createDefaultEntryPoint(http); } return entryPoint; } + private AuthenticationEntryPoint createDefaultEntryPoint(H http) { + if(defaultEntryPointMappings.isEmpty()) { + return new Http403ForbiddenEntryPoint(); + } + if(defaultEntryPointMappings.size() == 1) { + return defaultEntryPointMappings.values().iterator().next(); + } + DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(defaultEntryPointMappings); + entryPoint.setDefaultEntryPoint(defaultEntryPointMappings.values().iterator().next()); + return entryPoint; + } + /** * Gets the {@link RequestCache} to use. If one is defined using * {@link #requestCache(org.springframework.security.web.savedrequest.RequestCache)}, then it is used. Otherwise, an diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java index 654dd74e54..0b44872fee 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java @@ -15,8 +15,11 @@ */ package org.springframework.security.config.annotation.web.configurers; +import java.util.Collections; + import javax.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; @@ -25,6 +28,10 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.util.MediaTypeRequestMatcher; +import org.springframework.security.web.util.RequestMatcher; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; /** * Adds HTTP basic based authentication. All attributes have reasonable defaults @@ -118,8 +125,22 @@ public final class HttpBasicConfigurer> extends } public void init(B http) throws Exception { - http - .setSharedObject(AuthenticationEntryPoint.class, authenticationEntryPoint); + registerDefaultAuthenticationEntryPoint(http); + } + + @SuppressWarnings("unchecked") + private void registerDefaultAuthenticationEntryPoint(B http) { + ExceptionHandlingConfigurer exceptionHandling = http.getConfigurer(ExceptionHandlingConfigurer.class); + if(exceptionHandling == null) { + return; + } + ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class); + if(contentNegotiationStrategy == null) { + contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); + } + MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML); + preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); + exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher); } @Override diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java index baa5499aad..2971fbd951 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java @@ -75,7 +75,7 @@ public final class ServletApiConfigurer> extend public void configure(H http) throws Exception { securityContextRequestFilter.setAuthenticationManager(http.getAuthenticationManager()); ExceptionHandlingConfigurer exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class); - AuthenticationEntryPoint authenticationEntryPoint = exceptionConf == null ? null : exceptionConf.getEntryPoint(http); + AuthenticationEntryPoint authenticationEntryPoint = exceptionConf == null ? null : exceptionConf.getAuthenticationEntryPoint(http); securityContextRequestFilter.setAuthenticationEntryPoint(authenticationEntryPoint); LogoutConfigurer logoutConf = http.getConfigurer(LogoutConfigurer.class); List logoutHandlers = logoutConf == null ? null : logoutConf.getLogoutHandlers(); diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy index 5067affb6d..ff0372bac9 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy @@ -40,6 +40,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur import org.springframework.security.core.Authentication import org.springframework.security.core.authority.AuthorityUtils import org.springframework.security.ldap.DefaultSpringSecurityContextSource +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; /** * @author Rob Winch @@ -99,4 +101,35 @@ class WebSecurityConfigurerAdapterTests extends BaseSpringSpec { EVENTS.add(e) } } + + def "Override ContentNegotiationStrategy with @Bean"() { + setup: + OverrideContentNegotiationStrategySharedObjectConfig.CNS = Mock(ContentNegotiationStrategy) + when: + loadConfig(OverrideContentNegotiationStrategySharedObjectConfig) + then: + context.getBean(OverrideContentNegotiationStrategySharedObjectConfig).http.getSharedObject(ContentNegotiationStrategy) == OverrideContentNegotiationStrategySharedObjectConfig.CNS + } + + @EnableWebSecurity + @Configuration + static class OverrideContentNegotiationStrategySharedObjectConfig extends WebSecurityConfigurerAdapter { + static ContentNegotiationStrategy CNS + + @Bean + public ContentNegotiationStrategy cns() { + return CNS + } + } + + def "ContentNegotiationStrategy shareObject defaults to Header with no @Bean"() { + when: + loadConfig(ContentNegotiationStrategyDefaultSharedObjectConfig) + then: + context.getBean(ContentNegotiationStrategyDefaultSharedObjectConfig).http.getSharedObject(ContentNegotiationStrategy).class == HeaderContentNegotiationStrategy + } + + @EnableWebSecurity + @Configuration + static class ContentNegotiationStrategyDefaultSharedObjectConfig extends WebSecurityConfigurerAdapter {} } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy index 28b3a2b15c..2c56a56f6f 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy @@ -29,6 +29,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.BaseWebConfig; import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer; import org.springframework.security.web.FilterChainProxy +import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler @@ -331,6 +332,8 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { http // must set builder manually due to groovy not selecting correct method .apply(defaultLoginConfig).and() + .exceptionHandling() + .and() .formLogin() .and() .build() @@ -339,5 +342,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec { 1 * objectPostProcessor.postProcess(_ as DefaultLoginPageViewFilter) >> {DefaultLoginPageViewFilter o -> o} 1 * objectPostProcessor.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o} 1 * objectPostProcessor.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o} + 1 * objectPostProcessor.postProcess(_ as ExceptionTranslationFilter) >> {ExceptionTranslationFilter o -> o} } } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy index e7f2f39464..24f76b5d00 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.groovy @@ -15,11 +15,29 @@ */ package org.springframework.security.config.annotation.web.configurers +import javax.servlet.http.HttpServletResponse + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration +import org.springframework.http.MediaType +import org.springframework.mock.web.MockFilterChain +import org.springframework.mock.web.MockHttpServletRequest +import org.springframework.mock.web.MockHttpServletResponse import org.springframework.security.config.annotation.AnyObjectPostProcessor import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter +import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.ExceptionTranslationFilter +import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; + +import spock.lang.Unroll /** * @@ -40,4 +58,124 @@ class ExceptionHandlingConfigurerTests extends BaseSpringSpec { then: "ExceptionTranslationFilter is registered with LifecycleManager" 1 * opp.postProcess(_ as ExceptionTranslationFilter) >> {ExceptionTranslationFilter o -> o} } + + @Unroll + def "SEC-2199: defaultEntryPoint for httpBasic and formLogin"(String acceptHeader, int httpStatus) { + setup: + loadConfig(HttpBasicAndFormLoginEntryPointsConfig) + when: + request.addHeader("Accept", acceptHeader) + springSecurityFilterChain.doFilter(request,response,chain) + then: + response.status == httpStatus + where: + acceptHeader | httpStatus + MediaType.ALL_VALUE | HttpServletResponse.SC_MOVED_TEMPORARILY + MediaType.APPLICATION_XHTML_XML_VALUE | HttpServletResponse.SC_MOVED_TEMPORARILY + MediaType.IMAGE_GIF_VALUE | HttpServletResponse.SC_MOVED_TEMPORARILY + MediaType.IMAGE_JPEG_VALUE | HttpServletResponse.SC_MOVED_TEMPORARILY + MediaType.IMAGE_PNG_VALUE | HttpServletResponse.SC_MOVED_TEMPORARILY + MediaType.TEXT_HTML_VALUE | HttpServletResponse.SC_MOVED_TEMPORARILY + MediaType.TEXT_PLAIN_VALUE | HttpServletResponse.SC_MOVED_TEMPORARILY + MediaType.APPLICATION_ATOM_XML_VALUE | HttpServletResponse.SC_UNAUTHORIZED + MediaType.APPLICATION_FORM_URLENCODED_VALUE | HttpServletResponse.SC_UNAUTHORIZED + MediaType.APPLICATION_JSON_VALUE | HttpServletResponse.SC_UNAUTHORIZED + MediaType.APPLICATION_OCTET_STREAM_VALUE | HttpServletResponse.SC_UNAUTHORIZED + MediaType.APPLICATION_XML_VALUE | HttpServletResponse.SC_UNAUTHORIZED + MediaType.MULTIPART_FORM_DATA_VALUE | HttpServletResponse.SC_UNAUTHORIZED + MediaType.TEXT_XML_VALUE | HttpServletResponse.SC_UNAUTHORIZED + } + + def "ContentNegotiationStrategy defaults to HeaderContentNegotiationStrategy"() { + when: + loadConfig(HttpBasicAndFormLoginEntryPointsConfig) + DelegatingAuthenticationEntryPoint delegateEntryPoint = findFilter(ExceptionTranslationFilter).authenticationEntryPoint + then: + delegateEntryPoint.entryPoints.keySet().collect {it.contentNegotiationStrategy.class} == [HeaderContentNegotiationStrategy,HeaderContentNegotiationStrategy] + } + + @EnableWebSecurity + @Configuration + static class HttpBasicAndFormLoginEntryPointsConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void registerAuthentication(AuthenticationManagerBuilder auth) + throws Exception { + auth + .inMemoryAuthentication() + .withUser("user").password("password").roles("USER") + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeUrls() + .anyRequest().authenticated() + .and() + .httpBasic() + .and() + .formLogin() + } + } + + def "ContentNegotiationStrategy overrides with @Bean"() { + setup: + OverrideContentNegotiationStrategySharedObjectConfig.CNS = Mock(ContentNegotiationStrategy) + when: + loadConfig(OverrideContentNegotiationStrategySharedObjectConfig) + DelegatingAuthenticationEntryPoint delegateEntryPoint = findFilter(ExceptionTranslationFilter).authenticationEntryPoint + then: + delegateEntryPoint.entryPoints.keySet().collect {it.contentNegotiationStrategy} == [OverrideContentNegotiationStrategySharedObjectConfig.CNS,OverrideContentNegotiationStrategySharedObjectConfig.CNS] + } + + def "Override ContentNegotiationStrategy with @Bean"() { + setup: + OverrideContentNegotiationStrategySharedObjectConfig.CNS = Mock(ContentNegotiationStrategy) + when: + loadConfig(OverrideContentNegotiationStrategySharedObjectConfig) + then: + context.getBean(OverrideContentNegotiationStrategySharedObjectConfig).http.getSharedObject(ContentNegotiationStrategy) == OverrideContentNegotiationStrategySharedObjectConfig.CNS + } + + @EnableWebSecurity + @Configuration + static class OverrideContentNegotiationStrategySharedObjectConfig extends WebSecurityConfigurerAdapter { + static ContentNegotiationStrategy CNS + + @Bean + public ContentNegotiationStrategy cns() { + return CNS + } + } + + def "delegatingAuthenticationEntryPoint.defaultEntryPoint is LoginUrlAuthenticationEntryPoint when using DefaultHttpConf"() { + when: + loadConfig(DefaultHttpConf) + then: + findFilter(ExceptionTranslationFilter).authenticationEntryPoint.defaultEntryPoint.class == LoginUrlAuthenticationEntryPoint + } + + @EnableWebSecurity + @Configuration + static class DefaultHttpConf extends WebSecurityConfigurerAdapter { + } + + def "delegatingAuthenticationEntryPoint.defaultEntryPoint is BasicAuthenticationEntryPoint when httpBasic before formLogin"() { + when: + loadConfig(BasicAuthenticationEntryPointBeforeFormLoginConf) + then: + findFilter(ExceptionTranslationFilter).authenticationEntryPoint.defaultEntryPoint.class == BasicAuthenticationEntryPoint + } + + @EnableWebSecurity + @Configuration + static class BasicAuthenticationEntryPointBeforeFormLoginConf extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .httpBasic() + .and() + .formLogin() + } + } } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy index c488aec8b9..474b0345af 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy @@ -202,13 +202,17 @@ class FormLoginConfigurerTests extends BaseSpringSpec { HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:]) when: http + .exceptionHandling() + .and() .formLogin() .and() .build() - then: "UsernamePasswordAuthenticationFilter is registered with LifecycleManager" + then: "UsernamePasswordAuthenticationFilter is registered with ObjectPostProcessor" 1 * opp.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o} - and: "LoginUrlAuthenticationEntryPoint is registered with LifecycleManager" + and: "LoginUrlAuthenticationEntryPoint is registered with ObjectPostProcessor" 1 * opp.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o} + and: "ExceptionTranslationFilter is registered with ObjectPostProcessor" + 1 * opp.postProcess(_ as ExceptionTranslationFilter) >> {ExceptionTranslationFilter o -> o} } }