mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-08 11:32:47 +00:00
Default logout negotiation in Java Configuration
This commit adds content negotiation for log out. Fixes gh-3282
This commit is contained in:
parent
4093690322
commit
510cd59980
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user