diff --git a/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java
index 6e1de830f9..5dd4f14627 100644
--- a/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java
+++ b/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java
@@ -4,8 +4,10 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
+import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.util.StringUtils;
@@ -20,11 +22,11 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
static final String DEF_LOGOUT_SUCCESS_URL = "/";
static final String ATT_INVALIDATE_SESSION = "invalidate-session";
- static final String DEF_INVALIDATE_SESSION = "true";
static final String ATT_LOGOUT_URL = "logout-url";
static final String DEF_LOGOUT_URL = "/j_spring_security_logout";
static final String ATT_LOGOUT_HANDLER = "success-handler-ref";
+ static final String ATT_DELETE_COOKIES = "delete-cookies";
final String rememberMeServices;
@@ -38,6 +40,7 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
String successHandlerRef = null;
String logoutSuccessUrl = null;
String invalidateSession = null;
+ String deleteCookies = null;
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class);
@@ -50,6 +53,7 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
logoutSuccessUrl = element.getAttribute(ATT_LOGOUT_SUCCESS_URL);
WebConfigUtils.validateHttpRedirect(logoutSuccessUrl, pc, source);
invalidateSession = element.getAttribute(ATT_INVALIDATE_SESSION);
+ deleteCookies = element.getAttribute(ATT_DELETE_COOKIES);
}
if (!StringUtils.hasText(logoutUrl)) {
@@ -71,23 +75,22 @@ class LogoutBeanDefinitionParser implements BeanDefinitionParser {
builder.addConstructorArgValue(logoutSuccessUrl);
}
- if (!StringUtils.hasText(invalidateSession)) {
- invalidateSession = DEF_INVALIDATE_SESSION;
- }
-
ManagedList handlers = new ManagedList();
- SecurityContextLogoutHandler sclh = new SecurityContextLogoutHandler();
- if ("true".equals(invalidateSession)) {
- sclh.setInvalidateHttpSession(true);
- } else {
- sclh.setInvalidateHttpSession(false);
- }
+ BeanDefinition sclh = new RootBeanDefinition(SecurityContextLogoutHandler.class);
+ sclh.getPropertyValues().addPropertyValue("invalidateHttpSession", !"false".equals(invalidateSession));
handlers.add(sclh);
if (rememberMeServices != null) {
handlers.add(new RuntimeBeanReference(rememberMeServices));
}
+ if (StringUtils.hasText(deleteCookies)) {
+ BeanDefinition cookieDeleter = new RootBeanDefinition(CookieClearingLogoutHandler.class);
+ String[] names = StringUtils.commaDelimitedListToStringArray(deleteCookies);
+ cookieDeleter.getConstructorArgumentValues().addGenericArgumentValue(names);
+ handlers.add(cookieDeleter);
+ }
+
builder.addConstructorArgValue(handlers);
return builder.getBeanDefinition();
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
index b138557177..8b2294ce23 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
@@ -355,6 +355,9 @@ logout.attlist &=
logout.attlist &=
## A reference to a LogoutSuccessHandler implementation which will be used to determine the destination to which the user is taken after logging out.
attribute success-handler-ref {xsd:token}?
+logout.attlist &=
+ ## A comma-separated list of the names of cookies which should be deleted when the user logs out
+ attribute delete-cookies {xsd:token}?
request-cache =
## Allow the RequestCache used for saving requests during the login process to be set
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
index e950e484f0..9844c01e6e 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
@@ -881,6 +881,11 @@
A reference to a LogoutSuccessHandler implementation which will be used to determine the destination to which the user is taken after logging out.
+
+
+ A comma-separated list of the names of cookies which should be deleted when the user logs out
+
+
Allow the RequestCache used for saving requests during the login process to be set
diff --git a/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy
index 919d2d386b..5ab2134811 100644
--- a/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy
@@ -43,6 +43,7 @@ import org.springframework.security.web.savedrequest.HttpSessionRequestCache
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
import org.springframework.security.web.session.SessionManagementFilter
+import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler
class MiscHttpConfigTests extends AbstractHttpConfigTests {
def 'Minimal configuration parses'() {
@@ -312,8 +313,6 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
}
createAppContext()
- def filters = getFilters("/someurl")
-
expect:
getFilters("/someurl")[2] instanceof X509AuthenticationFilter
}
@@ -343,6 +342,20 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
BeanCreationException e = thrown()
}
+ def cookiesToDeleteOnLogoutUrlAddsCorrectLogoutHandler() {
+ xml.http {
+ 'logout'('delete-cookies': 'JSESSIONID, mycookie')
+ 'form-login'()
+ }
+ createAppContext()
+ def handlers = getFilter(LogoutFilter).handlers
+
+ expect:
+ handlers[1] instanceof CookieClearingLogoutHandler
+ handlers[1].cookiesToClear[0] = 'JSESSIONID'
+ handlers[1].cookiesToClear[1] = 'mycookie'
+ }
+
def invalidLogoutUrlIsDetected() {
when:
xml.http {
diff --git a/docs/manual/src/docbook/appendix-namespace.xml b/docs/manual/src/docbook/appendix-namespace.xml
index 63f3573b8a..533ddb1cf4 100644
--- a/docs/manual/src/docbook/appendix-namespace.xml
+++ b/docs/manual/src/docbook/appendix-namespace.xml
@@ -496,11 +496,22 @@
The destination URL which the user will be taken to after logging out.
Defaults to "/".
+
+ The success-handler-ref attribute
+ May be used to supply an instance of LogoutSuccessHandler
+ which will be invoked to control the navigation after logging out.
+
+
The invalidate-session attribute
Maps to the invalidateHttpSession of the
SecurityContextLogoutHandler. Defaults to "true", so the
- session will be invalidated on logout.
+ session will be invalidated on logout.
+
+
+ The delete-cookies attribute
+ A comma-separated list of the names of cookies which should be deleted when the user logs out.
+
diff --git a/docs/manual/src/docbook/namespace-config.xml b/docs/manual/src/docbook/namespace-config.xml
index 51a3baf3bd..a532ba5842 100644
--- a/docs/manual/src/docbook/namespace-config.xml
+++ b/docs/manual/src/docbook/namespace-config.xml
@@ -338,6 +338,14 @@
information on how to customize the flow when authentication fails.
+
+ Logout Handling
+ The logout element adds support for logging out by navigating
+ to a particular URL. The default logout URL is /j_spring_security_logout,
+ but you can set it to something else using the logout-url attribute.
+ More information on other available attributes may be found in the namespace appendix.
+
+
Using other Authentication Providers
In practice you will need a more scalable source of user information than a few
@@ -465,8 +473,28 @@
the session-management element:
...
-
- ]]>
+
+ ]]>Note that if you use this mechanism to detect session timeouts, it
+ may falsely report an error if the user logs out and then logs back in without
+ closing the browser. This is because the session cookie is not cleared when you
+ invalidate the session and will be resubmitted even if the user has logged out.
+ You may be able to explicitly delete the JSESSIONID cookie on logging out, for
+ example by using the following syntax in the logout handler:
+
+
+ ]]> Unfortunately this can't be guaranteed to work with every servlet container,
+ so you will need to test it in your environment
+ If you are running your application behind a proxy, you may also be able
+ to remove the session cookie by configuring the proxy server. For example,
+ using Apache HTTPD's mod_headers, the following directive would delete the
+ JSESSIONID cookie by expiring it in the response to a
+ logout request (assuming the application is deployed under the path
+ /tutorial):
+ <LocationMatch "/tutorial/j_spring_security_logout">
+ Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
+ </LocationMatch>
+ .
Concurrent Session Control
diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.java
index 20c98a974f..cb25f0a8cb 100644
--- a/web/src/main/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.java
+++ b/web/src/main/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.java
@@ -1,7 +1,34 @@
package org.springframework.security.web.authentication.logout;
+import java.util.*;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
/**
+ * A logout handler which clears a defined list of cookies, using the context path as the
+ * cookie path.
+ *
* @author Luke Taylor
+ * @since 3.1
*/
-public class CookieClearingLogoutHandler {
+public final class CookieClearingLogoutHandler implements LogoutHandler {
+ private final List cookiesToClear;
+
+ public CookieClearingLogoutHandler(String... cookiesToClear) {
+ Assert.notNull(cookiesToClear, "List of cookies cannot be null");
+ this.cookiesToClear = Arrays.asList(cookiesToClear);
+ }
+
+ public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+ for (String cookieName : cookiesToClear) {
+ Cookie cookie = new Cookie(cookieName, null);
+ cookie.setPath(request.getContextPath());
+ cookie.setMaxAge(0);
+ response.addCookie(cookie);
+ }
+ }
}
diff --git a/web/src/test/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandlerTests.java b/web/src/test/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandlerTests.java
index c7b04f7e5c..7583c451eb 100644
--- a/web/src/test/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandlerTests.java
+++ b/web/src/test/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandlerTests.java
@@ -1,7 +1,30 @@
package org.springframework.security.web.authentication.logout;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+
+import javax.servlet.http.Cookie;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.core.Authentication;
+
/**
* @author Luke Taylor
*/
public class CookieClearingLogoutHandlerTests {
+ @Test
+ public void configuredCookiesAreCleared() {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setContextPath("/app");
+ CookieClearingLogoutHandler handler = new CookieClearingLogoutHandler("my_cookie", "my_cookie_too");
+ handler.logout(request, response, mock(Authentication.class));
+ assertEquals(2, response.getCookies().length);
+ for (Cookie c : response.getCookies()) {
+ assertEquals("/app", c.getPath());
+ assertEquals(0, c.getMaxAge());
+ }
+ }
}