فهرست منبع

Default logout negotiation in Java Configuration

This commit adds content negotiation for log out.

Fixes gh-3282
Rob Winch 9 سال پیش
والد
کامیت
510cd59980

+ 24 - 7
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurer.java

@@ -31,6 +31,8 @@ import org.springframework.security.web.authentication.DelegatingAuthenticationE
 import org.springframework.security.web.authentication.HttpStatusEntryPoint;
 import org.springframework.security.web.authentication.RememberMeServices;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
 import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@@ -143,16 +145,11 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
 	}
 
 	public void init(B http) throws Exception {
-		registerDefaultAuthenticationEntryPoint(http);
+		registerDefaults(http);
 	}
 
 	@SuppressWarnings("unchecked")
-	private void registerDefaultAuthenticationEntryPoint(B http) {
-		ExceptionHandlingConfigurer<B> exceptionHandling = http
-				.getConfigurer(ExceptionHandlingConfigurer.class);
-		if (exceptionHandling == null) {
-			return;
-		}
+	private void registerDefaults(B http) {
 		ContentNegotiationStrategy contentNegotiationStrategy = http
 				.getSharedObject(ContentNegotiationStrategy.class);
 		if (contentNegotiationStrategy == null) {
@@ -164,9 +161,29 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
 				MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML,
 				MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML);
 		preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
+
+		registerDefaultEntryPoint(http, preferredMatcher);
+		registerDefaultLogoutSuccessHandler(http, preferredMatcher);
+	}
+
+	private void registerDefaultEntryPoint(B http, RequestMatcher preferredMatcher) {
+		ExceptionHandlingConfigurer<B> exceptionHandling = http
+				.getConfigurer(ExceptionHandlingConfigurer.class);
+		if (exceptionHandling == null) {
+			return;
+		}
 		exceptionHandling.defaultAuthenticationEntryPointFor(
 				postProcess(authenticationEntryPoint), preferredMatcher);
+	}
 
+	private void registerDefaultLogoutSuccessHandler(B http, RequestMatcher preferredMatcher) {
+		LogoutConfigurer<B> logout = http
+				.getConfigurer(LogoutConfigurer.class);
+		if (logout == null) {
+			return;
+		}
+		LogoutConfigurer<B> handler = logout.defaultLogoutSuccessHandlerFor(
+				postProcess(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT)), preferredMatcher);
 	}
 
 	@Override

+ 42 - 5
config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java

@@ -16,6 +16,7 @@
 package org.springframework.security.config.annotation.web.configurers;
 
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 
 import javax.servlet.http.HttpSession;
@@ -24,7 +25,9 @@ import org.springframework.security.config.annotation.SecurityConfigurer;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
 import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
+import org.springframework.security.web.authentication.logout.DelegatingLogoutSuccessHandler;
 import org.springframework.security.web.authentication.logout.LogoutFilter;
 import org.springframework.security.web.authentication.logout.LogoutHandler;
 import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
@@ -71,6 +74,9 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends
 	private boolean permitAll;
 	private boolean customLogoutSuccess;
 
