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 log = 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 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 }