View Javadoc

1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.springframework.security.ui.logout;
17  
18  import java.io.IOException;
19  
20  import javax.servlet.FilterChain;
21  import javax.servlet.ServletException;
22  import javax.servlet.http.HttpServletRequest;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.springframework.security.Authentication;
26  import org.springframework.security.ui.SpringSecurityFilter;
27  import org.springframework.security.ui.FilterChainOrder;
28  import org.springframework.security.util.RedirectUtils;
29  import org.springframework.security.util.UrlUtils;
30  import org.springframework.security.context.SecurityContextHolder;
31  import org.springframework.util.Assert;
32  import org.springframework.util.StringUtils;
33  
34  /**
35   * Logs a principal out.
36   * <p>
37   * Polls a series of {@link LogoutHandler}s. The handlers should be specified in the order they are required.
38   * Generally you will want to call logout handlers <code>TokenBasedRememberMeServices</code> and
39   * <code>SecurityContextLogoutHandler</code> (in that order).
40   * </p>
41   * <p>
42   * After logout, the URL specified by {@link #logoutSuccessUrl} will be shown.
43   * </p>
44   * 
45   * @author Ben Alex
46   * @version $Id: LogoutFilter.java 3111 2008-06-01 16:15:09Z luke_t $
47   */
48  public class LogoutFilter extends SpringSecurityFilter {
49  
50      //~ Instance fields ================================================================================================
51  
52      private String filterProcessesUrl = "/j_spring_security_logout";
53      private String logoutSuccessUrl;
54      private LogoutHandler[] handlers;
55      private boolean useRelativeContext;
56  
57      //~ Constructors ===================================================================================================
58  
59      public LogoutFilter(String logoutSuccessUrl, LogoutHandler[] handlers) {
60          Assert.notEmpty(handlers, "LogoutHandlers are required");
61          this.logoutSuccessUrl = logoutSuccessUrl;
62          Assert.isTrue(UrlUtils.isValidRedirectUrl(logoutSuccessUrl), logoutSuccessUrl + " isn't a valid redirect URL");
63          this.handlers = handlers;
64      }
65  
66      //~ Methods ========================================================================================================
67  
68      public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException,
69              ServletException {
70  
71          if (requiresLogout(request, response)) {
72              Authentication auth = SecurityContextHolder.getContext().getAuthentication();
73  
74              if (logger.isDebugEnabled()) {
75                  logger.debug("Logging out user '" + auth + "' and redirecting to logout page");
76              }
77  
78              for (int i = 0; i < handlers.length; i++) {
79                  handlers[i].logout(request, response, auth);
80              }
81  
82              String targetUrl = determineTargetUrl(request, response);
83  
84              sendRedirect(request, response, targetUrl);
85  
86              return;
87          }
88  
89          chain.doFilter(request, response);
90      }
91  
92      /**
93       * Allow subclasses to modify when a logout should take place.
94       *
95       * @param request the request
96       * @param response the response
97       *
98       * @return <code>true</code> if logout should occur, <code>false</code> otherwise
99       */
100     protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {
101         String uri = request.getRequestURI();
102         int pathParamIndex = uri.indexOf(';');
103 
104         if (pathParamIndex > 0) {
105             // strip everything from the first semi-colon
106             uri = uri.substring(0, pathParamIndex);
107         }
108 
109         int queryParamIndex = uri.indexOf('?');
110 
111         if (queryParamIndex > 0) {
112             // strip everything from the first question mark
113             uri = uri.substring(0, queryParamIndex);
114         }
115 
116         if ("".equals(request.getContextPath())) {
117             return uri.endsWith(filterProcessesUrl);
118         }
119 
120         return uri.endsWith(request.getContextPath() + filterProcessesUrl);
121     }
122 
123     /**
124      * Returns the target URL to redirect to after logout.
125      * <p>
126      * By default it will check for a <tt>logoutSuccessUrl</tt> parameter in
127      * the request and use this. If that isn't present it will use the configured <tt>logoutSuccessUrl</tt>. If this
128      * hasn't been set it will check the Referer header and use the URL from there.
129      *
130      */
131     protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
132         String targetUrl = request.getParameter("logoutSuccessUrl");
133 
134         if(!StringUtils.hasLength(targetUrl)) {
135             targetUrl = getLogoutSuccessUrl();
136         }
137 
138         if (!StringUtils.hasLength(targetUrl)) {
139             targetUrl = request.getHeader("Referer");
140         }        
141 
142         if (!StringUtils.hasLength(targetUrl)) {
143             targetUrl = "/";
144         }
145 
146         return targetUrl;
147     }
148 
149     /**
150      * Allow subclasses to modify the redirection message.
151      *
152      * @param request  the request
153      * @param response the response
154      * @param url      the URL to redirect to
155      *
156      * @throws IOException in the event of any failure
157      */
158     protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url)
159             throws IOException {
160 
161         RedirectUtils.sendRedirect(request, response, url, useRelativeContext);
162     }
163 
164     public void setFilterProcessesUrl(String filterProcessesUrl) {
165         Assert.hasText(filterProcessesUrl, "FilterProcessesUrl required");
166         Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
167         this.filterProcessesUrl = filterProcessesUrl;
168     }
169 
170     protected String getLogoutSuccessUrl() {
171         return logoutSuccessUrl;
172     }    
173     
174     protected String getFilterProcessesUrl() {
175         return filterProcessesUrl;
176     }
177 
178     public void setUseRelativeContext(boolean useRelativeContext) {
179         this.useRelativeContext = useRelativeContext;
180     }
181 
182     public int getOrder() {
183         return FilterChainOrder.LOGOUT_FILTER;
184     }
185 }