+	private LinkedHashMap<RequestMatcher, LogoutSuccessHandler> defaultLogoutSuccessHandlerMappings =
+			new LinkedHashMap<RequestMatcher, LogoutSuccessHandler>();
+
 	/**
 	 * Creates a new instance
 	 * @see HttpSecurity#logout()
@@ -205,6 +211,27 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends
 		return this;
 	}
 
+	/**
+	 * Sets a default {@link LogoutSuccessHandler} to be used which prefers being invoked
+	 * for the provided {@link RequestMatcher}. If no {@link LogoutSuccessHandler} is
+	 * specified a {@link SimpleUrlLogoutSuccessHandler} will be used.
+	 * If any default {@link LogoutSuccessHandler} instances are configured, then a
+	 * {@link DelegatingLogoutSuccessHandler} will be used that defaults to a
+	 * {@link SimpleUrlLogoutSuccessHandler}.
+	 *
+	 * @param handler the {@link LogoutSuccessHandler} to use
+	 * @param preferredMatcher the {@link RequestMatcher} for this default
+	 * {@link LogoutSuccessHandler}
+	 * @return the {@link LogoutConfigurer} for further customizations
+	 */
+	public LogoutConfigurer<H> defaultLogoutSuccessHandlerFor(
+			LogoutSuccessHandler handler, RequestMatcher preferredMatcher) {
+		Assert.notNull(handler, "handler cannot be null");
+		Assert.notNull(preferredMatcher, "preferredMatcher cannot be null");
+		this.defaultLogoutSuccessHandlerMappings.put(preferredMatcher, handler);
+		return this;
+	}
+
 	/**
 	 * Grants access to the {@link #logoutSuccessUrl(String)} and the
 	 * {@link #logoutUrl(String)} for every user.
@@ -224,12 +251,22 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends
 	 * @return the {@link LogoutSuccessHandler} to use
 	 */
 	private LogoutSuccessHandler getLogoutSuccessHandler() {
-		if (logoutSuccessHandler != null) {
-			return logoutSuccessHandler;
+		LogoutSuccessHandler handler = this.logoutSuccessHandler;
+		if (handler == null) {
+			handler = createDefaultSuccessHandler();
+		}
+		return handler;
+	}
+
+	private LogoutSuccessHandler createDefaultSuccessHandler() {
+		SimpleUrlLogoutSuccessHandler urlLogoutHandler = new SimpleUrlLogoutSuccessHandler();
+		urlLogoutHandler.setDefaultTargetUrl(logoutSuccessUrl);
+		if(defaultLogoutSuccessHandlerMappings.isEmpty()) {
+			return urlLogoutHandler;
 		}
-		SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
-		logoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
-		return logoutSuccessHandler;
+		DelegatingLogoutSuccessHandler successHandler = new DelegatingLogoutSuccessHandler(defaultLogoutSuccessHandlerMappings);
+		successHandler.setDefaultLogoutSuccessHandler(urlLogoutHandler);
+		return successHandler;
 	}
 
 	@Override

+ 52 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy

@@ -26,6 +26,9 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
 import org.springframework.security.config.annotation.web.configurers.LogoutConfigurerTests.RememberMeNoLogoutHandler;
 import org.springframework.security.web.authentication.RememberMeServices
 import org.springframework.security.web.authentication.logout.LogoutFilter
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
+import org.springframework.security.web.util.matcher.RequestMatcher
 
 /**
  *
@@ -33,6 +36,24 @@ import org.springframework.security.web.authentication.logout.LogoutFilter
  */
 class LogoutConfigurerTests extends BaseSpringSpec {
 
+	def defaultLogoutSuccessHandlerForNullLogoutHandler() {
+		setup:
+		LogoutConfigurer config = new LogoutConfigurer();
+		when:
+		config.defaultLogoutSuccessHandlerFor(null, Mock(RequestMatcher))
+		then:
+		thrown(IllegalArgumentException)
+	}
+
+	def defaultLogoutSuccessHandlerForNullMatcher() {
+		setup:
+		LogoutConfigurer config = new LogoutConfigurer();
+		when:
+		config.defaultLogoutSuccessHandlerFor(Mock(LogoutSuccessHandler), null)
+		then:
+		thrown(IllegalArgumentException)
+	}
+
 	def "logout ObjectPostProcessor"() {
 		setup:
 			AnyObjectPostProcessor opp = Mock()
@@ -145,4 +166,35 @@ class LogoutConfigurerTests extends BaseSpringSpec {
 					.rememberMeServices(REMEMBER_ME)
 		}
 	}
+
+	def "LogoutConfigurer content negotiation default redirects"() {
+		setup:
+			loadConfig(LogoutHandlerContentNegotiation)
+		when:
+			login()
+			request.method = 'POST'
+			request.servletPath = '/logout'
+			springSecurityFilterChain.doFilter(request,response,chain)
+		then:
+			response.status == 302
+			response.redirectedUrl == '/login?logout'
+	}
+
+	// gh-3282
+	def "LogoutConfigurer content negotiation json 201"() {
+		setup:
+			loadConfig(LogoutHandlerContentNegotiation)
+		when:
+			login()
+			request.method = 'POST'
+			request.servletPath = '/logout'
+			request.addHeader('Accept', 'application/json')
+			springSecurityFilterChain.doFilter(request,response,chain)
+		then:
+			response.status == 204
+	}
+
+	@EnableWebSecurity
+	static class LogoutHandlerContentNegotiation extends WebSecurityConfigurerAdapter {
+	}
 }