Logout is 204 for XMLHttpRequest

Fixes gh-3997
This commit is contained in:
Rob Winch 2016-07-31 22:09:14 -05:00
parent d2a37cb1d6
commit 519c15efb3
3 changed files with 31 additions and 5 deletions

View File

@ -38,6 +38,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher; import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.ContentNegotiationStrategy;
@ -78,6 +79,10 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
*/ */
public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
AbstractHttpConfigurer<HttpBasicConfigurer<B>, B> { AbstractHttpConfigurer<HttpBasicConfigurer<B>, B> {
private static final RequestHeaderRequestMatcher X_REQUESTED_WITH = new RequestHeaderRequestMatcher("X-Requested-With",
"XMLHttpRequest");
private static final String DEFAULT_REALM = "Realm"; private static final String DEFAULT_REALM = "Realm";
private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationEntryPoint authenticationEntryPoint;
@ -93,8 +98,7 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
realmName(DEFAULT_REALM); realmName(DEFAULT_REALM);
LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> entryPoints = new LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>(); LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> entryPoints = new LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>();
entryPoints.put(new RequestHeaderRequestMatcher("X-Requested-With", entryPoints.put(X_REQUESTED_WITH, new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
"XMLHttpRequest"), new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
DelegatingAuthenticationEntryPoint defaultEntryPoint = new DelegatingAuthenticationEntryPoint( DelegatingAuthenticationEntryPoint defaultEntryPoint = new DelegatingAuthenticationEntryPoint(
entryPoints); entryPoints);
@ -157,6 +161,7 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
if (contentNegotiationStrategy == null) { if (contentNegotiationStrategy == null) {
contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
} }
MediaTypeRequestMatcher restMatcher = new MediaTypeRequestMatcher( MediaTypeRequestMatcher restMatcher = new MediaTypeRequestMatcher(
contentNegotiationStrategy, MediaType.APPLICATION_ATOM_XML, contentNegotiationStrategy, MediaType.APPLICATION_ATOM_XML,
MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON,
@ -167,9 +172,11 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
RequestMatcher notHtmlMatcher = new NegatedRequestMatcher( RequestMatcher notHtmlMatcher = new NegatedRequestMatcher(
new MediaTypeRequestMatcher(contentNegotiationStrategy, new MediaTypeRequestMatcher(contentNegotiationStrategy,
MediaType.TEXT_HTML)); MediaType.TEXT_HTML));
RequestMatcher preferredMatcher = new AndRequestMatcher( RequestMatcher restNotHtmlMatcher = new AndRequestMatcher(
Arrays.<RequestMatcher>asList(notHtmlMatcher, restMatcher)); Arrays.<RequestMatcher>asList(notHtmlMatcher, restMatcher));
RequestMatcher preferredMatcher = new OrRequestMatcher(Arrays.asList(X_REQUESTED_WITH, restNotHtmlMatcher));
registerDefaultEntryPoint(http, preferredMatcher); registerDefaultEntryPoint(http, preferredMatcher);
registerDefaultLogoutSuccessHandler(http, preferredMatcher); registerDefaultLogoutSuccessHandler(http, preferredMatcher);
} }

View File

@ -92,7 +92,7 @@ class ExceptionHandlingConfigurerTests extends BaseSpringSpec {
DelegatingAuthenticationEntryPoint delegateEntryPoint = findFilter(ExceptionTranslationFilter).authenticationEntryPoint DelegatingAuthenticationEntryPoint delegateEntryPoint = findFilter(ExceptionTranslationFilter).authenticationEntryPoint
then: then:
def entryPoints = delegateEntryPoint.entryPoints.keySet() as List def entryPoints = delegateEntryPoint.entryPoints.keySet() as List
entryPoints[0].requestMatchers[1].contentNegotiationStrategy.class == HeaderContentNegotiationStrategy entryPoints[0].requestMatchers[1].requestMatchers[1].contentNegotiationStrategy.class == HeaderContentNegotiationStrategy
entryPoints[1].requestMatchers[1].contentNegotiationStrategy.class == HeaderContentNegotiationStrategy entryPoints[1].requestMatchers[1].contentNegotiationStrategy.class == HeaderContentNegotiationStrategy
} }
@ -138,7 +138,7 @@ class ExceptionHandlingConfigurerTests extends BaseSpringSpec {
then: then:
def entryPoints = delegateEntryPoint.entryPoints.keySet() as List def entryPoints = delegateEntryPoint.entryPoints.keySet() as List
entryPoints[0].requestMatchers[1].contentNegotiationStrategy == OverrideContentNegotiationStrategySharedObjectConfig.CNS entryPoints[0].requestMatchers[1].contentNegotiationStrategy == OverrideContentNegotiationStrategySharedObjectConfig.CNS
entryPoints[1].requestMatchers[1].contentNegotiationStrategy == OverrideContentNegotiationStrategySharedObjectConfig.CNS entryPoints[1].requestMatchers[1].requestMatchers[1].contentNegotiationStrategy == OverrideContentNegotiationStrategySharedObjectConfig.CNS
} }
def "Override ContentNegotiationStrategy with @Bean"() { def "Override ContentNegotiationStrategy with @Bean"() {

View File

@ -232,4 +232,23 @@ class LogoutConfigurerTests extends BaseSpringSpec {
@EnableWebSecurity @EnableWebSecurity
static class LogoutHandlerContentNegotiationForChrome extends WebSecurityConfigurerAdapter { static class LogoutHandlerContentNegotiationForChrome extends WebSecurityConfigurerAdapter {
} }
// gh-3997
def "LogoutConfigurer for XMLHttpRequest is 204"() {
setup:
loadConfig(LogoutXMLHttpRequestConfig)
when:
login()
request.method = 'POST'
request.servletPath = '/logout'
request.addHeader('Accept', 'text/html,application/json')
request.addHeader('X-Requested-With', 'XMLHttpRequest')
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 204
}
@EnableWebSecurity
static class LogoutXMLHttpRequestConfig extends WebSecurityConfigurerAdapter {
}
} }