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.authz.UnauthenticatedException;
025    import org.apache.shiro.subject.Subject;
026    
027    import javax.servlet.ServletException;
028    import javax.servlet.ServletRequest;
029    import javax.servlet.ServletResponse;
030    import java.io.IOException;
031    import java.util.Arrays;
032    
033    /**
034     * An <code>AuthenticationFilter</code> that is capable of automatically performing an authentication attempt
035     * based on the incoming request.
036     *
037     * @since 0.9
038     */
039    public abstract class AuthenticatingFilter extends AuthenticationFilter {
040        public static final String PERMISSIVE = "permissive";
041    
042        //TODO - complete JavaDoc
043    
044        protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
045            AuthenticationToken token = createToken(request, response);
046            if (token == null) {
047                String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
048                        "must be created in order to execute a login attempt.";
049                throw new IllegalStateException(msg);
050            }
051            try {
052                Subject subject = getSubject(request, response);
053                subject.login(token);
054                return onLoginSuccess(token, subject, request, response);
055            } catch (AuthenticationException e) {
056                return onLoginFailure(token, e, request, response);
057            }
058        }
059    
060        protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;
061    
062        protected AuthenticationToken createToken(String username, String password,
063                                                  ServletRequest request, ServletResponse response) {
064            boolean rememberMe = isRememberMe(request);
065            String host = getHost(request);
066            return createToken(username, password, rememberMe, host);
067        }
068    
069        protected AuthenticationToken createToken(String username, String password,
070                                                  boolean rememberMe, String host) {
071            return new UsernamePasswordToken(username, password, rememberMe, host);
072        }
073    
074        protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
075                                         ServletRequest request, ServletResponse response) throws Exception {
076            return true;
077        }
078    
079        protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
080                                         ServletRequest request, ServletResponse response) {
081            return false;
082        }
083    
084        /**
085         * Returns the host name or IP associated with the current subject.  This method is primarily provided for use
086         * during construction of an <code>AuthenticationToken</code>.
087         * <p/>
088         * The default implementation merely returns {@link ServletRequest#getRemoteHost()}.
089         *
090         * @param request the incoming ServletRequest
091         * @return the <code>InetAddress</code> to associate with the login attempt.
092         */
093        protected String getHost(ServletRequest request) {
094            return request.getRemoteHost();
095        }
096    
097        /**
098         * Returns <code>true</code> if &quot;rememberMe&quot; should be enabled for the login attempt associated with the
099         * current <code>request</code>, <code>false</code> otherwise.
100         * <p/>
101         * This implementation always returns <code>false</code> and is provided as a template hook to subclasses that
102         * support <code>rememberMe</code> logins and wish to determine <code>rememberMe</code> in a custom mannner
103         * based on the current <code>request</code>.
104         *
105         * @param request the incoming ServletRequest
106         * @return <code>true</code> if &quot;rememberMe&quot; should be enabled for the login attempt associated with the
107         *         current <code>request</code>, <code>false</code> otherwise.
108         */
109        protected boolean isRememberMe(ServletRequest request) {
110            return false;
111        }
112    
113        /**
114         * Determines whether the current subject should be allowed to make the current request.
115         * <p/>
116         * The default implementation returns <code>true</code> if the user is authenticated.  Will also return
117         * <code>true</code> if the {@link #isLoginRequest} returns false and the &quot;permissive&quot; flag is set.
118         *
119         * @return <code>true</code> if request should be allowed access
120         */
121        @Override
122        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
123            return super.isAccessAllowed(request, response, mappedValue) ||
124                    (!isLoginRequest(request, response) && isPermissive(mappedValue));
125        }
126    
127        /**
128         * Returns <code>true</code> if the mappedValue contains the {@link #PERMISSIVE} qualifier.
129         *
130         * @return <code>true</code> if this filter should be permissive
131         */
132        protected boolean isPermissive(Object mappedValue) {
133            if(mappedValue != null) {
134                String[] values = (String[]) mappedValue;
135                return Arrays.binarySearch(values, PERMISSIVE) >= 0;
136            }
137            return false;
138        }
139    
140        /**
141         * Overrides the default behavior to call {@link #onAccessDenied} and swallow the exception if the exception is
142         * {@link UnauthenticatedException}.
143         */
144        @Override
145        protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {
146            if (existing instanceof UnauthenticatedException || (existing instanceof ServletException && existing.getCause() instanceof UnauthenticatedException))
147            {
148                try {
149                    onAccessDenied(request, response);
150                    existing = null;
151                } catch (Exception e) {
152                    existing = e;
153                }
154            }
155            super.cleanup(request, response, existing);
156    
157        }
158    }