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.realm.AuthenticatingRealm; 20 21 import hunt.shiro.realm.CachingRealm; 22 23 import hunt.shiro.Exceptions; 24 import hunt.shiro.authc.AuthenticationInfo; 25 import hunt.shiro.authc.AuthenticationToken; 26 27 import hunt.shiro.authc.UsernamePasswordToken; 28 import hunt.shiro.authc.credential.AllowAllCredentialsMatcher; 29 import hunt.shiro.authc.credential.CredentialsMatcher; 30 import hunt.shiro.authc.credential.SimpleCredentialsMatcher; 31 import hunt.shiro.cache.Cache; 32 import hunt.shiro.cache.CacheManager; 33 import hunt.shiro.subject.PrincipalCollection; 34 import hunt.shiro.util.Common; 35 36 import hunt.Exceptions; 37 import hunt.logging.ConsoleLogger; 38 import hunt.String; 39 40 import core.atomic; 41 import std.conv; 42 import std.string; 43 44 45 /** 46 * A top-level abstract implementation of the <tt>Realm</tt> interface that only implements authentication support 47 * (log-in) operations and leaves authorization (access control) behavior to subclasses. 48 * <h2>Authentication Caching</h2> 49 * For applications that perform frequent repeated authentication of the same accounts (e.g. as is often done in 50 * REST or Soap applications that authenticate on every request), it might be prudent to enable authentication 51 * caching to alleviate constant load on any back-end data sources. 52 * <p/> 53 * This feature is disabled by default to retain backwards-compatibility with Shiro 1.1 and earlier. It may be 54 * enabled by setting {@link #setAuthenticationCachingEnabled(bool) authenticationCachingEnabled} = {@code true} 55 * (and configuring Shiro with a {@link CacheManager} of course), but <b>NOTE:</b> 56 * <p/> 57 * <b>ONLY enable authentication caching if either of the following is true for your realm implementation:</b> 58 * <ul> 59 * <li>The {@link #doGetAuthenticationInfo(hunt.shiro.authc.AuthenticationToken) doGetAuthenticationInfo} 60 * implementation returns {@code AuthenticationInfo} instances where the 61 * {@link hunt.shiro.authc.AuthenticationInfo#getCredentials() credentials} are securely obfuscated and NOT 62 * plaintext (raw) credentials. For example, 63 * if your realm references accounts with passwords, that the {@code AuthenticationInfo}'s 64 * {@link hunt.shiro.authc.AuthenticationInfo#getCredentials() credentials} are safely hashed and salted or otherwise 65 * fully encrypted.<br/><br/></li> 66 * <li>The {@link #doGetAuthenticationInfo(hunt.shiro.authc.AuthenticationToken) doGetAuthenticationInfo} 67 * implementation returns {@code AuthenticationInfo} instances where the 68 * {@link hunt.shiro.authc.AuthenticationInfo#getCredentials() credentials} are plaintext (raw) <b>AND</b> the 69 * cache region storing the {@code AuthenticationInfo} instances WILL NOT overflow to disk and WILL NOT transmit cache 70 * entries over an unprotected (non TLS/SSL) network (as might be the case with a networked/distributed enterprise cache). 71 * This should be the case even in private/trusted/corporate networks.</li> 72 * </ul> 73 * <p/> 74 * These points are very important because if authentication caching is enabled, this abstract class implementation 75 * will place AuthenticationInfo instances returned from the subclass implementations directly into the cache, for 76 * example: 77 * <pre> 78 * cache.put(cacheKey, subclassAuthenticationInfoInstance); 79 * </pre> 80 * <p/> 81 * Enabling authentication caching is ONLY safe to do if the above two scenarios apply. It is NOT safe to enable under 82 * any other scenario. 83 * <p/> 84 * When possible, always represent and store credentials in a safe form (hash+salt or encrypted) to eliminate plaintext 85 * visibility. 86 * <h3>Authentication Cache Invalidation on Logout</h3> 87 * If authentication caching is enabled, this implementation will attempt to evict (remove) cached authentication data 88 * for an account during logout. This can only occur if the 89 * {@link #getAuthenticationCacheKey(hunt.shiro.authc.AuthenticationToken)} and 90 * {@link #getAuthenticationCacheKey(hunt.shiro.subject.PrincipalCollection)} methods return the exact same value. 91 * <p/> 92 * The default implementations of these methods expect that the 93 * {@link hunt.shiro.authc.AuthenticationToken#getPrincipal()} (what the user submits during login) and 94 * {@link #getAvailablePrincipal(hunt.shiro.subject.PrincipalCollection) getAvailablePrincipal} (what is returned 95 * by the realm after account lookup) return 96 * the same exact value. For example, the user submitted username is also the primary account identifier. 97 * <p/> 98 * However, if your application uses, say, a username for end-user login, but returns a primary key ID as the 99 * primary principal after authentication, then you will need to override either 100 * {@link #getAuthenticationCacheKey(hunt.shiro.authc.AuthenticationToken) getAuthenticationCacheKey(token)} or 101 * {@link #getAuthenticationCacheKey(hunt.shiro.subject.PrincipalCollection) getAuthenticationCacheKey(principals)} 102 * (or both) to ensure that the same cache key can be used for either object. 103 * <p/> 104 * This guarantees that the same cache key used to cache the data during authentication (derived from the 105 * {@code AuthenticationToken}) will be used to remove the cached data during logout (derived from the 106 * {@code PrincipalCollection}). 107 * <h4>Unmatching Cache Key Values</h4> 108 * If the return values from {@link #getAuthenticationCacheKey(hunt.shiro.authc.AuthenticationToken)} and 109 * {@link #getAuthenticationCacheKey(hunt.shiro.subject.PrincipalCollection)} are not identical, cached 110 * authentication data removal is at the mercy of your cache provider settings. For example, often cache 111 * implementations will evict cache entries based on a timeToIdle or timeToLive (TTL) value. 112 * <p/> 113 * If this lazy eviction capability of the cache product is not sufficient and you want discrete behavior 114 * (highly recommended for authentication data), ensure that the return values from those two methods are identical in 115 * the subclass implementation. 116 * 117 */ 118 abstract class AuthenticatingRealm : CachingRealm, Initializable { 119 120 //TODO - complete JavaDoc 121 122 private shared static int INSTANCE_COUNT = 0; 123 124 /** 125 * The default suffix appended to the realm name used for caching authentication data. 126 * 127 */ 128 private enum string DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authenticationCache"; 129 130 /** 131 * Credentials matcher used to determine if the provided credentials match the credentials stored in the data store. 132 */ 133 private CredentialsMatcher credentialsMatcher; 134 135 136 private Cache!(Object, AuthenticationInfo) authenticationCache; 137 138 private bool authenticationCachingEnabled; 139 private string authenticationCacheName; 140 141 /** 142 * The class that this realm supports for authentication tokens. This is used by the 143 * default implementation of the {@link Realm#supports(hunt.shiro.authc.AuthenticationToken)} method to 144 * determine whether or not the given authentication token is supported by this realm. 145 */ 146 private TypeInfo_Class authenticationTokenClass; 147 148 /*------------------------------------------- 149 | C O N S T R U C T O R S | 150 ============================================*/ 151 this() { 152 this(null, new SimpleCredentialsMatcher()); 153 } 154 155 this(CacheManager cacheManager) { 156 this(cacheManager, new SimpleCredentialsMatcher()); 157 } 158 159 this(CredentialsMatcher matcher) { 160 this(null, matcher); 161 } 162 163 this(CacheManager cacheManager, CredentialsMatcher matcher) { 164 authenticationTokenClass = UsernamePasswordToken.classinfo; 165 166 //retain backwards compatibility for Shiro 1.1 and earlier. Setting to true by default will probably cause 167 //unexpected results for existing applications: 168 this.authenticationCachingEnabled = false; 169 170 171 int instanceNumber = atomicOp!("+=")(INSTANCE_COUNT, 1); 172 instanceNumber--; 173 174 this.authenticationCacheName = typeid(this).name ~ DEFAULT_AUTHORIZATION_CACHE_SUFFIX; 175 if (instanceNumber > 0) { 176 this.authenticationCacheName = this.authenticationCacheName ~ "." ~ instanceNumber.to!string(); 177 } 178 179 if (cacheManager !is null) { 180 setCacheManager(cacheManager); 181 } 182 if (matcher !is null) { 183 setCredentialsMatcher(matcher); 184 } 185 } 186 187 /*-------------------------------------------- 188 | A C C E S S O R S / M O D I F I E R S | 189 ============================================*/ 190 191 /** 192 * Returns the <code>CredentialsMatcher</code> used during an authentication attempt to verify submitted 193 * credentials with those stored in the system. 194 * <p/> 195 * <p>Unless overridden by the {@link #setCredentialsMatcher setCredentialsMatcher} method, the default 196 * value is a {@link hunt.shiro.authc.credential.SimpleCredentialsMatcher SimpleCredentialsMatcher} instance. 197 * 198 * @return the <code>CredentialsMatcher</code> used during an authentication attempt to verify submitted 199 * credentials with those stored in the system. 200 */ 201 CredentialsMatcher getCredentialsMatcher() { 202 return credentialsMatcher; 203 } 204 205 /** 206 * Sets the CrendialsMatcher used during an authentication attempt to verify submitted credentials with those 207 * stored in the system. The implementation of this matcher can be switched via configuration to 208 * support any number of schemes, including plain text comparisons, hashing comparisons, and others. 209 * <p/> 210 * <p>Unless overridden by this method, the default value is a 211 * {@link hunt.shiro.authc.credential.SimpleCredentialsMatcher} instance. 212 * 213 * @param credentialsMatcher the matcher to use. 214 */ 215 void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) { 216 this.credentialsMatcher = credentialsMatcher; 217 } 218 219 /** 220 * Returns the authenticationToken class supported by this realm. 221 * <p/> 222 * <p>The default value is <tt>{@link hunt.shiro.authc.UsernamePasswordToken UsernamePasswordToken.class}</tt>, since 223 * about 90% of realms use username/password authentication, regardless of their protocol (e.g. over jdbc, ldap, 224 * kerberos, http, etc). 225 * <p/> 226 * <p>If subclasses haven't already overridden the {@link Realm#supports Realm.supports(AuthenticationToken)} method, 227 * they must {@link #setAuthenticationTokenClass(Class) set a new class} if they won't support 228 * <tt>UsernamePasswordToken</tt> authentication token submissions. 229 * 230 * @return the authenticationToken class supported by this realm. 231 * @see #setAuthenticationTokenClass 232 */ 233 TypeInfo_Class getAuthenticationTokenClass() { 234 return authenticationTokenClass; 235 } 236 237 /** 238 * Sets the authenticationToken class supported by this realm. 239 * <p/> 240 * <p>Unless overridden by this method, the default value is 241 * {@link hunt.shiro.authc.UsernamePasswordToken UsernamePasswordToken.class} to support the majority of applications. 242 * 243 * @param authenticationTokenClass the class of authentication token instances supported by this realm. 244 * @see #getAuthenticationTokenClass getAuthenticationTokenClass() for more explanation. 245 */ 246 void setAuthenticationTokenClass(TypeInfo_Class authenticationTokenClass) { 247 this.authenticationTokenClass = authenticationTokenClass; 248 } 249 250 /** 251 * Sets an explicit {@link Cache} instance to use for authentication caching. If not set and authentication 252 * caching is {@link #isAuthenticationCachingEnabled() enabled}, any available 253 * {@link #getCacheManager() cacheManager} will be used to acquire the cache instance if available. 254 * <p/> 255 * <b>WARNING:</b> Only set this property if safe caching conditions apply, as documented at the top 256 * of this page in the class-level JavaDoc. 257 * 258 * @param authenticationCache an explicit {@link Cache} instance to use for authentication caching or 259 * {@code null} if the cache should possibly be obtained another way. 260 * @see #isAuthenticationCachingEnabled() 261 */ 262 void setAuthenticationCache(Cache!(Object, AuthenticationInfo) authenticationCache) { 263 this.authenticationCache = authenticationCache; 264 } 265 266 /** 267 * Returns a {@link Cache} instance to use for authentication caching, or {@code null} if no cache has been 268 * set. 269 * 270 * @return a {@link Cache} instance to use for authentication caching, or {@code null} if no cache has been 271 * set. 272 * @see #setAuthenticationCache(hunt.shiro.cache.Cache) 273 * @see #isAuthenticationCachingEnabled() 274 */ 275 Cache!(Object, AuthenticationInfo) getAuthenticationCache() { 276 return this.authenticationCache; 277 } 278 279 /** 280 * Returns the name of a {@link Cache} to lookup from any available {@link #getCacheManager() cacheManager} if 281 * a cache is not explicitly configured via {@link #setAuthenticationCache(hunt.shiro.cache.Cache)}. 282 * <p/> 283 * This name will only be used to look up a cache if authentication caching is 284 * {@link #isAuthenticationCachingEnabled() enabled}. 285 * <p/> 286 * <b>WARNING:</b> Only set this property if safe caching conditions apply, as documented at the top 287 * of this page in the class-level JavaDoc. 288 * 289 * @return the name of a {@link Cache} to lookup from any available {@link #getCacheManager() cacheManager} if 290 * a cache is not explicitly configured via {@link #setAuthenticationCache(hunt.shiro.cache.Cache)}. 291 * @see #isAuthenticationCachingEnabled() 292 */ 293 string getAuthenticationCacheName() { 294 return this.authenticationCacheName; 295 } 296 297 /** 298 * Sets the name of a {@link Cache} to lookup from any available {@link #getCacheManager() cacheManager} if 299 * a cache is not explicitly configured via {@link #setAuthenticationCache(hunt.shiro.cache.Cache)}. 300 * <p/> 301 * This name will only be used to look up a cache if authentication caching is 302 * {@link #isAuthenticationCachingEnabled() enabled}. 303 * 304 * @param authenticationCacheName the name of a {@link Cache} to lookup from any available 305 * {@link #getCacheManager() cacheManager} if a cache is not explicitly configured 306 * via {@link #setAuthenticationCache(hunt.shiro.cache.Cache)}. 307 * @see #isAuthenticationCachingEnabled() 308 */ 309 void setAuthenticationCacheName(string authenticationCacheName) { 310 this.authenticationCacheName = authenticationCacheName; 311 } 312 313 /** 314 * Returns {@code true} if authentication caching should be utilized if a {@link CacheManager} has been 315 * {@link #setCacheManager(hunt.shiro.cache.CacheManager) configured}, {@code false} otherwise. 316 * <p/> 317 * The default value is {@code true}. 318 * 319 * @return {@code true} if authentication caching should be utilized, {@code false} otherwise. 320 */ 321 bool isAuthenticationCachingEnabled() { 322 return this.authenticationCachingEnabled && isCachingEnabled(); 323 } 324 325 /** 326 * Sets whether or not authentication caching should be utilized if a {@link CacheManager} has been 327 * {@link #setCacheManager(hunt.shiro.cache.CacheManager) configured}, {@code false} otherwise. 328 * <p/> 329 * The default value is {@code false} to retain backwards compatibility with Shiro 1.1 and earlier. 330 * <p/> 331 * <b>WARNING:</b> Only set this property to {@code true} if safe caching conditions apply, as documented at the top 332 * of this page in the class-level JavaDoc. 333 * 334 * @param authenticationCachingEnabled the value to set 335 */ 336 //@SuppressWarnings({"UnusedDeclaration"}) 337 void setAuthenticationCachingEnabled(bool authenticationCachingEnabled) { 338 this.authenticationCachingEnabled = authenticationCachingEnabled; 339 if (authenticationCachingEnabled) { 340 setCachingEnabled(true); 341 } 342 } 343 344 override void setName(string name) { 345 super.setName(name); 346 string authcCacheName = this.authenticationCacheName; 347 if (authcCacheName !is null && authcCacheName.startsWith(typeid(this).name)) { 348 //get rid of the default heuristically-created cache name. Create a more meaningful one 349 //based on the application-unique Realm name: 350 this.authenticationCacheName = name ~ DEFAULT_AUTHORIZATION_CACHE_SUFFIX; 351 } 352 } 353 354 355 /*-------------------------------------------- 356 | M E T H O D S | 357 ============================================*/ 358 359 /** 360 * Convenience implementation that returns 361 * <tt>getAuthenticationTokenClass().isAssignableFrom( token.getClass() );</tt>. Can be overridden 362 * by subclasses for more complex token checking. 363 * <p>Most configurations will only need to set a different class via 364 * {@link #setAuthenticationTokenClass}, as opposed to overriding this method. 365 * 366 * @param token the token being submitted for authentication. 367 * @return true if this authentication realm can process the submitted token instance of the class, false otherwise. 368 */ 369 bool supports(AuthenticationToken token) { 370 return token !is null && _d_isbaseof(getAuthenticationTokenClass(), typeid(token)); 371 } 372 373 /** 374 * Initializes this realm and potentially enables an authentication cache, depending on configuration. Based on 375 * the availability of an authentication cache, this class functions as follows: 376 * <ol> 377 * <li>If the {@link #setAuthenticationCache cache} property has been set, it will be 378 * used to cache the AuthenticationInfo objects returned from {@link #getAuthenticationInfo} 379 * method invocations. 380 * All future calls to {@link #getAuthenticationInfo} will attempt to use this cache first 381 * to alleviate any potentially unnecessary calls to an underlying data store.</li> 382 * <li>If the {@link #setAuthenticationCache cache} property has <b>not</b> been set, 383 * the {@link #setCacheManager cacheManager} property will be checked. 384 * If a {@code cacheManager} has been set, it will be used to eagerly acquire an authentication 385 * {@code cache}, and this cache which will be used as specified in #1.</li> 386 * <li>If neither the {@link #setAuthenticationCache (hunt.shiro.cache.Cache) authenticationCache} 387 * or {@link #setCacheManager(hunt.shiro.cache.CacheManager) cacheManager} 388 * properties are set, caching will not be utilized and authentication look-ups will be delegated to 389 * subclass implementations for each authentication attempt.</li> 390 * </ol> 391 * <p/> 392 * This method finishes by calling {@link #onInit()} is to allow subclasses to perform any init behavior desired. 393 * 394 */ 395 final void init() { 396 //trigger obtaining the authorization cache if possible 397 getAvailableAuthenticationCache(); 398 onInit(); 399 } 400 401 /** 402 * Template method for subclasses to implement any initialization logic. Called from 403 * {@link #init()}. 404 * 405 */ 406 protected void onInit() { 407 } 408 409 /** 410 * This implementation attempts to acquire an authentication cache if one is not already configured. 411 * 412 */ 413 override protected void afterCacheManagerSet() { 414 //trigger obtaining the authorization cache if possible 415 getAvailableAuthenticationCache(); 416 } 417 418 /** 419 * Returns any available {@link Cache} instance to use for authentication caching. This functions as follows: 420 * <ol> 421 * <li>If an {@link #setAuthenticationCache(hunt.shiro.cache.Cache) authenticationCache} has been explicitly 422 * configured (it is not null), it is returned.</li> 423 * <li>If there is no {@link #getAuthenticationCache() authenticationCache} configured: 424 * <ol> 425 * <li>If authentication caching is {@link #isAuthenticationCachingEnabled() enabled}, any available 426 * {@link #getCacheManager() cacheManager} will be consulted to obtain an available authentication cache. 427 * </li> 428 * <li>If authentication caching is disabled, this implementation does nothing.</li> 429 * </ol> 430 * </li> 431 * </ol> 432 * 433 * @return any available {@link Cache} instance to use for authentication caching. 434 */ 435 private Cache!(Object, AuthenticationInfo) getAvailableAuthenticationCache() { 436 Cache!(Object, AuthenticationInfo) cache = getAuthenticationCache(); 437 bool authcCachingEnabled = isAuthenticationCachingEnabled(); 438 if (cache is null && authcCachingEnabled) { 439 cache = getAuthenticationCacheLazy(); 440 } 441 return cache; 442 } 443 444 /** 445 * Checks to see if the authenticationCache class attribute is null, and if so, attempts to acquire one from 446 * any configured {@link #getCacheManager() cacheManager}. If one is acquired, it is set as the class attribute. 447 * The class attribute is then returned. 448 * 449 * @return an available cache instance to be used for authentication caching or {@code null} if one is not available. 450 */ 451 private Cache!(Object, AuthenticationInfo) getAuthenticationCacheLazy() { 452 453 if (this.authenticationCache is null) { 454 455 tracef("No authenticationCache instance set. Checking for a cacheManager..."); 456 457 CacheManager cacheManager = getCacheManager(); 458 459 if (cacheManager !is null) { 460 string cacheName = getAuthenticationCacheName(); 461 tracef("CacheManager [%s] configured. Building authentication cache '%s'", cacheManager, cacheName); 462 // this.authenticationCache = cacheManager.getCache(cacheName); 463 464 implementationMissing(false); 465 } 466 } 467 468 return this.authenticationCache; 469 } 470 471 /** 472 * Returns any cached AuthenticationInfo corresponding to the specified token or {@code null} if there currently 473 * isn't any cached data. 474 * 475 * @param token the token submitted during the authentication attempt. 476 * @return any cached AuthenticationInfo corresponding to the specified token or {@code null} if there currently 477 * isn't any cached data. 478 */ 479 private AuthenticationInfo getCachedAuthenticationInfo(AuthenticationToken token) { 480 AuthenticationInfo info = null; 481 482 Cache!(Object, AuthenticationInfo) cache = getAvailableAuthenticationCache(); 483 if (cache !is null && token !is null) { 484 tracef("Attempting to retrieve the AuthenticationInfo from cache."); 485 string key = getAuthenticationCacheKey(token); 486 info = cache.get(new String(key)); 487 if (info is null) { 488 tracef("No AuthorizationInfo found in cache for key [%s]", key); 489 } else { 490 tracef("Found cached AuthorizationInfo for key [%s]", key); 491 } 492 } 493 494 return info; 495 } 496 497 /** 498 * Caches the specified info if authentication caching 499 * {@link #isAuthenticationCachingEnabled(hunt.shiro.authc.AuthenticationToken, hunt.shiro.authc.AuthenticationInfo) isEnabled} 500 * for the specific token/info pair and a cache instance is available to be used. 501 * 502 * @param token the authentication token submitted which resulted in a successful authentication attempt. 503 * @param info the AuthenticationInfo to cache as a result of the successful authentication attempt. 504 */ 505 private void cacheAuthenticationInfoIfPossible(AuthenticationToken token, AuthenticationInfo info) { 506 if (!isAuthenticationCachingEnabled(token, info)) { 507 version(HUNT_SHIRO_DEBUG) tracef("AuthenticationInfo caching is disabled for info [%s]. Submitted token: [%s].", info, token); 508 //return quietly, caching is disabled for this token/info pair: 509 return; 510 } 511 512 Cache!(Object, AuthenticationInfo) cache = getAvailableAuthenticationCache(); 513 if (cache !is null) { 514 string key = getAuthenticationCacheKey(token); 515 cache.put(new String(key), info); 516 version(HUNT_SHIRO_DEBUG) tracef("Cached AuthenticationInfo for continued authentication. key=[%s], value=[%s].", key, info); 517 } 518 } 519 520 /** 521 * Returns {@code true} if authentication caching should be utilized based on the specified 522 * {@link AuthenticationToken} and/or {@link AuthenticationInfo}, {@code false} otherwise. 523 * <p/> 524 * The default implementation simply delegates to {@link #isAuthenticationCachingEnabled()}, the general-case 525 * authentication caching setting. Subclasses can override this to turn on or off caching at runtime 526 * based on the specific submitted runtime values. 527 * 528 * @param token the submitted authentication token 529 * @param info the {@code AuthenticationInfo} acquired from data source lookup via 530 * {@link #doGetAuthenticationInfo(hunt.shiro.authc.AuthenticationToken)} 531 * @return {@code true} if authentication caching should be utilized based on the specified 532 * {@link AuthenticationToken} and/or {@link AuthenticationInfo}, {@code false} otherwise. 533 */ 534 protected bool isAuthenticationCachingEnabled(AuthenticationToken token, AuthenticationInfo info) { 535 return isAuthenticationCachingEnabled(); 536 } 537 538 /** 539 * This implementation functions as follows: 540 * <ol> 541 * <li>It attempts to acquire any cached {@link AuthenticationInfo} corresponding to the specified 542 * {@link AuthenticationToken} argument. If a cached value is found, it will be used for credentials matching, 543 * alleviating the need to perform any lookups with a data source.</li> 544 * <li>If there is no cached {@link AuthenticationInfo} found, delegate to the 545 * {@link #doGetAuthenticationInfo(hunt.shiro.authc.AuthenticationToken)} method to perform the actual 546 * lookup. If authentication caching is enabled and possible, any returned info object will be 547 * {@link #cacheAuthenticationInfoIfPossible(hunt.shiro.authc.AuthenticationToken, hunt.shiro.authc.AuthenticationInfo) cached} 548 * to be used in future authentication attempts.</li> 549 * <li>If an AuthenticationInfo instance is not found in the cache or by lookup, {@code null} is returned to 550 * indicate an account cannot be found.</li> 551 * <li>If an AuthenticationInfo instance is found (either cached or via lookup), ensure the submitted 552 * AuthenticationToken's credentials match the expected {@code AuthenticationInfo}'s credentials using the 553 * {@link #getCredentialsMatcher() credentialsMatcher}. This means that credentials are always verified 554 * for an authentication attempt.</li> 555 * </ol> 556 * 557 * @param token the submitted account principal and credentials. 558 * @return the AuthenticationInfo corresponding to the given {@code token}, or {@code null} if no 559 * AuthenticationInfo could be found. 560 * @throws AuthenticationException if authentication failed. 561 */ 562 final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token){ 563 564 AuthenticationInfo info = getCachedAuthenticationInfo(token); 565 if (info is null) { 566 //otherwise not cached, perform the lookup: 567 info = doGetAuthenticationInfo(token); 568 version(HUNT_SHIRO_DEBUG) tracef("Looked up AuthenticationInfo [%s] from doGetAuthenticationInfo", info); 569 if (token !is null && info !is null) { 570 cacheAuthenticationInfoIfPossible(token, info); 571 } 572 } else { 573 version(HUNT_SHIRO_DEBUG) 574 tracef("Using cached authentication info [%s] to perform credentials matching.", info); 575 } 576 577 if (info !is null) { 578 assertCredentialsMatch(token, info); 579 } else { 580 version(HUNT_SHIRO_DEBUG) 581 tracef("No AuthenticationInfo found for submitted AuthenticationToken [%s]. Returning null.", token); 582 } 583 584 return info; 585 } 586 587 /** 588 * Asserts that the submitted {@code AuthenticationToken}'s credentials match the stored account 589 * {@code AuthenticationInfo}'s credentials, and if not,{@link AuthenticationException}. 590 * 591 * @param token the submitted authentication token 592 * @param info the AuthenticationInfo corresponding to the given {@code token} 593 * @throws AuthenticationException if the token's credentials do not match the stored account credentials. 594 */ 595 protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info){ 596 CredentialsMatcher cm = getCredentialsMatcher(); 597 if (cm !is null) { 598 if (!cm.doCredentialsMatch(token, info)) { 599 //not successful - throw an exception to indicate this: 600 string msg = "Submitted credentials for token [" ~ (cast(Object)token).toString() ~ 601 "] did not match the expected credentials."; 602 throw new IncorrectCredentialsException(msg); 603 } 604 } else { 605 throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " ~ 606 "credentials during authentication. If you do not wish for credentials to be examined, you " ~ 607 "can configure an " ~ typeid(AllowAllCredentialsMatcher).name ~ " instance."); 608 } 609 } 610 611 /** 612 * Returns the key under which {@link AuthenticationInfo} instances are cached if authentication caching is enabled. 613 * This implementation defaults to returning the token's 614 * {@link hunt.shiro.authc.AuthenticationToken#getPrincipal() principal}, which is usually a username in 615 * most applications. 616 * <h3>Cache Invalidation on Logout</h3> 617 * <b>NOTE:</b> If you want to be able to invalidate an account's cached {@code AuthenticationInfo} on logout, you 618 * must ensure the {@link #getAuthenticationCacheKey(hunt.shiro.subject.PrincipalCollection)} method returns 619 * the same value as this method. 620 * 621 * @param token the authentication token for which any successful authentication will be cached. 622 * @return the cache key to use to cache the associated {@link AuthenticationInfo} after a successful authentication. 623 */ 624 protected string getAuthenticationCacheKey(AuthenticationToken token) { 625 return token !is null ? token.getPrincipal() : null; 626 } 627 628 /** 629 * Returns the key under which {@link AuthenticationInfo} instances are cached if authentication caching is enabled. 630 * This implementation delegates to 631 * {@link #getAvailablePrincipal(hunt.shiro.subject.PrincipalCollection)}, which returns the primary principal 632 * associated with this particular Realm. 633 * <h3>Cache Invalidation on Logout</h3> 634 * <b>NOTE:</b> If you want to be able to invalidate an account's cached {@code AuthenticationInfo} on logout, you 635 * must ensure that this method returns the same value as the 636 * {@link #getAuthenticationCacheKey(hunt.shiro.authc.AuthenticationToken)} method! 637 * 638 * @param principals the principals of the account for which to set or remove cached {@code AuthenticationInfo}. 639 * @return the cache key to use when looking up cached {@link AuthenticationInfo} instances. 640 */ 641 protected Object getAuthenticationCacheKey(PrincipalCollection principals) { 642 return getAvailablePrincipal(principals); 643 } 644 645 /** 646 * This implementation clears out any cached authentication data by calling 647 * {@link #clearCachedAuthenticationInfo(hunt.shiro.subject.PrincipalCollection)}. 648 * If overriding in a subclass, be sure to call {@code super.doClearCache} to ensure this behavior is maintained. 649 * 650 * @param principals principals the principals of the account for which to clear any cached data. 651 */ 652 override 653 protected void doClearCache(PrincipalCollection principals) { 654 super.doClearCache(principals); 655 clearCachedAuthenticationInfo(principals); 656 } 657 658 private static bool isEmpty(PrincipalCollection pc) { 659 return pc is null || pc.isEmpty(); 660 } 661 662 /** 663 * Clears out the AuthenticationInfo cache entry for the specified account. 664 * <p/> 665 * This method is provided as a convenience to subclasses so they can invalidate a cache entry when they 666 * change an account's authentication data (e.g. reset password) during runtime. Because an account's 667 * AuthenticationInfo can be cached, there needs to be a way to invalidate the cache for only that account so that 668 * subsequent authentication operations don't used the (old) cached value if account data changes. 669 * <p/> 670 * After this method is called, the next authentication for that same account will result in a call to 671 * {@link #doGetAuthenticationInfo(hunt.shiro.authc.AuthenticationToken) doGetAuthenticationInfo}, and the 672 * resulting return value will be cached before being returned so it can be reused for later authentications. 673 * <p/> 674 * If you wish to clear out all associated cached data (and not just authentication data), use the 675 * {@link #clearCache(hunt.shiro.subject.PrincipalCollection)} method instead (which will in turn call this 676 * method by default). 677 * 678 * @param principals the principals of the account for which to clear the cached AuthorizationInfo. 679 * @see #clearCache(hunt.shiro.subject.PrincipalCollection) 680 */ 681 protected void clearCachedAuthenticationInfo(PrincipalCollection principals) { 682 if (!isEmpty(principals)) { 683 Cache!(Object, AuthenticationInfo) cache = getAvailableAuthenticationCache(); 684 //cache instance will be non-null if caching is enabled: 685 if (cache !is null) { 686 Object key = getAuthenticationCacheKey(principals); 687 cache.remove(key); 688 } 689 } 690 } 691 692 /** 693 * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given 694 * authentication token. 695 * <p/> 696 * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing 697 * more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific 698 * log-in logic in addition to just retrieving data - it is up to the Realm implementation. 699 * <p/> 700 * A {@code null} return value means that no account could be associated with the specified token. 701 * 702 * @param token the authentication token containing the user's principal and credentials. 703 * @return an {@link AuthenticationInfo} object containing account data resulting from the 704 * authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.) 705 * @throws AuthenticationException if there is an error acquiring data or performing 706 * realm-specific authentication logic for the specified <tt>token</tt> 707 */ 708 protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token); 709 710 }