Просмотр исходного кода

SEC-249: Support logout filter.

Ben Alex 19 лет назад
Родитель
Сommit
8cc5dcde30

+ 178 - 0
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.
+ * 
+ * <p>
+ * 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 <code>TokenBasedRememberMeServices</code> and
+ * <code>SecurityContextLogoutHandler</code> (in that order).
+ * </p>
+ * 
+ * <p>
+ * After logout, the URL specified by {@link #logoutSuccessUrl} will be shown.
+ * </p>
+ * 
+ * <p>
+ * <b>Do not use this class directly.</b> Instead configure
+ * <code>web.xml</code> to use the {@link
+ * org.acegisecurity.util.FilterToBeanProxy}.
+ * </p>
+ *
+ * @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 <code>true</code> if logout should occur, <code>false</code>
+     *         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;
+    }
+}

+ 27 - 0
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.
+ * 
+ * <p>
+ * 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);
+}

+ 47 - 0
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 <code>null</code>.
+     *
+     * @param request not used (can be <code>null</code>)
+     * @param response not used (can be <code>null</code>)
+     * @param authentication not used (can be <code>null</code>)
+     */
+    public void logout(HttpServletRequest request,
+        HttpServletResponse response, Authentication authentication) {
+        SecurityContextHolder.clearContext();
+    }
+}

+ 8 - 1
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);

+ 11 - 1
samples/contacts/src/main/webapp/ca/WEB-INF/applicationContext-acegi-security.xml

@@ -21,7 +21,7 @@
          <value>
 		    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 		    PATTERN_TYPE_APACHE_ANT
-            /**=httpSessionContextIntegrationFilter,httpRequestIntegrationFilter
+            /**=httpSessionContextIntegrationFilter,httpRequestIntegrationFilter,logoutFilter
          </value>
       </property>
     </bean>
@@ -49,6 +49,16 @@
    <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
    </bean>
 
+	<!-- note logout has little impact, due to container authentication functionality (used only so /j_acegi_logout doesn't give URL error) -->
+   <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
+      <constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
+      <constructor-arg>
+         <list>
+              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
+         </list>
+      </constructor-arg>
+   </bean>
+   
 	<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
 	
 				<!-- Implement by servlet specification -->

+ 12 - 1
samples/contacts/src/main/webapp/cas/WEB-INF/applicationContext-acegi-security.xml

@@ -19,7 +19,7 @@
          <value>
 		    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 		    PATTERN_TYPE_APACHE_ANT
-            /**=channelProcessingFilter,httpSessionContextIntegrationFilter,casProcessingFilter,basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
+            /**=channelProcessingFilter,httpSessionContextIntegrationFilter,logoutFilter,casProcessingFilter,basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
          </value>
       </property>
     </bean>
@@ -92,6 +92,17 @@
 		<property name="sendRenew"><value>false</value></property>
 	</bean>
 
+	<!-- note logout has little impact, due to CAS reauthentication functionality (it will cause a refresh of the authentication though) -->
+   <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
+      <constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
+      <constructor-arg>
+         <list>
+              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
+         </list>
+      </constructor-arg>
+   </bean>
+
+
 	<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
 	
 	<!-- Enabled by default for CAS, as a CAS deployment uses HTTPS -->

+ 1 - 1
samples/contacts/src/main/webapp/common/WEB-INF/jsp/index.jsp

@@ -27,6 +27,6 @@
   </tr>
 </c:forEach>
 </table>
-<p><a href="<c:url value="add.htm"/>">Add</a>   <p><a href="<c:url value="../logoff.jsp"/>">Logoff</a> (also clears any remember-me cookie)
+<p><a href="<c:url value="add.htm"/>">Add</a>   <p><a href="<c:url value="../j_acegi_logout"/>">Logoff</a> (also clears any remember-me cookie)
 </body>
 </html>

+ 0 - 9
samples/contacts/src/main/webapp/common/logoff.jsp

@@ -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");
-%>

+ 11 - 1
samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml

@@ -21,7 +21,7 @@
          <value>
 		    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
          </value>
       </property>
     </bean>
@@ -102,6 +102,16 @@
    <bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
       <property name="key"><value>springRocks</value></property>
    </bean>
+   
+   <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
+      <constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
+      <constructor-arg>
+         <list>
+              <ref bean="rememberMeServices"/>
+              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
+         </list>
+      </constructor-arg>
+   </bean>
 
    <!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
 

+ 11 - 1
samples/contacts/src/main/webapp/ldap/WEB-INF/applicationContext-acegi-security.xml

@@ -21,7 +21,7 @@
          <value>
         CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
         PATTERN_TYPE_APACHE_ANT
-            /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
+            /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
          </value>
       </property>
    </bean>
@@ -64,6 +64,16 @@
    <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
    </bean>
 
+   <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
+      <constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
+      <constructor-arg>
+         <list>
+              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
+         </list>
+      </constructor-arg>
+   </bean>
+
+
    <!-- ===================== HTTP REQUEST SECURITY ==================== -->
 
    <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">

+ 12 - 1
samples/contacts/src/main/webapp/x509/WEB-INF/applicationContext-acegi-security.xml

@@ -19,7 +19,7 @@
          <value>
 		    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 		    PATTERN_TYPE_APACHE_ANT
-            /**=channelProcessingFilter,httpSessionContextIntegrationFilter,x509ProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
+            /**=channelProcessingFilter,httpSessionContextIntegrationFilter,logoutFilter,x509ProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
          </value>
       </property>
     </bean>
@@ -75,6 +75,17 @@
       <!--  <property name="subjectDNRegex"><value>emailAddress=(.*?),</value></property> -->
 	</bean>
 
+	<!-- note logout has little impact, due to X509 certificate still being presented (it will cause a refresh of the authentication though) -->
+   <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
+      <constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
+      <constructor-arg>
+         <list>
+              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
+         </list>
+      </constructor-arg>
+   </bean>
+
+
 	<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
 	
 	<!-- Enabled by default for X.509 (obviously) -->