SEC-2199: Support multiple AuthenticationEntryPoint defaults

This commit is contained in:
Rob Winch 2013-07-19 16:27:06 -05:00
parent 87c9a14bff
commit 90bd241ce2
9 changed files with 296 additions and 25 deletions

View File

@ -37,6 +37,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; 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} * Provides a convenient base class for creating a {@link WebSecurityConfigurer}
@ -51,6 +53,8 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer
private ApplicationContext context; private ApplicationContext context;
private ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() { private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
@Override @Override
public <T> T postProcess(T object) { public <T> T postProcess(T object) {
@ -145,6 +149,7 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer
authenticationBuilder.parentAuthenticationManager(authenticationManager); authenticationBuilder.parentAuthenticationManager(authenticationManager);
http = new HttpSecurity(objectPostProcessor,authenticationBuilder, parentAuthenticationBuilder.getSharedObjects()); http = new HttpSecurity(objectPostProcessor,authenticationBuilder, parentAuthenticationBuilder.getSharedObjects());
http.setSharedObject(UserDetailsService.class, userDetailsService()); http.setSharedObject(UserDetailsService.class, userDetailsService());
http.setSharedObject(ContentNegotiationStrategy.class, contentNegotiationStrategy);
if(!disableDefaults) { if(!disableDefaults) {
http http
.exceptionHandling().and() .exceptionHandling().and()
@ -304,6 +309,11 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer
this.context = context; this.context = context;
} }
@Autowired(required=false)
public void setContentNegotationStrategy(ContentNegotiationStrategy contentNegotiationStrategy) {
this.contentNegotiationStrategy = contentNegotiationStrategy;
}
@Autowired(required=false) @Autowired(required=false)
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor; this.objectPostProcessor = objectPostProcessor;

View File

@ -17,11 +17,11 @@ package org.springframework.security.config.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer; 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.PortMapper;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; 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.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; 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. * Base class for confuring {@link AbstractAuthenticationFilterConfigurer}. This is intended for internal use only.
@ -221,7 +225,21 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecu
if(permitAll) { if(permitAll) {
PermitAllSupport.permitAll(http, loginPage, loginProcessingUrl, failureUrl); PermitAllSupport.permitAll(http, loginPage, loginProcessingUrl, failureUrl);
} }
http.setSharedObject(AuthenticationEntryPoint.class, postProcess(authenticationEntryPoint)); registerDefaultAuthenticationEntryPoint(http);
}
@SuppressWarnings("unchecked")
private void registerDefaultAuthenticationEntryPoint(B http) {
ExceptionHandlingConfigurer<B> 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 @Override

View File

@ -15,15 +15,19 @@
*/ */
package org.springframework.security.config.annotation.web.configurers; 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.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter; 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.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.RequestMatcher;
/** /**
* Adds exception handling for Spring Security related exceptions to an application. All properties have reasonable * 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: * The following shared objects are used:
* *
* <ul> * <ul>
* <li>{@link HttpSecurity#authenticationEntryPoint()} is used to process requests that require
* authentication</li>
* <li>If no explicit {@link RequestCache}, is provided a {@link RequestCache} shared object is used to replay * <li>If no explicit {@link RequestCache}, is provided a {@link RequestCache} shared object is used to replay
* the request after authentication is successful</li> * the request after authentication is successful</li>
* <li>{@link AuthenticationEntryPoint} - see {@link #authenticationEntryPoint(AuthenticationEntryPoint)} </li> * <li>{@link AuthenticationEntryPoint} - see {@link #authenticationEntryPoint(AuthenticationEntryPoint)} </li>
@ -63,6 +65,8 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
private AccessDeniedHandler accessDeniedHandler; private AccessDeniedHandler accessDeniedHandler;
private LinkedHashMap<RequestMatcher,AuthenticationEntryPoint> defaultEntryPointMappings = new LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>();
/** /**
* Creates a new instance * Creates a new instance
* @see HttpSecurity#exceptionHandling() * @see HttpSecurity#exceptionHandling()
@ -96,18 +100,51 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
} }
/** /**
* Sets the {@link AuthenticationEntryPoint} to be used. Defaults to the * Sets the {@link AuthenticationEntryPoint} to be used.
* {@link HttpSecurity#getSharedObject(Class)} value. If that is not
* provided defaults to {@link Http403ForbiddenEntryPoint}.
* *
* @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use * <p>
* @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.
* </p>
*
* <p>
* If that is not provided defaults to {@link Http403ForbiddenEntryPoint}.
* </p>
*
* @param authenticationEntryPoint
* the {@link AuthenticationEntryPoint} to use
* @return the {@link ExceptionHandlingConfigurer} for further
* customizations
*/ */
public ExceptionHandlingConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { public ExceptionHandlingConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint; this.authenticationEntryPoint = authenticationEntryPoint;
return this; 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<H> defaultAuthenticationEntryPointFor(AuthenticationEntryPoint entryPoint, RequestMatcher preferredMatcher) {
this.defaultEntryPointMappings.put(preferredMatcher, entryPoint);
return this;
}
/** /**
* Gets any explicitly configured {@link AuthenticationEntryPoint} * Gets any explicitly configured {@link AuthenticationEntryPoint}
* @return * @return
@ -118,7 +155,7 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
@Override @Override
public void configure(H http) throws Exception { public void configure(H http) throws Exception {
AuthenticationEntryPoint entryPoint = getEntryPoint(http); AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http)); ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http));
if(accessDeniedHandler != null) { if(accessDeniedHandler != null) {
exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler); exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler);
@ -126,25 +163,31 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
exceptionTranslationFilter = postProcess(exceptionTranslationFilter); exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
http.addFilter(exceptionTranslationFilter); http.addFilter(exceptionTranslationFilter);
} }
/** /**
* Gets the {@link AuthenticationEntryPoint} according to the rules specified by {@link #authenticationEntryPoint(AuthenticationEntryPoint)} * 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} * @param http the {@link HttpSecurity} used to look up shared {@link AuthenticationEntryPoint}
* @return the {@link AuthenticationEntryPoint} to use * @return the {@link AuthenticationEntryPoint} to use
*/ */
AuthenticationEntryPoint getEntryPoint(H http) { AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint; AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
if(entryPoint == null) { if(entryPoint == null) {
AuthenticationEntryPoint sharedEntryPoint = http.getSharedObject(AuthenticationEntryPoint.class); entryPoint = createDefaultEntryPoint(http);
if(sharedEntryPoint != null) {
entryPoint = sharedEntryPoint;
} else {
entryPoint = new Http403ForbiddenEntryPoint();
}
} }
return entryPoint; 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 * 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 * {@link #requestCache(org.springframework.security.web.savedrequest.RequestCache)}, then it is used. Otherwise, an

View File

@ -15,8 +15,11 @@
*/ */
package org.springframework.security.config.annotation.web.configurers; package org.springframework.security.config.annotation.web.configurers;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; 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.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 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 * Adds HTTP basic based authentication. All attributes have reasonable defaults
@ -118,8 +125,22 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
} }
public void init(B http) throws Exception { public void init(B http) throws Exception {
http registerDefaultAuthenticationEntryPoint(http);
.setSharedObject(AuthenticationEntryPoint.class, authenticationEntryPoint); }
@SuppressWarnings("unchecked")
private void registerDefaultAuthenticationEntryPoint(B http) {
ExceptionHandlingConfigurer<B> 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 @Override

View File

@ -75,7 +75,7 @@ public final class ServletApiConfigurer<H extends HttpSecurityBuilder<H>> extend
public void configure(H http) throws Exception { public void configure(H http) throws Exception {
securityContextRequestFilter.setAuthenticationManager(http.getAuthenticationManager()); securityContextRequestFilter.setAuthenticationManager(http.getAuthenticationManager());
ExceptionHandlingConfigurer<H> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class); ExceptionHandlingConfigurer<H> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class);
AuthenticationEntryPoint authenticationEntryPoint = exceptionConf == null ? null : exceptionConf.getEntryPoint(http); AuthenticationEntryPoint authenticationEntryPoint = exceptionConf == null ? null : exceptionConf.getAuthenticationEntryPoint(http);
securityContextRequestFilter.setAuthenticationEntryPoint(authenticationEntryPoint); securityContextRequestFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
LogoutConfigurer<H> logoutConf = http.getConfigurer(LogoutConfigurer.class); LogoutConfigurer<H> logoutConf = http.getConfigurer(LogoutConfigurer.class);
List<LogoutHandler> logoutHandlers = logoutConf == null ? null : logoutConf.getLogoutHandlers(); List<LogoutHandler> logoutHandlers = logoutConf == null ? null : logoutConf.getLogoutHandlers();

View File

@ -40,6 +40,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.AuthorityUtils import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.ldap.DefaultSpringSecurityContextSource import org.springframework.security.ldap.DefaultSpringSecurityContextSource
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
/** /**
* @author Rob Winch * @author Rob Winch
@ -99,4 +101,35 @@ class WebSecurityConfigurerAdapterTests extends BaseSpringSpec {
EVENTS.add(e) 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 {}
} }

View File

@ -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.configuration.BaseWebConfig;
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer; import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
import org.springframework.security.web.FilterChainProxy 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.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
@ -331,6 +332,8 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
http http
// must set builder manually due to groovy not selecting correct method // must set builder manually due to groovy not selecting correct method
.apply(defaultLoginConfig).and() .apply(defaultLoginConfig).and()
.exceptionHandling()
.and()
.formLogin() .formLogin()
.and() .and()
.build() .build()
@ -339,5 +342,6 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
1 * objectPostProcessor.postProcess(_ as DefaultLoginPageViewFilter) >> {DefaultLoginPageViewFilter o -> o} 1 * objectPostProcessor.postProcess(_ as DefaultLoginPageViewFilter) >> {DefaultLoginPageViewFilter o -> o}
1 * objectPostProcessor.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o} 1 * objectPostProcessor.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o}
1 * objectPostProcessor.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o} 1 * objectPostProcessor.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o}
1 * objectPostProcessor.postProcess(_ as ExceptionTranslationFilter) >> {ExceptionTranslationFilter o -> o}
} }
} }

View File

@ -15,11 +15,29 @@
*/ */
package org.springframework.security.config.annotation.web.configurers 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.AnyObjectPostProcessor
import org.springframework.security.config.annotation.BaseSpringSpec import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.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.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" then: "ExceptionTranslationFilter is registered with LifecycleManager"
1 * opp.postProcess(_ as ExceptionTranslationFilter) >> {ExceptionTranslationFilter o -> o} 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()
}
}
} }

View File

@ -202,13 +202,17 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:]) HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
when: when:
http http
.exceptionHandling()
.and()
.formLogin() .formLogin()
.and() .and()
.build() .build()
then: "UsernamePasswordAuthenticationFilter is registered with LifecycleManager" then: "UsernamePasswordAuthenticationFilter is registered with ObjectPostProcessor"
1 * opp.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o} 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} 1 * opp.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o}
and: "ExceptionTranslationFilter is registered with ObjectPostProcessor"
1 * opp.postProcess(_ as ExceptionTranslationFilter) >> {ExceptionTranslationFilter o -> o}
} }
} }