Default logout negotiation in Java Configuration

This commit adds content negotiation for log out.

Fixes gh-3282
This commit is contained in:
Rob Winch 2016-04-19 13:57:51 -05:00
parent 4093690322
commit 510cd59980
3 changed files with 118 additions and 12 deletions

View File

@ -31,6 +31,8 @@ import org.springframework.security.web.authentication.DelegatingAuthenticationE
import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 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.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; 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 { public void init(B http) throws Exception {
registerDefaultAuthenticationEntryPoint(http); registerDefaults(http);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void registerDefaultAuthenticationEntryPoint(B http) { private void registerDefaults(B http) {
ExceptionHandlingConfigurer<B> exceptionHandling = http
.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptionHandling == null) {
return;
}
ContentNegotiationStrategy contentNegotiationStrategy = http ContentNegotiationStrategy contentNegotiationStrategy = http
.getSharedObject(ContentNegotiationStrategy.class); .getSharedObject(ContentNegotiationStrategy.class);
if (contentNegotiationStrategy == null) { if (contentNegotiationStrategy == null) {
@ -164,9 +161,29 @@ public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML,
MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML); MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML);
preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); 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( exceptionHandling.defaultAuthenticationEntryPointFor(
postProcess(authenticationEntryPoint), preferredMatcher); 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 @Override

View File

@ -16,6 +16,7 @@
package org.springframework.security.config.annotation.web.configurers; package org.springframework.security.config.annotation.web.configurers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpSession; 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.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication; 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.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.DelegatingLogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; 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 permitAll;
private boolean customLogoutSuccess; private boolean customLogoutSuccess;
private LinkedHashMap<RequestMatcher, LogoutSuccessHandler> defaultLogoutSuccessHandlerMappings =
new LinkedHashMap<RequestMatcher, LogoutSuccessHandler>();
/** /**
* Creates a new instance * Creates a new instance
* @see HttpSecurity#logout() * @see HttpSecurity#logout()
@ -205,6 +211,27 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends
return this; 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 * Grants access to the {@link #logoutSuccessUrl(String)} and the
* {@link #logoutUrl(String)} for every user. * {@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 * @return the {@link LogoutSuccessHandler} to use
*/ */
private LogoutSuccessHandler getLogoutSuccessHandler() { private LogoutSuccessHandler getLogoutSuccessHandler() {
if (logoutSuccessHandler != null) { LogoutSuccessHandler handler = this.logoutSuccessHandler;
return logoutSuccessHandler; if (handler == null) {
handler = createDefaultSuccessHandler();
} }
SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); return handler;
logoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl); }
return logoutSuccessHandler;
private LogoutSuccessHandler createDefaultSuccessHandler() {
SimpleUrlLogoutSuccessHandler urlLogoutHandler = new SimpleUrlLogoutSuccessHandler();
urlLogoutHandler.setDefaultTargetUrl(logoutSuccessUrl);
if(defaultLogoutSuccessHandlerMappings.isEmpty()) {
return urlLogoutHandler;
}
DelegatingLogoutSuccessHandler successHandler = new DelegatingLogoutSuccessHandler(defaultLogoutSuccessHandlerMappings);
successHandler.setDefaultLogoutSuccessHandler(urlLogoutHandler);
return successHandler;
} }
@Override @Override

View File

@ -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.config.annotation.web.configurers.LogoutConfigurerTests.RememberMeNoLogoutHandler;
import org.springframework.security.web.authentication.RememberMeServices import org.springframework.security.web.authentication.RememberMeServices
import org.springframework.security.web.authentication.logout.LogoutFilter 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 { 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"() { def "logout ObjectPostProcessor"() {
setup: setup:
AnyObjectPostProcessor opp = Mock() AnyObjectPostProcessor opp = Mock()
@ -145,4 +166,35 @@ class LogoutConfigurerTests extends BaseSpringSpec {
.rememberMeServices(REMEMBER_ME) .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 {
}
} }