View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.web.filter.authc;
20  
21  import org.apache.shiro.authc.AuthenticationException;
22  import org.apache.shiro.authc.AuthenticationToken;
23  import org.apache.shiro.authc.UsernamePasswordToken;
24  import org.apache.shiro.subject.Subject;
25  import org.apache.shiro.web.util.WebUtils;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  import javax.servlet.ServletRequest;
30  import javax.servlet.ServletResponse;
31  import javax.servlet.http.HttpServletRequest;
32  
33  
34  /**
35   * Requires the requesting user to be authenticated for the request to continue, and if they are not, forces the user
36   * to login via by redirecting them to the {@link #setLoginUrl(String) loginUrl} you configure.
37   * <p/>
38   * <p>This filter constructs a {@link UsernamePasswordToken UsernamePasswordToken} with the values found in
39   * {@link #setUsernameParam(String) username}, {@link #setPasswordParam(String) password},
40   * and {@link #setRememberMeParam(String) rememberMe} request parameters.  It then calls
41   * {@link org.apache.shiro.subject.Subject#login(org.apache.shiro.authc.AuthenticationToken) Subject.login(usernamePasswordToken)},
42   * effectively automatically performing a login attempt.  Note that the login attempt will only occur when the
43   * {@link #isLoginSubmission(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isLoginSubmission(request,response)}
44   * is <code>true</code>, which by default occurs when the request is for the {@link #setLoginUrl(String) loginUrl} and
45   * is a POST request.
46   * <p/>
47   * <p>If the login attempt fails, the resulting <code>AuthenticationException</code> fully qualified class name will
48   * be set as a request attribute under the {@link #setFailureKeyAttribute(String) failureKeyAttribute} key.  This
49   * FQCN can be used as an i18n key or lookup mechanism to explain to the user why their login attempt failed
50   * (e.g. no account, incorrect password, etc).
51   * <p/>
52   * <p>If you would prefer to handle the authentication validation and login in your own code, consider using the
53   * {@link PassThruAuthenticationFilter} instead, which allows requests to the
54   * {@link #loginUrl} to pass through to your application's code directly.
55   *
56   * @see PassThruAuthenticationFilter
57   * @since 0.9
58   */
59  public class FormAuthenticationFilter extends AuthenticatingFilter {
60  
61      //TODO - complete JavaDoc
62  
63      public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure";
64  
65      public static final String DEFAULT_USERNAME_PARAM = "username";
66      public static final String DEFAULT_PASSWORD_PARAM = "password";
67      public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";
68  
69      private static final Logger log = LoggerFactory.getLogger(FormAuthenticationFilter.class);
70  
71      private String usernameParam = DEFAULT_USERNAME_PARAM;
72      private String passwordParam = DEFAULT_PASSWORD_PARAM;
73      private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;
74  
75      private String failureKeyAttribute = DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;
76  
77      public FormAuthenticationFilter() {
78          setLoginUrl(DEFAULT_LOGIN_URL);
79      }
80  
81      @Override
82      public void setLoginUrl(String loginUrl) {
83          String previous = getLoginUrl();
84          if (previous != null) {
85              this.appliedPaths.remove(previous);
86          }
87          super.setLoginUrl(loginUrl);
88          if (log.isTraceEnabled()) {
89              log.trace("Adding login url to applied paths.");
90          }
91          this.appliedPaths.put(getLoginUrl(), null);
92      }
93  
94      public String getUsernameParam() {
95          return usernameParam;
96      }
97  
98      /**
99       * 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 }