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    }