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 */
019 package org.apache.shiro.web.filter.authc;
020
021 import org.apache.shiro.authc.AuthenticationException;
022 import org.apache.shiro.authc.AuthenticationToken;
023 import org.apache.shiro.authc.UsernamePasswordToken;
024 import org.apache.shiro.subject.Subject;
025 import org.apache.shiro.web.util.WebUtils;
026 import org.slf4j.Logger;
027 import org.slf4j.LoggerFactory;
028
029 import javax.servlet.ServletRequest;
030 import javax.servlet.ServletResponse;
031 import 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 */
059 public 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 setFailureAttribute(request, e);
206 //login failed, let request continue back to the login page:
207 return true;
208 }
209
210 protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
211 String className = ae.getClass().getName();
212 request.setAttribute(getFailureKeyAttribute(), className);
213 }
214
215 protected String getUsername(ServletRequest request) {
216 return WebUtils.getCleanParam(request, getUsernameParam());
217 }
218
219 protected String getPassword(ServletRequest request) {
220 return WebUtils.getCleanParam(request, getPasswordParam());
221 }
222
223
224 }