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 */
019package org.apache.shiro.authc;
020
021import org.apache.shiro.subject.PrincipalCollection;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025import java.util.ArrayList;
026import 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 */
043public 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 argument (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                if (log.isWarnEnabled())
216                    log.warn(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}