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