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 }