diff --git a/core/src/main/java/org/acegisecurity/ui/logout/LogoutFilter.java b/core/src/main/java/org/acegisecurity/ui/logout/LogoutFilter.java new file mode 100644 index 0000000000..3294b337ee --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/logout/LogoutFilter.java @@ -0,0 +1,178 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * 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.acegisecurity.ui.logout; + +import org.acegisecurity.Authentication; + +import org.acegisecurity.context.SecurityContextHolder; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.Assert; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * Logs a principal out. + * + *

+ * Polls a series of {@link LogoutHandler}s. The handlers should be specified + * in the order they are required. Generally you will want to call logout + * handlers TokenBasedRememberMeServices and + * SecurityContextLogoutHandler (in that order). + *

+ * + *

+ * After logout, the URL specified by {@link #logoutSuccessUrl} will be shown. + *

+ * + *

+ * Do not use this class directly. Instead configure + * web.xml to use the {@link + * org.acegisecurity.util.FilterToBeanProxy}. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class LogoutFilter implements Filter { + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(LogoutFilter.class); + + //~ Instance fields ======================================================== + + private String filterProcessesUrl = "/j_acegi_logout"; + private String logoutSuccessUrl; + private LogoutHandler[] handlers; + + //~ Constructors =========================================================== + + public LogoutFilter(String logoutSuccessUrl, LogoutHandler[] handlers) { + Assert.hasText(logoutSuccessUrl, "LogoutSuccessUrl required"); + Assert.notEmpty(handlers, "LogoutHandlers are required"); + this.logoutSuccessUrl = logoutSuccessUrl; + this.handlers = handlers; + } + + //~ Methods ================================================================ + + /** + * Not used. Use IoC container lifecycle methods instead. + */ + public void destroy() {} + + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + if (!(request instanceof HttpServletRequest)) { + throw new ServletException("Can only process HttpServletRequest"); + } + + if (!(response instanceof HttpServletResponse)) { + throw new ServletException("Can only process HttpServletResponse"); + } + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + if (requiresLogout(httpRequest, httpResponse)) { + Authentication auth = SecurityContextHolder.getContext() + .getAuthentication(); + + if (logger.isDebugEnabled()) { + logger.debug("Logging out user '" + auth + + "' and redirecting to logout page"); + } + + if (auth != null) { + for (int i = 0; i < handlers.length; i++) { + handlers[i].logout(httpRequest, httpResponse, auth); + } + } + + sendRedirect(httpRequest, httpResponse, logoutSuccessUrl); + + return; + } + + chain.doFilter(request, response); + } + + /** + * Not used. Use IoC container lifecycle methods instead. + * + * @param arg0 ignored + * + * @throws ServletException ignored + */ + public void init(FilterConfig arg0) throws ServletException {} + + /** + * Allow subclasses to modify when a logout should tak eplace. + * + * @param request the request + * @param response the response + * + * @return true if logout should occur, false + * otherwise + */ + protected boolean requiresLogout(HttpServletRequest request, + HttpServletResponse response) { + String uri = request.getRequestURI(); + int pathParamIndex = uri.indexOf(';'); + + if (pathParamIndex > 0) { + // strip everything after the first semi-colon + uri = uri.substring(0, pathParamIndex); + } + + return uri.endsWith(request.getContextPath() + filterProcessesUrl); + } + + /** + * Allow subclasses to modify the redirection message. + * + * @param request the request + * @param response the response + * @param url the URL to redirect to + * + * @throws IOException in the event of any failure + */ + protected void sendRedirect(HttpServletRequest request, + HttpServletResponse response, String url) throws IOException { + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url = request.getContextPath() + url; + } + + response.sendRedirect(response.encodeRedirectURL(url)); + } + + public void setFilterProcessesUrl(String filterProcessesUrl) { + Assert.hasText(filterProcessesUrl, "FilterProcessesUrl required"); + this.filterProcessesUrl = filterProcessesUrl; + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/logout/LogoutHandler.java b/core/src/main/java/org/acegisecurity/ui/logout/LogoutHandler.java new file mode 100644 index 0000000000..24c363a850 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/logout/LogoutHandler.java @@ -0,0 +1,27 @@ +package org.acegisecurity.ui.logout; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.acegisecurity.Authentication; + +/** + * Indicates a class that is able to participate in logout handling. + * + *

+ * Called by {@link LogoutFilter}. + * + * @author Ben Alex + * @version $Id$ + */ +public interface LogoutHandler { + + /** + * Causes a logout to be completed. The method must complete successfully. + * + * @param request the HTTP request + * @param response the HTTP resonse + * @param authentication the current principal details + */ + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication); +} diff --git a/core/src/main/java/org/acegisecurity/ui/logout/SecurityContextLogoutHandler.java b/core/src/main/java/org/acegisecurity/ui/logout/SecurityContextLogoutHandler.java new file mode 100644 index 0000000000..99a5745a40 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/logout/SecurityContextLogoutHandler.java @@ -0,0 +1,47 @@ +/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * 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.acegisecurity.ui.logout; + +import org.acegisecurity.Authentication; + +import org.acegisecurity.context.SecurityContextHolder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * Performs a logout by modifying the {@link + * org.acegisecurity.context.SecurityContextHolder}. + * + * @author Ben Alex + * @version $Id$ + */ +public class SecurityContextLogoutHandler implements LogoutHandler { + //~ Methods ================================================================ + + /** + * Does not use any arguments. They can all be null. + * + * @param request not used (can be null) + * @param response not used (can be null) + * @param authentication not used (can be null) + */ + public void logout(HttpServletRequest request, + HttpServletResponse response, Authentication authentication) { + SecurityContextHolder.clearContext(); + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java b/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java index 11b405b020..9a8d08d6e8 100644 --- a/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java +++ b/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java @@ -21,6 +21,7 @@ import org.acegisecurity.providers.rememberme.RememberMeAuthenticationToken; import org.acegisecurity.ui.AuthenticationDetailsSource; import org.acegisecurity.ui.AuthenticationDetailsSourceImpl; +import org.acegisecurity.ui.logout.LogoutHandler; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.userdetails.UserDetailsService; @@ -108,7 +109,7 @@ import javax.servlet.http.HttpServletResponse; * @version $Id$ */ public class TokenBasedRememberMeServices implements RememberMeServices, - InitializingBean { + InitializingBean, LogoutHandler { //~ Static fields/initializers ============================================= public static final String ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY = "ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE"; @@ -340,6 +341,12 @@ public class TokenBasedRememberMeServices implements RememberMeServices, } } + public void logout(HttpServletRequest request, + HttpServletResponse response, Authentication authentication) { + cancelCookie(request, response, + "Logout of user " + authentication.getName()); + } + protected Cookie makeCancelCookie(HttpServletRequest request) { Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, null); diff --git a/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml b/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml index 6c3eb67a86..f99f5e48fd 100644 --- a/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml +++ b/samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml @@ -21,7 +21,7 @@ CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT - /**=httpSessionContextIntegrationFilter,httpRequestIntegrationFilter + /**=httpSessionContextIntegrationFilter,httpRequestIntegrationFilter,logoutFilter @@ -49,6 +49,16 @@ + + + + + + + + + + diff --git a/samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml b/samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml index 8443a141f4..eaabf39df2 100644 --- a/samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml +++ b/samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml @@ -19,7 +19,7 @@ CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT - /**=channelProcessingFilter,httpSessionContextIntegrationFilter,casProcessingFilter,basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor + /**=channelProcessingFilter,httpSessionContextIntegrationFilter,logoutFilter,casProcessingFilter,basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor @@ -92,6 +92,17 @@ false + + + + + + + + + + + diff --git a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp index 2c2ddc28a7..024bef7e72 100644 --- a/samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp +++ b/samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp @@ -27,6 +27,6 @@ -

">Add

">Logoff (also clears any remember-me cookie) +

">Add

">Logoff (also clears any remember-me cookie) diff --git a/samples/contacts/src/main/webapp/common/logoff.jsp b/samples/contacts/src/main/webapp/common/logoff.jsp deleted file mode 100644 index 7002eac627..0000000000 --- a/samples/contacts/src/main/webapp/common/logoff.jsp +++ /dev/null @@ -1,9 +0,0 @@ -<%@ page import="javax.servlet.http.Cookie" %> -<%@ page import="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices" %> -<% -session.invalidate(); -Cookie terminate = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, null); -terminate.setMaxAge(0); -response.addCookie(terminate); -response.sendRedirect("index.jsp"); -%> \ No newline at end of file diff --git a/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml b/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml index fbfc0eecad..373316c057 100644 --- a/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml +++ b/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml @@ -21,7 +21,7 @@ CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT - /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,switchUserProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor + /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,switchUserProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor @@ -102,6 +102,16 @@ springRocks + + + + + + + + + + diff --git a/samples/contacts/src/main/webapp/ldap/WEB-INF/applicationContext-acegi-security.xml b/samples/contacts/src/main/webapp/ldap/WEB-INF/applicationContext-acegi-security.xml index 905eac4967..8e13044fe3 100644 --- a/samples/contacts/src/main/webapp/ldap/WEB-INF/applicationContext-acegi-security.xml +++ b/samples/contacts/src/main/webapp/ldap/WEB-INF/applicationContext-acegi-security.xml @@ -21,7 +21,7 @@ CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT - /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor + /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor @@ -64,6 +64,16 @@ + + + + + + + + + + diff --git a/samples/contacts/src/main/webapp/x509/WEB-INF/applicationContext-acegi-security.xml b/samples/contacts/src/main/webapp/x509/WEB-INF/applicationContext-acegi-security.xml index 3f5ec39ac9..8fb89e398b 100644 --- a/samples/contacts/src/main/webapp/x509/WEB-INF/applicationContext-acegi-security.xml +++ b/samples/contacts/src/main/webapp/x509/WEB-INF/applicationContext-acegi-security.xml @@ -19,7 +19,7 @@ CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT - /**=channelProcessingFilter,httpSessionContextIntegrationFilter,x509ProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor + /**=channelProcessingFilter,httpSessionContextIntegrationFilter,logoutFilter,x509ProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor @@ -75,6 +75,17 @@ + + + + + + + + + + +