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