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