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