From 519c15efb3941a47a90a3d6bca26ad9d281c5c24 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Sun, 31 Jul 2016 22:09:14 -0500 Subject: [PATCH] Logout is 204 for XMLHttpRequest Fixes gh-3997 --- .../web/configurers/HttpBasicConfigurer.java | 13 ++++++++++--- .../ExceptionHandlingConfigurerTests.groovy | 4 ++-- .../configurers/LogoutConfigurerTests.groovy | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) 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 2a1bedfe62..adf7353f38 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 @@ -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.MediaTypeRequestMatcher; 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.RequestMatcher; import org.springframework.web.accept.ContentNegotiationStrategy; @@ -78,6 +79,10 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy; */ public final class HttpBasicConfigurer> extends AbstractHttpConfigurer, B> { + + private static final RequestHeaderRequestMatcher X_REQUESTED_WITH = new RequestHeaderRequestMatcher("X-Requested-With", + "XMLHttpRequest"); + private static final String DEFAULT_REALM = "Realm"; private AuthenticationEntryPoint authenticationEntryPoint; @@ -93,8 +98,7 @@ public final class HttpBasicConfigurer> extends realmName(DEFAULT_REALM); LinkedHashMap entryPoints = new LinkedHashMap(); - entryPoints.put(new RequestHeaderRequestMatcher("X-Requested-With", - "XMLHttpRequest"), new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); + entryPoints.put(X_REQUESTED_WITH, new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); DelegatingAuthenticationEntryPoint defaultEntryPoint = new DelegatingAuthenticationEntryPoint( entryPoints); @@ -157,6 +161,7 @@ public final class HttpBasicConfigurer> extends if (contentNegotiationStrategy == null) { contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); } + MediaTypeRequestMatcher restMatcher = new MediaTypeRequestMatcher( contentNegotiationStrategy, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, @@ -167,9 +172,11 @@ public final class HttpBasicConfigurer> extends RequestMatcher notHtmlMatcher = new NegatedRequestMatcher( new MediaTypeRequestMatcher(contentNegotiationStrategy, MediaType.TEXT_HTML)); - RequestMatcher preferredMatcher = new AndRequestMatcher( + RequestMatcher restNotHtmlMatcher = new AndRequestMatcher( Arrays.asList(notHtmlMatcher, restMatcher)); + RequestMatcher preferredMatcher = new OrRequestMatcher(Arrays.asList(X_REQUESTED_WITH, restNotHtmlMatcher)); + registerDefaultEntryPoint(http, preferredMatcher); registerDefaultLogoutSuccessHandler(http, preferredMatcher); } 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 bb32fd8592..c0ee2ae2a3 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 @@ -92,7 +92,7 @@ class ExceptionHandlingConfigurerTests extends BaseSpringSpec { DelegatingAuthenticationEntryPoint delegateEntryPoint = findFilter(ExceptionTranslationFilter).authenticationEntryPoint then: 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 } @@ -138,7 +138,7 @@ class ExceptionHandlingConfigurerTests extends BaseSpringSpec { then: def entryPoints = delegateEntryPoint.entryPoints.keySet() as List 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"() { diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy index 9724cd4d82..3ca43f1040 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy @@ -232,4 +232,23 @@ class LogoutConfigurerTests extends BaseSpringSpec { @EnableWebSecurity 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 { + } }