1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.shiro.authc;
20
21 import org.apache.shiro.subject.PrincipalCollection;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import java.util.ArrayList;
26 import java.util.Collection;
27
28
29 /**
30 * Superclass for almost all {@link Authenticator} implementations that performs the common work around authentication
31 * attempts.
32 * <p/>
33 * This class delegates the actual authentication attempt to subclasses but supports notification for
34 * successful and failed logins as well as logouts. Notification is sent to one or more registered
35 * {@link AuthenticationListener AuthenticationListener}s to allow for custom processing logic
36 * when these conditions occur.
37 * <p/>
38 * In most cases, the only thing a subclass needs to do (via its {@link #doAuthenticate} implementation)
39 * is perform the actual principal/credential verification process for the submitted {@code AuthenticationToken}.
40 *
41 * @since 0.1
42 */
43 public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
44
45 /*-------------------------------------------
46 | C O N S T A N T S |
47 ============================================*/
48 /**
49 * Private class log instance.
50 */
51 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAuthenticator.class);
52
53 /*-------------------------------------------
54 | I N S T A N C E V A R I A B L E S |
55 ============================================*/
56 /**
57 * Any registered listeners that wish to know about things during the authentication process.
58 */
59 private Collection<AuthenticationListener> listeners;
60
61 /*-------------------------------------------
62 | C O N S T R U C T O R S |
63 ============================================*/
64
65 /**
66 * Default no-argument constructor. Ensures the internal
67 * {@link AuthenticationListener AuthenticationListener} collection is a non-null {@code ArrayList}.
68 */
69 public AbstractAuthenticator() {
70 listeners = new ArrayList<AuthenticationListener>();
71 }
72
73 /*--------------------------------------------
74 | A C C E S S O R S / M O D I F I E R S |
75 ============================================*/
76
77 /**
78 * Sets the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
79 * attempts.
80 *
81 * @param listeners one or more {@code AuthenticationListener}s that should be notified due to an
82 * authentication attempt.
83 */
84 @SuppressWarnings({"UnusedDeclaration"})
85 public void setAuthenticationListeners(Collection<AuthenticationListener> listeners) {
86 if (listeners == null) {
87 this.listeners = new ArrayList<AuthenticationListener>();
88 } else {
89 this.listeners = listeners;
90 }
91 }
92
93 /**
94 * Returns the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
95 * attempts.
96 *
97 * @return the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
98 * attempts.
99 */
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 propagate 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 LOGGER.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 (LOGGER.isWarnEnabled()) {
216 LOGGER.warn(msg, t);
217 }
218 }
219 try {
220 notifyFailure(token, ae);
221 } catch (Throwable t2) {
222 if (LOGGER.isWarnEnabled()) {
223 String msg = "Unable to send notification for failed authentication attempt - listener error?. "
224 + "Please check your AuthenticationListener implementation(s). Logging sending exception "
225 + "and propagating original AuthenticationException instead...";
226 LOGGER.warn(msg, t2);
227 }
228 }
229
230
231 throw ae;
232 }
233
234 LOGGER.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
235
236 notifySuccess(token, info);
237
238 return info;
239 }
240
241 /**
242 * Template design pattern hook for subclasses to implement specific authentication behavior.
243 * <p/>
244 * Common behavior for most authentication attempts is encapsulated in the
245 * {@link #authenticate} method and that method invokes this one for custom behavior.
246 * <p/>
247 * <b>N.B.</b> Subclasses <em>should</em> throw some kind of
248 * {@code AuthenticationException} if there is a problem during
249 * authentication instead of returning {@code null}. A {@code null} return value indicates
250 * a configuration or programming error, since {@code AuthenticationException}s should
251 * indicate any expected problem (such as an unknown account or username, or invalid password, etc.).
252 *
253 * @param token the authentication token encapsulating the user's login information.
254 * @return an {@code AuthenticationInfo} object encapsulating the user's account information
255 * important to Shiro.
256 * @throws AuthenticationException if there is a problem logging in the user.
257 */
258 protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token)
259 throws AuthenticationException;
260
261
262 }