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.authc.AuthenticationException; 022import org.apache.shiro.authc.AuthenticationToken; 023import org.apache.shiro.authc.UsernamePasswordToken; 024import org.apache.shiro.subject.Subject; 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.HttpServletRequest; 032 033/** 034 * Requires the requesting user to be authenticated for the request to continue, and if they are not, forces the user 035 * to login via by redirecting them to the {@link #setLoginUrl(String) loginUrl} you configure. 036 * <p/> 037 * <p>This filter constructs a {@link UsernamePasswordToken UsernamePasswordToken} with the values found in 038 * {@link #setUsernameParam(String) username}, {@link #setPasswordParam(String) password}, 039 * and {@link #setRememberMeParam(String) rememberMe} request parameters. It then calls 040 * {@link Subject#login(AuthenticationToken) Subject.login(usernamePasswordToken)}, 041 * effectively automatically performing a login attempt. Note that the login attempt will only occur when the 042 * {@link #isLoginSubmission(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isLoginSubmission(request,response)} 043 * is <code>true</code>, which by default occurs when the request is for the {@link #setLoginUrl(String) loginUrl} and 044 * is a POST request. 045 * <p/> 046 * <p>If the login attempt fails, the resulting <code>AuthenticationException</code> fully qualified class name will 047 * be set as a request attribute under the {@link #setFailureKeyAttribute(String) failureKeyAttribute} key. This 048 * FQCN can be used as an i18n key or lookup mechanism to explain to the user why their login attempt failed 049 * (e.g. no account, incorrect password, etc.). 050 * <p/> 051 * <p>If you would prefer to handle the authentication validation and login in your own code, consider using the 052 * {@link PassThruAuthenticationFilter} instead, which allows requests to the 053 * {@link #setLoginUrl(String) loginUrl} to pass through to your application's code directly. 054 * 055 * @see PassThruAuthenticationFilter 056 * @since 0.9 057 */ 058public class FormAuthenticationFilter extends AuthenticatingFilter { 059 060 /** 061 * default error key attribute name. 062 */ 063 public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure"; 064 065 /** 066 * username param. 067 */ 068 public static final String DEFAULT_USERNAME_PARAM = "username"; 069 070 /** 071 * password param. 072 */ 073 public static final String DEFAULT_PASSWORD_PARAM = "password"; 074 075 /** 076 * rememberMe param. 077 */ 078 public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe"; 079 080 private static final Logger LOGGER = LoggerFactory.getLogger(FormAuthenticationFilter.class); 081 082 private String usernameParam = DEFAULT_USERNAME_PARAM; 083 private String passwordParam = DEFAULT_PASSWORD_PARAM; 084 private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM; 085 086 private String failureKeyAttribute = DEFAULT_ERROR_KEY_ATTRIBUTE_NAME; 087 088 public FormAuthenticationFilter() { 089 setLoginUrl(DEFAULT_LOGIN_URL); 090 } 091 092 @Override 093 public void setLoginUrl(String loginUrl) { 094 String previous = getLoginUrl(); 095 if (previous != null) { 096 this.appliedPaths.remove(previous); 097 } 098 super.setLoginUrl(loginUrl); 099 if (LOGGER.isTraceEnabled()) { 100 LOGGER.trace("Adding login url to applied paths."); 101 } 102 this.appliedPaths.put(getLoginUrl(), null); 103 } 104 105 public String getUsernameParam() { 106 return usernameParam; 107 } 108 109 /** 110 * Sets the request parameter name to look for when acquiring the username. Unless overridden by calling this 111 * method, the default is <code>username</code>. 112 * 113 * @param usernameParam the name of the request param to check for acquiring the username. 114 */ 115 public void setUsernameParam(String usernameParam) { 116 this.usernameParam = usernameParam; 117 } 118 119 public String getPasswordParam() { 120 return passwordParam; 121 } 122 123 /** 124 * Sets the request parameter name to look for when acquiring the password. Unless overridden by calling this 125 * method, the default is <code>password</code>. 126 * 127 * @param passwordParam the name of the request param to check for acquiring the password. 128 */ 129 public void setPasswordParam(String passwordParam) { 130 this.passwordParam = passwordParam; 131 } 132 133 public String getRememberMeParam() { 134 return rememberMeParam; 135 } 136 137 /** 138 * Sets the request parameter name to look for when acquiring the rememberMe boolean value. Unless overridden 139 * by calling this method, the default is <code>rememberMe</code>. 140 * <p/> 141 * RememberMe will be <code>true</code> if the parameter value equals any of those supported by 142 * {@link WebUtils#isTrue(ServletRequest, String) WebUtils.isTrue(request,value)}, <code>false</code> 143 * otherwise. 144 * 145 * @param rememberMeParam the name of the request param to check for acquiring the rememberMe boolean value. 146 */ 147 public void setRememberMeParam(String rememberMeParam) { 148 this.rememberMeParam = rememberMeParam; 149 } 150 151 public String getFailureKeyAttribute() { 152 return failureKeyAttribute; 153 } 154 155 public void setFailureKeyAttribute(String failureKeyAttribute) { 156 this.failureKeyAttribute = failureKeyAttribute; 157 } 158 159 protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { 160 if (isLoginRequest(request, response)) { 161 if (isLoginSubmission(request, response)) { 162 if (LOGGER.isTraceEnabled()) { 163 LOGGER.trace("Login submission detected. Attempting to execute login."); 164 } 165 return executeLogin(request, response); 166 } else { 167 if (LOGGER.isTraceEnabled()) { 168 LOGGER.trace("Login page view."); 169 } 170 //allow them to see the login page ;) 171 return true; 172 } 173 } else { 174 if (LOGGER.isTraceEnabled()) { 175 LOGGER.trace("Attempting to access a path which requires authentication. Forwarding to the " 176 + "Authentication url [" + getLoginUrl() + "]"); 177 } 178 saveRequestAndRedirectToLogin(request, response); 179 return false; 180 } 181 } 182 183 /** 184 * This default implementation merely returns <code>true</code> if the request is an HTTP <code>POST</code>, 185 * <code>false</code> otherwise. Can be overridden by subclasses for custom login submission detection behavior. 186 * 187 * @param request the incoming ServletRequest 188 * @param response the outgoing ServletResponse. 189 * @return <code>true</code> if the request is an HTTP <code>POST</code>, <code>false</code> otherwise. 190 */ 191 @SuppressWarnings({"UnusedDeclaration"}) 192 protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) { 193 return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD); 194 } 195 196 protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { 197 String username = getUsername(request); 198 String password = getPassword(request); 199 return createToken(username, password, request, response); 200 } 201 202 protected boolean isRememberMe(ServletRequest request) { 203 return WebUtils.isTrue(request, getRememberMeParam()); 204 } 205 206 protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, 207 ServletRequest request, ServletResponse response) throws Exception { 208 issueSuccessRedirect(request, response); 209 //we handled the success redirect directly, prevent the chain from continuing: 210 return false; 211 } 212 213 protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, 214 ServletRequest request, ServletResponse response) { 215 if (LOGGER.isDebugEnabled()) { 216 LOGGER.debug("Authentication exception", e); 217 } 218 setFailureAttribute(request, e); 219 //login failed, let request continue back to the login page: 220 return true; 221 } 222 223 protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) { 224 String className = ae.getClass().getName(); 225 request.setAttribute(getFailureKeyAttribute(), className); 226 } 227 228 protected String getUsername(ServletRequest request) { 229 return WebUtils.getCleanParam(request, getUsernameParam()); 230 } 231 232 protected String getPassword(ServletRequest request) { 233 return WebUtils.getCleanParam(request, getPasswordParam()); 234 } 235 236 237}