001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.web.filter.authc; 020 021import org.apache.shiro.SecurityUtils; 022import org.apache.shiro.session.SessionException; 023import org.apache.shiro.subject.Subject; 024import org.apache.shiro.util.StringUtils; 025import org.apache.shiro.web.servlet.AdviceFilter; 026import org.apache.shiro.web.util.WebUtils; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029 030import javax.servlet.ServletRequest; 031import javax.servlet.ServletResponse; 032import javax.servlet.http.HttpServletResponse; 033 034import java.util.Locale; 035 036import static org.apache.shiro.web.filter.mgt.DefaultFilter.logout; 037 038/** 039 * Simple Filter that, upon receiving a request, will immediately log-out the currently executing 040 * {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject} 041 * and then redirect them to a configured {@link #getRedirectUrl() redirectUrl}. 042 * 043 * @since 1.2 044 */ 045public class LogoutFilter extends AdviceFilter { 046 047 private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class); 048 049 /** 050 * The default redirect URL to where the user will be redirected after logout. The value is {@code "/"}, Shiro's 051 * representation of the web application's context root. 052 */ 053 public static final String DEFAULT_REDIRECT_URL = "/"; 054 055 /** 056 * The URL to where the user will be redirected after logout. 057 */ 058 private String redirectUrl = DEFAULT_REDIRECT_URL; 059 060 /** 061 * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example: 062 * out while typing in an address bar. If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause 063 * a logout to occur. 064 */ 065 private boolean postOnlyLogout = false; 066 067 /** 068 * Acquires the currently executing {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}, 069 * a potentially Subject or request-specific 070 * {@link #getRedirectUrl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, org.apache.shiro.subject.Subject) redirectUrl}, 071 * and redirects the end-user to that redirect url. 072 * 073 * @param request the incoming ServletRequest 074 * @param response the outgoing ServletResponse 075 * @return {@code false} always as typically no further interaction should be done after user logout. 076 * @throws Exception if there is any error. 077 */ 078 @Override 079 protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { 080 081 Subject subject = getSubject(request, response); 082 083 // Check if POST only logout is enabled 084 if (isPostOnlyLogout()) { 085 086 // check if the current request's method is a POST, if not redirect 087 if (!WebUtils.toHttp(request).getMethod().toUpperCase(Locale.ENGLISH).equals("POST")) { 088 return onLogoutRequestNotAPost(request, response); 089 } 090 } 091 092 String redirectUrl = getRedirectUrl(request, response, subject); 093 //try/catch added for SHIRO-298: 094 try { 095 subject.logout(); 096 } catch (SessionException ise) { 097 log.debug("Encountered session exception during logout. This can generally safely be ignored.", ise); 098 } 099 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}