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