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.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

View File

@ -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();
}
SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
logoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
return logoutSuccessHandler;
return handler;
}
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

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.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 {
}
}