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    }