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.web.servlet.AdviceFilter; 025import org.apache.shiro.web.util.WebUtils; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import javax.servlet.ServletRequest; 030import javax.servlet.ServletResponse; 031import javax.servlet.http.HttpServletResponse; 032 033import java.util.Locale; 034 035/** 036 * Simple Filter that, upon receiving a request, will immediately log-out the currently executing 037 * {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject} 038 * and then redirect them to a configured {@link #getRedirectUrl() redirectUrl}. 039 * 040 * @since 1.2 041 */ 042public class LogoutFilter extends AdviceFilter { 043 /** 044 * The default redirect URL to where the user will be redirected after logout. The value is {@code "/"}, Shiro's 045 * representation of the web application's context root. 046 */ 047 public static final String DEFAULT_REDIRECT_URL = "/"; 048 049 private static final Logger LOGGER = LoggerFactory.getLogger(LogoutFilter.class); 050 051 052 /** 053 * The URL to where the user will be redirected after logout. 054 */ 055 private String redirectUrl = DEFAULT_REDIRECT_URL; 056 057 /** 058 * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example: 059 * out while typing in an address bar. If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause 060 * a logout to occur. 061 */ 062 private boolean postOnlyLogout; 063 064 /** 065 * Acquires the currently executing {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}, 066 * a potentially Subject or request-specific 067 * {@link #getRedirectUrl(ServletRequest, ServletResponse, org.apache.shiro.subject.Subject) redirectUrl}, 068 * and redirects the end-user to that redirect url. 069 * 070 * @param request the incoming ServletRequest 071 * @param response the outgoing ServletResponse 072 * @return {@code false} always as typically no further interaction should be done after user logout. 073 * @throws Exception if there is any error. 074 */ 075 @Override 076 protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { 077 078 Subject subject = getSubject(request, response); 079 080 // Check if POST only logout is enabled 081 if (isPostOnlyLogout()) { 082 083 // check if the current request's method is a POST, if not redirect 084 if (!WebUtils.toHttp(request).getMethod().toUpperCase(Locale.ENGLISH).equals("POST")) { 085 return onLogoutRequestNotAPost(request, response); 086 } 087 } 088 089 String redirectUrl = getRedirectUrl(request, response, subject); 090 //try/catch added for SHIRO-298: 091 try { 092 subject.logout(); 093 } catch (SessionException ise) { 094 LOGGER.debug("Encountered session exception during logout. This can generally safely be ignored.", ise); 095 } 096 issueRedirect(request, response, redirectUrl); 097 return false; 098 } 099 100 /** 101 * Returns the currently executing {@link Subject}. This implementation merely defaults to calling 102 * {@code SecurityUtils.}{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}, but can be overridden 103 * by subclasses for different retrieval strategies. 104 * 105 * @param request the incoming Servlet request 106 * @param response the outgoing Servlet response 107 * @return the currently executing {@link Subject}. 108 */ 109 protected Subject getSubject(ServletRequest request, ServletResponse response) { 110 return SecurityUtils.getSubject(); 111 } 112 113 /** 114 * Issues an HTTP redirect to the specified URL after subject logout. This implementation simply calls 115 * {@code WebUtils.}{@link WebUtils#issueRedirect(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String) 116 * issueRedirect(request,response,redirectUrl)}. 117 * 118 * @param request the incoming Servlet request 119 * @param response the outgoing Servlet response 120 * @param redirectUrl the URL to where the browser will be redirected immediately after Subject logout. 121 * @throws Exception if there is any error. 122 */ 123 protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl) throws Exception { 124 WebUtils.issueRedirect(request, response, redirectUrl); 125 } 126 127 /** 128 * Returns the redirect URL to send the user after logout. This default implementation ignores the arguments and 129 * returns the static configured {@link #getRedirectUrl() redirectUrl} property, but this method may be overridden 130 * by subclasses to dynamically construct the URL based on the request or subject if necessary. 131 * <p/> 132 * Note: the Subject is <em>not</em> yet logged out at the time this method is invoked. You may access the Subject's 133 * session if one is available and if necessary. 134 * <p/> 135 * Tip: if you need to access the Subject's session, consider using the 136 * {@code Subject.}{@link Subject#getSession(boolean) getSession(false)} method to ensure a new session 137 * isn't created unnecessarily. If a session was created, it will be immediately stopped after logout, 138 * not providing any value and unnecessarily taxing session infrastructure/resources. 139 * 140 * @param request the incoming Servlet request 141 * @param response the outgoing ServletResponse 142 * @param subject the not-yet-logged-out currently executing Subject 143 * @return the redirect URL to send the user after logout. 144 */ 145 protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject) { 146 return getRedirectUrl(); 147 } 148 149 /** 150 * Returns the URL to where the user will be redirected after logout. Default is the web application's context 151 * root, i.e. {@code "/"} 152 * 153 * @return the URL to where the user will be redirected after logout. 154 */ 155 public String getRedirectUrl() { 156 return redirectUrl; 157 } 158 159 /** 160 * Sets the URL to where the user will be redirected after logout. Default is the web application's context 161 * root, i.e. {@code "/"} 162 * 163 * @param redirectUrl the url to where the user will be redirected after logout 164 */ 165 @SuppressWarnings("unused") 166 public void setRedirectUrl(String redirectUrl) { 167 this.redirectUrl = redirectUrl; 168 } 169 170 171 /** 172 * This method is called when <code>postOnlyLogout</code> is <code>true</code>, and the request was NOT a <code>POST</code>. 173 * For example if this filter is bound to '/logout' and the caller makes a GET request, this method would be invoked. 174 * <p> 175 * The default implementation sets the response code to a 405, and sets the 'Allow' header to 'POST', and 176 * always returns false. 177 * </p> 178 * 179 * @return The return value indicates if the processing should continue in this filter chain. 180 */ 181 protected boolean onLogoutRequestNotAPost(ServletRequest request, ServletResponse response) { 182 183 HttpServletResponse httpServletResponse = WebUtils.toHttp(response); 184 httpServletResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); 185 httpServletResponse.setHeader("Allow", "POST"); 186 return false; 187 } 188 189 /** 190 * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example: 191 * out while typing in an address bar. If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause 192 * a logout to occur. 193 * 194 * @return Returns true if POST only logout is enabled 195 */ 196 public boolean isPostOnlyLogout() { 197 return postOnlyLogout; 198 } 199 200 /** 201 * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example: 202 * out while typing in an address bar. If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause 203 * a logout to occur. 204 * 205 * @param postOnlyLogout enable or disable POST only logout. 206 */ 207 public void setPostOnlyLogout(boolean postOnlyLogout) { 208 this.postOnlyLogout = postOnlyLogout; 209 } 210}