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