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 module hunt.shiro.authc.AbstractAuthenticator; 20 21 import hunt.shiro.authc.Authenticator; 22 import hunt.shiro.authc.AuthenticationInfo; 23 import hunt.shiro.authc.AuthenticationListener; 24 import hunt.shiro.authc.AuthenticationToken; 25 import hunt.shiro.authc.LogoutAware; 26 27 import hunt.shiro.Exceptions; 28 import hunt.shiro.subject.PrincipalCollection; 29 30 import hunt.Exceptions; 31 import hunt.logging.Logger; 32 33 34 /** 35 * Superclass for almost all {@link Authenticator} implementations that performs the common work around authentication 36 * attempts. 37 * <p/> 38 * This class delegates the actual authentication attempt to subclasses but supports notification for 39 * successful and failed logins as well as logouts. Notification is sent to one or more registered 40 * {@link AuthenticationListener AuthenticationListener}s to allow for custom processing logic 41 * when these conditions occur. 42 * <p/> 43 * In most cases, the only thing a subclass needs to do (via its {@link #doAuthenticate} implementation) 44 * is perform the actual principal/credential verification process for the submitted {@code AuthenticationToken}. 45 * 46 */ 47 abstract class AbstractAuthenticator : Authenticator, LogoutAware { 48 49 /*------------------------------------------- 50 | C O N S T A N T S | 51 ============================================*/ 52 /** 53 * Private class log instance. 54 */ 55 56 57 /*------------------------------------------- 58 | I N S T A N C E V A R I A B L E S | 59 ============================================*/ 60 /** 61 * Any registered listeners that wish to know about things during the authentication process. 62 */ 63 private AuthenticationListener[] listeners; 64 65 /*------------------------------------------- 66 | C O N S T R U C T O R S | 67 ============================================*/ 68 69 /** 70 * Default no-argument constructor. Ensures the internal 71 * {@link AuthenticationListener AuthenticationListener} collection is a non-null {@code ArrayList}. 72 */ 73 this() { 74 } 75 76 /*-------------------------------------------- 77 | A C C E S S O R S / M O D I F I E R S | 78 ============================================*/ 79 80 /** 81 * Sets the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication 82 * attempts. 83 * 84 * @param listeners one or more {@code AuthenticationListener}s that should be notified due to an 85 * authentication attempt. 86 */ 87 //@SuppressWarnings({"UnusedDeclaration"}) 88 void setAuthenticationListeners(AuthenticationListener[] listeners) { 89 this.listeners = listeners; 90 } 91 92 /** 93 * Returns the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication 94 * attempts. 95 * 96 * @return the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication 97 * attempts. 98 */ 99 //@SuppressWarnings({"UnusedDeclaration"}) 100 AuthenticationListener[] getAuthenticationListeners() { 101 return this.listeners; 102 } 103 104 /*------------------------------------------- 105 | M E T H O D S | 106 ============================================*/ 107 108 /** 109 * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that 110 * authentication was successful for the specified {@code token} which resulted in the specified 111 * {@code info}. This implementation merely iterates over the internal {@code listeners} collection and 112 * calls {@link AuthenticationListener#onSuccess(AuthenticationToken, AuthenticationInfo) onSuccess} 113 * for each. 114 * 115 * @param token the submitted {@code AuthenticationToken} that resulted in a successful authentication. 116 * @param info the returned {@code AuthenticationInfo} resulting from the successful authentication. 117 */ 118 protected void notifySuccess(AuthenticationToken token, AuthenticationInfo info) { 119 foreach(AuthenticationListener listener ; this.listeners) { 120 if(listener !is null) 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 foreach(AuthenticationListener listener ; this.listeners) { 138 if(listener !is null) 139 listener.onFailure(token, ae); 140 } 141 } 142 143 /** 144 * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that a 145 * {@code Subject} has logged-out. This implementation merely 146 * iterates over the internal {@code listeners} collection and calls 147 * {@link AuthenticationListener#onLogout(hunt.shiro.subject.PrincipalCollection) onLogout} 148 * for each. 149 * 150 * @param principals the identifying principals of the {@code Subject}/account logging out. 151 */ 152 protected void notifyLogout(PrincipalCollection principals) { 153 foreach(AuthenticationListener listener ; this.listeners) { 154 if(listener !is null) 155 listener.onLogout(principals); 156 } 157 } 158 159 /** 160 * This implementation merely calls 161 * {@link #notifyLogout(hunt.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 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 propagate 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 final AuthenticationInfo authenticate(AuthenticationToken token){ 191 192 if (token is null) { 193 throw new IllegalArgumentException("Method argument (authentication token) cannot be null."); 194 } 195 196 version(HUNT_SHIRO_DEBUG) tracef("Authentication attempt received for token [%s]", token); 197 198 AuthenticationInfo info; 199 try { 200 info = doAuthenticate(token); 201 if (info is null) { 202 string msg = "No account information found for authentication token [" ~ 203 (cast(Object)token).toString() ~ "] by this " ~ 204 "Authenticator instance. Please check that it is configured correctly."; 205 throw new AuthenticationException(msg); 206 } 207 } catch (Throwable t) { 208 warning(t.msg); 209 version(HUNT_DEBUG) warning(t); 210 AuthenticationException ae = null; 211 auto tCast = cast(AuthenticationException)t; 212 if (tCast !is null) { 213 ae = tCast; 214 } 215 if (ae is null) { 216 //Exception thrown was not an expected AuthenticationException. Therefore it is probably a little more 217 //severe or unexpected. So, wrap in an AuthenticationException, log to warn, and propagate: 218 string msg = "Authentication failed for token submission [" ~ 219 (cast(Object)token).toString() ~ "]. Possible unexpected " ~ 220 "error? (Typical or expected login exceptions should extend from AuthenticationException)."; 221 ae = new AuthenticationException(msg, t); 222 } 223 try { 224 notifyFailure(token, ae); 225 } catch (Throwable t2) { 226 version(HUNT_DEBUG) { 227 string msg = "Unable to send notification for failed authentication attempt - listener error?. " ~ 228 "Please check your AuthenticationListener implementation(s). Logging sending exception " ~ 229 "and propagating original AuthenticationException instead..."; 230 warning(msg, t2); 231 } 232 } 233 234 235 throw ae; 236 } 237 238 version(HUNT_SHIRO_DEBUG) { 239 infof("Authentication successful for token [%s]. Returned account [%s]", 240 token, info); 241 } 242 243 notifySuccess(token, info); 244 245 return info; 246 } 247 248 /** 249 * Template design pattern hook for subclasses to implement specific authentication behavior. 250 * <p/> 251 * Common behavior for most authentication attempts is encapsulated in the 252 * {@link #authenticate} method and that method invokes this one for custom behavior. 253 * <p/> 254 * <b>N.B.</b> Subclasses <em>should</em> throw some kind of 255 * {@code AuthenticationException} if there is a problem during 256 * authentication instead of returning {@code null}. A {@code null} return value indicates 257 * a configuration or programming error, since {@code AuthenticationException}s should 258 * indicate any expected problem (such as an unknown account or username, or invalid password, etc). 259 * 260 * @param token the authentication token encapsulating the user's login information. 261 * @return an {@code AuthenticationInfo} object encapsulating the user's account information 262 * important to Shiro. 263 * @throws AuthenticationException if there is a problem logging in the user. 264 */ 265 protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token); 266 267 268 }