/* * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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; 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.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; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; /** * Adds logout support. Other {@link SecurityConfigurer} instances may invoke * {@link #addLogoutHandler(LogoutHandler)} in the {@link #init(HttpSecurityBuilder)} phase. * * <h2>Security Filters</h2> * * The following Filters are populated * * <ul> * <li> * {@link LogoutFilter}</li> * </ul> * * <h2>Shared Objects Created</h2> * * No shared Objects are created * * <h2>Shared Objects Used</h2> * * No shared objects are used. * * @author Rob Winch * @since 3.2 * @see RememberMeConfigurer */ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<LogoutConfigurer<H>, H> { private List<LogoutHandler> logoutHandlers = new ArrayList<LogoutHandler>(); private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler(); private String logoutSuccessUrl = "/login?logout"; private LogoutSuccessHandler logoutSuccessHandler; private String logoutUrl = "/logout"; private RequestMatcher logoutRequestMatcher; private boolean permitAll; private boolean customLogoutSuccess; private LinkedHashMap<RequestMatcher, LogoutSuccessHandler> defaultLogoutSuccessHandlerMappings = new LinkedHashMap<RequestMatcher, LogoutSuccessHandler>(); /** * Creates a new instance * @see HttpSecurity#logout() */ public LogoutConfigurer() { } /** * Adds a {@link LogoutHandler}. The {@link SecurityContextLogoutHandler} is added as * the last {@link LogoutHandler} by default. * * @param logoutHandler the {@link LogoutHandler} to add * @return the {@link LogoutConfigurer} for further customization */ public LogoutConfigurer<H> addLogoutHandler(LogoutHandler logoutHandler) { Assert.notNull(logoutHandler, "logoutHandler cannot be null"); this.logoutHandlers.add(logoutHandler); return this; } /** * Specifies if {@link SecurityContextLogoutHandler} should clear the {@link Authentication} at the time of logout. * @param clearAuthentication true {@link SecurityContextLogoutHandler} should clear the {@link Authentication} (default), or false otherwise. * @return the {@link LogoutConfigurer} for further customization */ public LogoutConfigurer<H> clearAuthentication(boolean clearAuthentication) { contextLogoutHandler.setClearAuthentication(clearAuthentication); return this; } /** * Configures {@link SecurityContextLogoutHandler} to invalidate the * {@link HttpSession} at the time of logout. * @param invalidateHttpSession true if the {@link HttpSession} should be invalidated * (default), or false otherwise. * @return the {@link LogoutConfigurer} for further customization */ public LogoutConfigurer<H> invalidateHttpSession(boolean invalidateHttpSession) { contextLogoutHandler.setInvalidateHttpSession(invalidateHttpSession); return this; } /** * The URL that triggers log out to occur (default is "/logout"). If CSRF protection * is enabled (default), then the request must also be a POST. This means that by * default POST "/logout" is required to trigger a log out. If CSRF protection is * disabled, then any HTTP method is allowed. * * <p> * It is considered best practice to use an HTTP POST on any action that changes state * (i.e. log out) to protect against <a * href="http://en.wikipedia.org/wiki/Cross-site_request_forgery">CSRF attacks</a>. If * you really want to use an HTTP GET, you can use * <code>logoutRequestMatcher(new AntPathRequestMatcher(logoutUrl, "GET"));</code> * </p> * * @see #logoutRequestMatcher(RequestMatcher) * @see HttpSecurity#csrf() * * @param logoutUrl the URL that will invoke logout. * @return the {@link LogoutConfigurer} for further customization */ public LogoutConfigurer<H> logoutUrl(String logoutUrl) { this.logoutRequestMatcher = null; this.logoutUrl = logoutUrl; return this; } /** * The RequestMatcher that triggers log out to occur. In most circumstances users will * use {@link #logoutUrl(String)} which helps enforce good practices. * * @see #logoutUrl(String) * * @param logoutRequestMatcher the RequestMatcher used to determine if logout should * occur. * @return the {@link LogoutConfigurer} for further customization */ public LogoutConfigurer<H> logoutRequestMatcher(RequestMatcher logoutRequestMatcher) { this.logoutRequestMatcher = logoutRequestMatcher; return this; } /** * The URL to redirect to after logout has occurred. The default is "/login?logout". * This is a shortcut for invoking {@link #logoutSuccessHandler(LogoutSuccessHandler)} * with a {@link SimpleUrlLogoutSuccessHandler}. * * @param logoutSuccessUrl the URL to redirect to after logout occurred * @return the {@link LogoutConfigurer} for further customization */ public LogoutConfigurer<H> logoutSuccessUrl(String logoutSuccessUrl) { this.customLogoutSuccess = true; this.logoutSuccessUrl = logoutSuccessUrl; return this; } /** * A shortcut for {@link #permitAll(boolean)} with <code>true</code> as an argument. * @return the {@link LogoutConfigurer} for further customizations */ public LogoutConfigurer<H> permitAll() { return permitAll(true); } /** * Allows specifying the names of cookies to be removed on logout success. This is a * shortcut to easily invoke {@link #addLogoutHandler(LogoutHandler)} with a * {@link CookieClearingLogoutHandler}. * * @param cookieNamesToClear the names of cookies to be removed on logout success. * @return the {@link LogoutConfigurer} for further customization */ public LogoutConfigurer<H> deleteCookies(String... cookieNamesToClear) { return addLogoutHandler(new CookieClearingLogoutHandler(cookieNamesToClear)); } /** * Sets the {@link LogoutSuccessHandler} to use. If this is specified, * {@link #logoutSuccessUrl(String)} is ignored. * * @param logoutSuccessHandler the {@link LogoutSuccessHandler} to use after a user * has been logged out. * @return the {@link LogoutConfigurer} for further customizations */ public LogoutConfigurer<H> logoutSuccessHandler( LogoutSuccessHandler logoutSuccessHandler) { this.logoutSuccessUrl = null; this.customLogoutSuccess = true; this.logoutSuccessHandler = logoutSuccessHandler; 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. * * @param permitAll if true grants access, else nothing is done * @return the {@link LogoutConfigurer} for further customization. */ public LogoutConfigurer<H> permitAll(boolean permitAll) { this.permitAll = permitAll; return this; } /** * Gets the {@link LogoutSuccessHandler} if not null, otherwise creates a new * {@link SimpleUrlLogoutSuccessHandler} using the {@link #logoutSuccessUrl(String)}. * * @return the {@link LogoutSuccessHandler} to use */ private LogoutSuccessHandler getLogoutSuccessHandler() { 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; } DelegatingLogoutSuccessHandler successHandler = new DelegatingLogoutSuccessHandler(defaultLogoutSuccessHandlerMappings); successHandler.setDefaultLogoutSuccessHandler(urlLogoutHandler); return successHandler; } @Override public void init(H http) throws Exception { if (permitAll) { PermitAllSupport.permitAll(http, this.logoutSuccessUrl); PermitAllSupport.permitAll(http, this.getLogoutRequestMatcher(http)); } DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http .getSharedObject(DefaultLoginPageGeneratingFilter.class); if (loginPageGeneratingFilter != null && !isCustomLogoutSuccess()) { loginPageGeneratingFilter.setLogoutSuccessUrl(getLogoutSuccessUrl()); } } @Override public void configure(H http) throws Exception { LogoutFilter logoutFilter = createLogoutFilter(http); http.addFilter(logoutFilter); } /** * Returns true if the logout success has been customized via * {@link #logoutSuccessUrl(String)} or * {@link #logoutSuccessHandler(LogoutSuccessHandler)}. * * @return true if logout success handling has been customized, else false */ boolean isCustomLogoutSuccess() { return customLogoutSuccess; } /** * Gets the logoutSuccesUrl or null if a * {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured. * * @return the logoutSuccessUrl */ private String getLogoutSuccessUrl() { return logoutSuccessUrl; } /** * Gets the {@link LogoutHandler} instances that will be used. * @return the {@link LogoutHandler} instances. Cannot be null. */ List<LogoutHandler> getLogoutHandlers() { return logoutHandlers; } /** * Creates the {@link LogoutFilter} using the {@link LogoutHandler} instances, the * {@link #logoutSuccessHandler(LogoutSuccessHandler)} and the * {@link #logoutUrl(String)}. * * @param http the builder to use * @return the {@link LogoutFilter} to use. * @throws Exception */ private LogoutFilter createLogoutFilter(H http) throws Exception { logoutHandlers.add(contextLogoutHandler); LogoutHandler[] handlers = logoutHandlers .toArray(new LogoutHandler[logoutHandlers.size()]); LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers); result.setLogoutRequestMatcher(getLogoutRequestMatcher(http)); result = postProcess(result); return result; } @SuppressWarnings("unchecked") private RequestMatcher getLogoutRequestMatcher(H http) { if (logoutRequestMatcher != null) { return logoutRequestMatcher; } if (http.getConfigurer(CsrfConfigurer.class) != null) { this.logoutRequestMatcher = new AntPathRequestMatcher(this.logoutUrl, "POST"); } else { this.logoutRequestMatcher = new OrRequestMatcher( new AntPathRequestMatcher(this.logoutUrl, "GET"), new AntPathRequestMatcher(this.logoutUrl, "POST"), new AntPathRequestMatcher(this.logoutUrl, "PUT"), new AntPathRequestMatcher(this.logoutUrl, "DELETE") ); } return this.logoutRequestMatcher; } }