View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.web.filter.authc;
20  
21  import org.apache.shiro.SecurityUtils;
22  import org.apache.shiro.session.SessionException;
23  import org.apache.shiro.subject.Subject;
24  import org.apache.shiro.util.StringUtils;
25  import org.apache.shiro.web.servlet.AdviceFilter;
26  import org.apache.shiro.web.util.WebUtils;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import javax.servlet.ServletRequest;
31  import javax.servlet.ServletResponse;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import java.util.Locale;
35  
36  import static org.apache.shiro.web.filter.mgt.DefaultFilter.logout;
37  
38  /**
39   * Simple Filter that, upon receiving a request, will immediately log-out the currently executing
40   * {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}
41   * and then redirect them to a configured {@link #getRedirectUrl() redirectUrl}.
42   *
43   * @since 1.2
44   */
45  public class LogoutFilter extends AdviceFilter {
46      
47      private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class);
48  
49      /**
50       * The default redirect URL to where the user will be redirected after logout.  The value is {@code "/"}, Shiro's
51       * representation of the web application's context root.
52       */
53      public static final String DEFAULT_REDIRECT_URL = "/";
54  
55      /**
56       * The URL to where the user will be redirected after logout.
57       */
58      private String redirectUrl = DEFAULT_REDIRECT_URL;
59  
60      /**
61       * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example:
62       * out while typing in an address bar.  If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause
63       * a logout to occur.
64       */
65      private boolean postOnlyLogout = false;
66  
67      /**
68       * Acquires the currently executing {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject},
69       * a potentially Subject or request-specific
70       * {@link #getRedirectUrl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, org.apache.shiro.subject.Subject) redirectUrl},
71       * and redirects the end-user to that redirect url.
72       *
73       * @param request  the incoming ServletRequest
74       * @param response the outgoing ServletResponse
75       * @return {@code false} always as typically no further interaction should be done after user logout.
76       * @throws Exception if there is any error.
77       */
78      @Override
79      protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
80  
81          Subject subject = getSubject(request, response);
82  
83          // Check if POST only logout is enabled
84          if (isPostOnlyLogout()) {
85  
86              // check if the current request's method is a POST, if not redirect
87              if (!WebUtils.toHttp(request).getMethod().toUpperCase(Locale.ENGLISH).equals("POST")) {
88                 return onLogoutRequestNotAPost(request, response);
89              }
90          }
91  
92          String redirectUrl = getRedirectUrl(request, response, subject);
93          //try/catch added for SHIRO-298:
94          try {
95              subject.logout();
96          } catch (SessionException ise) {
97              log.debug("Encountered session exception during logout.  This can generally safely be ignored.", ise);
98          }
99          issueRedirect(request, response, redirectUrl);
100         return false;
101     }
102 
103     /**
104      * Returns the currently executing {@link Subject}.  This implementation merely defaults to calling
105      * {@code SecurityUtils.}{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}, but can be overridden
106      * by subclasses for different retrieval strategies.
107      *
108      * @param request  the incoming Servlet request
109      * @param response the outgoing Servlet response
110      * @return the currently executing {@link Subject}.
111      */
112     protected Subject getSubject(ServletRequest request, ServletResponse response) {
113         return SecurityUtils.getSubject();
114     }
115 
116     /**
117      * Issues an HTTP redirect to the specified URL after subject logout.  This implementation simply calls
118      * {@code WebUtils.}{@link WebUtils#issueRedirect(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String) issueRedirect(request,response,redirectUrl)}.
119      *
120      * @param request  the incoming Servlet request
121      * @param response the outgoing Servlet response
122      * @param redirectUrl the URL to where the browser will be redirected immediately after Subject logout.
123      * @throws Exception if there is any error.
124      */
125     protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl) throws Exception {
126         WebUtils.issueRedirect(request, response, redirectUrl);
127     }
128 
129     /**
130      * Returns the redirect URL to send the user after logout.  This default implementation ignores the arguments and
131      * returns the static configured {@link #getRedirectUrl() redirectUrl} property, but this method may be overridden
132      * by subclasses to dynamically construct the URL based on the request or subject if necessary.
133      * <p/>
134      * Note: the Subject is <em>not</em> yet logged out at the time this method is invoked.  You may access the Subject's
135      * session if one is available and if necessary.
136      * <p/>
137      * Tip: if you need to access the Subject's session, consider using the
138      * {@code Subject.}{@link Subject#getSession(boolean) getSession(false)} method to ensure a new session isn't created unnecessarily.
139      * If a session would be created, it will be immediately stopped after logout, not providing any value and
140      * unnecessarily taxing session infrastructure/resources.
141      *
142      * @param request the incoming Servlet request
143      * @param response the outgoing ServletResponse
144      * @param subject the not-yet-logged-out currently executing Subject
145      * @return the redirect URL to send the user after logout.
146      */
147     protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject) {
148         return getRedirectUrl();
149     }
150 
151     /**
152      * Returns the URL to where the user will be redirected after logout.  Default is the web application's context
153      * root, i.e. {@code "/"}
154      *
155      * @return the URL to where the user will be redirected after logout.
156      */
157     public String getRedirectUrl() {
158         return redirectUrl;
159     }
160 
161     /**
162      * Sets the URL to where the user will be redirected after logout.  Default is the web application's context
163      * root, i.e. {@code "/"}
164      *
165      * @param redirectUrl the url to where the user will be redirected after logout
166      */
167     @SuppressWarnings("unused")
168     public void setRedirectUrl(String redirectUrl) {
169         this.redirectUrl = redirectUrl;
170     }
171 
172 
173     /**
174      * This method is called when <code>postOnlyLogout</code> is <code>true</code>, and the request was NOT a <code>POST</code>.
175      * For example if this filter is bound to '/logout' and the caller makes a GET request, this method would be invoked.
176      * <p>
177      *     The default implementation sets the response code to a 405, and sets the 'Allow' header to 'POST', and
178      *     always returns false.
179      * </p>
180      *
181      * @return The return value indicates if the processing should continue in this filter chain.
182      */
183     protected boolean onLogoutRequestNotAPost(ServletRequest request, ServletResponse response) {
184 
185         HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
186         httpServletResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
187         httpServletResponse.setHeader("Allow", "POST");
188         return false;
189     }
190 
191     /**
192      * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example:
193      * out while typing in an address bar.  If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause
194      * a logout to occur.
195      *
196      * @return Returns true if POST only logout is enabled
197      */
198     public boolean isPostOnlyLogout() {
199         return postOnlyLogout;
200     }
201 
202     /**
203      * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example:
204      * out while typing in an address bar.  If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause
205      * a logout to occur.
206      * @param postOnlyLogout enable or disable POST only logout.
207      */
208     public void setPostOnlyLogout(boolean postOnlyLogout) {
209         this.postOnlyLogout = postOnlyLogout;
210     }
211 }