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.authc;
020    
021    import org.apache.shiro.subject.PrincipalCollection;
022    import org.slf4j.Logger;
023    import org.slf4j.LoggerFactory;
024    
025    import java.util.ArrayList;
026    import java.util.Collection;
027    
028    
029    /**
030     * Superclass for almost all {@link Authenticator} implementations that performs the common work around authentication
031     * attempts.
032     * <p/>
033     * This class delegates the actual authentication attempt to subclasses but supports notification for
034     * successful and failed logins as well as logouts. Notification is sent to one or more registered
035     * {@link AuthenticationListener AuthenticationListener}s to allow for custom processing logic
036     * when these conditions occur.
037     * <p/>
038     * In most cases, the only thing a subclass needs to do (via its {@link #doAuthenticate} implementation)
039     * is perform the actual principal/credential verification process for the submitted {@code AuthenticationToken}.
040     *
041     * @since 0.1
042     */
043    public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
044    
045        /*-------------------------------------------
046        |             C O N S T A N T S             |
047        ============================================*/
048        /**
049         * Private class log instance.
050         */
051        private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticator.class);
052    
053        /*-------------------------------------------
054        |    I N S T A N C E   V A R I A B L E S    |
055        ============================================*/
056        /**
057         * Any registered listeners that wish to know about things during the authentication process.
058         */
059        private Collection<AuthenticationListener> listeners;
060    
061        /*-------------------------------------------
062        |         C O N S T R U C T O R S           |
063        ============================================*/
064    
065        /**
066         * Default no-argument constructor. Ensures the internal
067         * {@link AuthenticationListener AuthenticationListener} collection is a non-null {@code ArrayList}.
068         */
069        public AbstractAuthenticator() {
070            listeners = new ArrayList<AuthenticationListener>();
071        }
072    
073        /*--------------------------------------------
074        |  A C C E S S O R S / M O D I F I E R S    |
075        ============================================*/
076    
077        /**
078         * Sets the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
079         * attempts.
080         *
081         * @param listeners one or more {@code AuthenticationListener}s that should be notified due to an
082         *                  authentication attempt.
083         */
084        @SuppressWarnings({"UnusedDeclaration"})
085        public void setAuthenticationListeners(Collection<AuthenticationListener> listeners) {
086            if (listeners == null) {
087                this.listeners = new ArrayList<AuthenticationListener>();
088            } else {
089                this.listeners = listeners;
090            }
091        }
092    
093        /**
094         * Returns the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
095         * attempts.
096         *
097         * @return the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
098         *         attempts.
099         */
100        @SuppressWarnings({"UnusedDeclaration"})
101        public Collection<AuthenticationListener> getAuthenticationListeners() {
102            return this.listeners;
103        }
104    
105        /*-------------------------------------------
106        |               M E T H O D S               |
107        ============================================*/
108    
109        /**
110         * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
111         * authentication was successful for the specified {@code token} which resulted in the specified
112         * {@code info}.  This implementation merely iterates over the internal {@code listeners} collection and
113         * calls {@link AuthenticationListener#onSuccess(AuthenticationToken, AuthenticationInfo) onSuccess}
114         * for each.
115         *
116         * @param token the submitted {@code AuthenticationToken} that resulted in a successful authentication.
117         * @param info  the returned {@code AuthenticationInfo} resulting from the successful authentication.
118         */
119        protected void notifySuccess(AuthenticationToken token, AuthenticationInfo info) {
120            for (AuthenticationListener listener : this.listeners) {
121                listener.onSuccess(token, info);
122            }
123        }
124    
125        /**
126         * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
127         * authentication failed for the
128         * specified {@code token} which resulted in the specified {@code ae} exception.  This implementation merely
129         * iterates over the internal {@code listeners} collection and calls
130         * {@link AuthenticationListener#onFailure(AuthenticationToken, AuthenticationException) onFailure}
131         * for each.
132         *
133         * @param token the submitted {@code AuthenticationToken} that resulted in a failed authentication.
134         * @param ae    the resulting {@code AuthenticationException} that caused the authentication to fail.
135         */
136        protected void notifyFailure(AuthenticationToken token, AuthenticationException ae) {
137            for (AuthenticationListener listener : this.listeners) {
138                listener.onFailure(token, ae);
139            }
140        }
141    
142        /**
143         * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that a
144         * {@code Subject} has logged-out.  This implementation merely
145         * iterates over the internal {@code listeners} collection and calls
146         * {@link AuthenticationListener#onLogout(org.apache.shiro.subject.PrincipalCollection) onLogout}
147         * for each.
148         *
149         * @param principals the identifying principals of the {@code Subject}/account logging out.
150         */
151        protected void notifyLogout(PrincipalCollection principals) {
152            for (AuthenticationListener listener : this.listeners) {
153                listener.onLogout(principals);
154            }
155        }
156    
157        /**
158         * This implementation merely calls
159         * {@link #notifyLogout(org.apache.shiro.subject.PrincipalCollection) notifyLogout} to allow any registered listeners
160         * to react to the logout.
161         *
162         * @param principals the identifying principals of the {@code Subject}/account logging out.
163         */
164        public void onLogout(PrincipalCollection principals) {
165            notifyLogout(principals);
166        }
167    
168        /**
169         * Implementation of the {@link Authenticator} interface that functions in the following manner:
170         * <ol>
171         * <li>Calls template {@link #doAuthenticate doAuthenticate} method for subclass execution of the actual
172         * authentication behavior.</li>
173         * <li>If an {@code AuthenticationException} is thrown during {@code doAuthenticate},
174         * {@link #notifyFailure(AuthenticationToken, AuthenticationException) notify} any registered
175         * {@link AuthenticationListener AuthenticationListener}s of the exception and then propogate the exception
176         * for the caller to handle.</li>
177         * <li>If no exception is thrown (indicating a successful login),
178         * {@link #notifySuccess(AuthenticationToken, AuthenticationInfo) notify} any registered
179         * {@link AuthenticationListener AuthenticationListener}s of the successful attempt.</li>
180         * <li>Return the {@code AuthenticationInfo}</li>
181         * </ol>
182         *
183         * @param token the submitted token representing the subject's (user's) login principals and credentials.
184         * @return the AuthenticationInfo referencing the authenticated user's account data.
185         * @throws AuthenticationException if there is any problem during the authentication process - see the
186         *                                 interface's JavaDoc for a more detailed explanation.
187         */
188        public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
189    
190            if (token == null) {
191                throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
192            }
193    
194            log.trace("Authentication attempt received for token [{}]", token);
195    
196            AuthenticationInfo info;
197            try {
198                info = doAuthenticate(token);
199                if (info == null) {
200                    String msg = "No account information found for authentication token [" + token + "] by this " +
201                            "Authenticator instance.  Please check that it is configured correctly.";
202                    throw new AuthenticationException(msg);
203                }
204            } catch (Throwable t) {
205                AuthenticationException ae = null;
206                if (t instanceof AuthenticationException) {
207                    ae = (AuthenticationException) t;
208                }
209                if (ae == null) {
210                    //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
211                    //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
212                    String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
213                            "error? (Typical or expected login exceptions should extend from AuthenticationException).";
214                    ae = new AuthenticationException(msg, t);
215                }
216                try {
217                    notifyFailure(token, ae);
218                } catch (Throwable t2) {
219                    if (log.isWarnEnabled()) {
220                        String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
221                                "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
222                                "and propagating original AuthenticationException instead...";
223                        log.warn(msg, t2);
224                    }
225                }
226    
227    
228                throw ae;
229            }
230    
231            log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
232    
233            notifySuccess(token, info);
234    
235            return info;
236        }
237    
238        /**
239         * Template design pattern hook for subclasses to implement specific authentication behavior.
240         * <p/>
241         * Common behavior for most authentication attempts is encapsulated in the
242         * {@link #authenticate} method and that method invokes this one for custom behavior.
243         * <p/>
244         * <b>N.B.</b> Subclasses <em>should</em> throw some kind of
245         * {@code AuthenticationException} if there is a problem during
246         * authentication instead of returning {@code null}.  A {@code null} return value indicates
247         * a configuration or programming error, since {@code AuthenticationException}s should
248         * indicate any expected problem (such as an unknown account or username, or invalid password, etc).
249         *
250         * @param token the authentication token encapsulating the user's login information.
251         * @return an {@code AuthenticationInfo} object encapsulating the user's account information
252         *         important to Shiro.
253         * @throws AuthenticationException if there is a problem logging in the user.
254         */
255        protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token)
256                throws AuthenticationException;
257    
258    
259    }