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.mgt;
20
21 import org.apache.shiro.authc.AuthenticationException;
22 import org.apache.shiro.authc.AuthenticationInfo;
23 import org.apache.shiro.authc.AuthenticationToken;
24 import org.apache.shiro.authc.RememberMeAuthenticationToken;
25 import org.apache.shiro.crypto.cipher.AesCipherService;
26 import org.apache.shiro.crypto.cipher.ByteSourceBroker;
27 import org.apache.shiro.crypto.cipher.CipherService;
28 import org.apache.shiro.lang.io.DefaultSerializer;
29 import org.apache.shiro.lang.io.Serializer;
30 import org.apache.shiro.lang.util.ByteSource;
31 import org.apache.shiro.lang.util.ByteUtils;
32 import org.apache.shiro.subject.PrincipalCollection;
33 import org.apache.shiro.subject.Subject;
34 import org.apache.shiro.subject.SubjectContext;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import java.util.function.Supplier;
39
40 /**
41 * Abstract implementation of the {@code RememberMeManager} interface that handles
42 * {@link #setSerializer(Serializer) serialization} and
43 * {@link #setCipherService encryption} of the remembered user identity.
44 * <p/>
45 * The remembered identity storage location and details are left to subclasses.
46 * <h2>Default encryption key</h2>
47 * This implementation uses an {@link AesCipherService AesCipherService} for strong encryption by default. It also
48 * uses a default generated symmetric key to both encrypt and decrypt data. As AES is a symmetric cipher, the same
49 * {@code key} is used to both encrypt and decrypt data, BUT NOTE:
50 * <p/>
51 * Because Shiro is an open-source project, if anyone knew that you were using Shiro's default
52 * {@code key}, they could download/view the source, and with enough effort, reconstruct the {@code key}
53 * and decode encrypted data at will.
54 * <p/>
55 * Of course, this key is only really used to encrypt the remembered {@code PrincipalCollection} which is typically
56 * a user id or username. So if you do not consider that sensitive information, and you think the default key still
57 * makes things 'sufficiently difficult', then you can ignore this issue.
58 * <p/>
59 * However, if you do feel this constitutes sensitive information, it is recommended that you provide your own
60 * {@code key} via the {@link #setCipherKey setCipherKey} method to a key known only to your application,
61 * guaranteeing that no third party can decrypt your data. You can generate your own key by calling the
62 * {@code CipherService}'s {@link AesCipherService#generateNewKey() generateNewKey} method
63 * and using that result as the {@link #setCipherKey cipherKey} configuration attribute.
64 *
65 * @since 0.9
66 */
67 public abstract class AbstractRememberMeManager implements RememberMeManager {
68
69 /**
70 * private inner log instance.
71 */
72 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRememberMeManager.class);
73
74 /**
75 * Serializer to use for converting PrincipalCollection instances to/from byte arrays
76 */
77 private Serializer<PrincipalCollection> serializer = new DefaultSerializer<>();
78
79 /**
80 * Cipher to use for encrypting/decrypting serialized byte arrays for added security
81 */
82 private CipherService cipherService = new AesCipherService();
83 ;
84
85 /**
86 * Cipher encryption key to use with the Cipher when encrypting data
87 */
88 private byte[] encryptionCipherKey;
89
90 /**
91 * Cipher decryption key to use with the Cipher when decrypting data
92 */
93 private byte[] decryptionCipherKey;
94
95 /**
96 * Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and
97 * an {@link AesCipherService} as the {@link #getCipherService() cipherService}.
98 */
99 public AbstractRememberMeManager() {
100 setCipherKey(((AesCipherService) cipherService).generateNewKey().getEncoded());
101 }
102
103 /**
104 * Constructor. Pass keySupplier that supplies encryption key
105 *
106 * @param keySupplier
107 * @since 2.0
108 */
109 public AbstractRememberMeManager(Supplier<byte[]> keySupplier) {
110 this();
111 setCipherKey(keySupplier.get());
112 }
113
114 /**
115 * Returns the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
116 * persistent remember me storage.
117 * <p/>
118 * Unless overridden by the {@link #setSerializer} method, the default instance is a
119 * {@link org.apache.shiro.lang.io.DefaultSerializer}.
120 *
121 * @return the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
122 * persistent remember me storage.
123 */
124 public Serializer<PrincipalCollection> getSerializer() {
125 return serializer;
126 }
127
128 /**
129 * Sets the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
130 * persistent remember me storage.
131 * <p/>
132 * Unless overridden by this method, the default instance is a {@link DefaultSerializer}.
133 *
134 * @param serializer the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances
135 * for persistent remember me storage.
136 */
137 public void setSerializer(Serializer<PrincipalCollection> serializer) {
138 this.serializer = serializer;
139 }
140
141 /**
142 * Returns the {@code CipherService} to use for encrypting and decrypting serialized identity data to prevent easy
143 * inspection of Subject identity data.
144 * <p/>
145 * Unless overridden by the {@link #setCipherService} method, the default instance is an {@link AesCipherService}.
146 *
147 * @return the {@code Cipher} to use for encrypting and decrypting serialized identity data to prevent easy
148 * inspection of Subject identity data
149 */
150 public CipherService getCipherService() {
151 return cipherService;
152 }
153
154 /**
155 * Sets the {@code CipherService} to use for encrypting and decrypting serialized identity data to prevent easy
156 * inspection of Subject identity data.
157 * <p/>
158 * If the CipherService is a symmetric CipherService (using the same key for both encryption and decryption), you
159 * should set your key via the {@link #setCipherKey(byte[])} method.
160 * <p/>
161 * If the CipherService is an asymmetric CipherService (different keys for encryption and decryption, such as
162 * public/private key pairs), you should set your encryption and decryption key via the respective
163 * {@link #setEncryptionCipherKey(byte[])} and {@link #setDecryptionCipherKey(byte[])} methods.
164 * <p/>
165 * <b>N.B.</b> Unless overridden by this method, the default CipherService instance is an
166 * {@link AesCipherService}. This {@code RememberMeManager} implementation already has a configured symmetric key
167 * to use for encryption and decryption, but it is recommended to provide your own for added security. See the
168 * class-level JavaDoc for more information and why it might be good to provide your own.
169 *
170 * @param cipherService the {@code CipherService} to use for encrypting and decrypting serialized identity data to
171 * prevent easy inspection of Subject identity data.
172 */
173 public void setCipherService(CipherService cipherService) {
174 this.cipherService = cipherService;
175 }
176
177 /**
178 * Returns the cipher key to use for encryption operations.
179 *
180 * @return the cipher key to use for encryption operations.
181 * @see #setCipherService for a description of the various {@code get/set*Key} methods.
182 */
183 public byte[] getEncryptionCipherKey() {
184 return encryptionCipherKey;
185 }
186
187 /**
188 * Sets the encryption key to use for encryption operations.
189 *
190 * @param encryptionCipherKey the encryption key to use for encryption operations.
191 * @see #setCipherService for a description of the various {@code get/set*Key} methods.
192 */
193 public void setEncryptionCipherKey(byte[] encryptionCipherKey) {
194 this.encryptionCipherKey = encryptionCipherKey;
195 }
196
197 /**
198 * Returns the decryption cipher key to use for decryption operations.
199 *
200 * @return the cipher key to use for decryption operations.
201 * @see #setCipherService for a description of the various {@code get/set*Key} methods.
202 */
203 public byte[] getDecryptionCipherKey() {
204 return decryptionCipherKey;
205 }
206
207 /**
208 * Sets the decryption key to use for decryption operations.
209 *
210 * @param decryptionCipherKey the decryption key to use for decryption operations.
211 * @see #setCipherService for a description of the various {@code get/set*Key} methods.
212 */
213 public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
214 this.decryptionCipherKey = decryptionCipherKey;
215 }
216
217 /**
218 * Convenience method that returns the cipher key to use for <em>both</em> encryption and decryption.
219 * <p/>
220 * <b>N.B.</b> This method can only be called if the underlying {@link #getCipherService() cipherService} is a symmetric
221 * CipherService which by definition uses the same key for both encryption and decryption. If using an asymmetric
222 * CipherService public/private key pair, you cannot use this method, and should instead use the
223 * {@link #getEncryptionCipherKey()} and {@link #getDecryptionCipherKey()} methods individually.
224 * <p/>
225 * The default {@link AesCipherService} instance is a symmetric cipher service, so this method can be used if you are
226 * using the default.
227 *
228 * @return the symmetric cipher key used for both encryption and decryption.
229 */
230 public byte[] getCipherKey() {
231 //Since this method should only be used with symmetric ciphers
232 //(where the enc and dec keys are the same), either is fine, just return one of them:
233 return getEncryptionCipherKey();
234 }
235
236 /**
237 * Convenience method that sets the cipher key to use for <em>both</em> encryption and decryption.
238 * <p/>
239 * <b>N.B.</b> This method can only be called if the underlying {@link #getCipherService() cipherService} is a
240 * symmetric CipherService?which by definition uses the same key for both encryption and decryption. If using an
241 * asymmetric CipherService?(such as a public/private key pair), you cannot use this method, and should instead use
242 * the {@link #setEncryptionCipherKey(byte[])} and {@link #setDecryptionCipherKey(byte[])} methods individually.
243 * <p/>
244 * The default {@link AesCipherService} instance is a symmetric CipherService, so this method can be used if you
245 * are using the default.
246 *
247 * @param cipherKey the symmetric cipher key to use for both encryption and decryption.
248 */
249 public void setCipherKey(byte[] cipherKey) {
250 //Since this method should only be used in symmetric ciphers
251 //(where the enc and dec keys are the same), set it on both:
252 setEncryptionCipherKey(cipherKey);
253 setDecryptionCipherKey(cipherKey);
254 }
255
256 /**
257 * Forgets (removes) any remembered identity data for the specified {@link Subject} instance.
258 *
259 * @param subject the subject instance for which identity data should be forgotten from the underlying persistence
260 * mechanism.
261 */
262 protected abstract void forgetIdentity(Subject subject);
263
264 /**
265 * Determines whether or not remember me services should be performed for the specified token. This method returns
266 * {@code true} iff:
267 * <ol>
268 * <li>The token is not {@code null} and</li>
269 * <li>The token is an {@code instanceof} {@link RememberMeAuthenticationToken} and</li>
270 * <li>{@code token}.{@link org.apache.shiro.authc.RememberMeAuthenticationToken#isRememberMe() isRememberMe()} is
271 * {@code true}</li>
272 * </ol>
273 *
274 * @param token the authentication token submitted during the successful authentication attempt.
275 * @return true if remember me services should be performed as a result of the successful authentication attempt.
276 */
277 protected boolean isRememberMe(AuthenticationToken token) {
278 return token instanceof RememberMeAuthenticationToken && ((RememberMeAuthenticationToken) token).isRememberMe();
279 }
280
281 /**
282 * Reacts to the successful login attempt by first always {@link #forgetIdentity(Subject) forgetting} any previously
283 * stored identity. Then if the {@code token}
284 * {@link #isRememberMe(org.apache.shiro.authc.AuthenticationToken) is a RememberMe} token, the associated identity
285 * will be {@link #rememberIdentity(org.apache.shiro.subject.Subject, org.apache.shiro.authc.AuthenticationToken,
286 * org.apache.shiro.authc.AuthenticationInfo) remembered}
287 * for later retrieval during a new user session.
288 *
289 * @param subject the subject for which the principals are being remembered.
290 * @param token the token that resulted in a successful authentication attempt.
291 * @param info the authentication info resulting from the successful authentication attempt.
292 */
293 public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
294 //always clear any previous identity:
295 forgetIdentity(subject);
296
297 //now save the new identity:
298 if (isRememberMe(token)) {
299 rememberIdentity(subject, token, info);
300 } else {
301 if (LOGGER.isDebugEnabled()) {
302 LOGGER.debug("AuthenticationToken did not indicate RememberMe is requested. "
303 + "RememberMe functionality will not be executed for corresponding account.");
304 }
305 }
306 }
307
308 /**
309 * Remembers a subject-unique identity for retrieval later. This implementation first
310 * {@link #getIdentityToRemember resolves} the exact
311 * {@link PrincipalCollection principals} to remember. It then remembers the principals by calling
312 * {@link #rememberIdentity(org.apache.shiro.subject.Subject, org.apache.shiro.subject.PrincipalCollection)}.
313 * <p/>
314 * This implementation ignores the {@link AuthenticationToken} argument, but it is available to subclasses if
315 * necessary for custom logic.
316 *
317 * @param subject the subject for which the principals are being remembered.
318 * @param token the token that resulted in a successful authentication attempt.
319 * @param authcInfo the authentication info resulting from the successful authentication attempt.
320 */
321 public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
322 PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
323 rememberIdentity(subject, principals);
324 }
325
326 /**
327 * Returns {@code info}.{@link org.apache.shiro.authc.AuthenticationInfo#getPrincipals() getPrincipals()} and
328 * ignores the {@link Subject} argument.
329 *
330 * @param subject the subject for which the principals are being remembered.
331 * @param info the authentication info resulting from the successful authentication attempt.
332 * @return the {@code PrincipalCollection} to remember.
333 */
334 protected PrincipalCollection getIdentityToRemember(Subject subject, AuthenticationInfo info) {
335 return info.getPrincipals();
336 }
337
338 /**
339 * Remembers the specified account principals by first
340 * {@link #convertPrincipalsToBytes(org.apache.shiro.subject.PrincipalCollection) converting} them to a byte
341 * array and then {@link #rememberSerializedIdentity(org.apache.shiro.subject.Subject, byte[]) remembers} that
342 * byte array.
343 *
344 * @param subject the subject for which the principals are being remembered.
345 * @param accountPrincipals the principals to remember for retrieval later.
346 */
347 protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
348 byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
349 rememberSerializedIdentity(subject, bytes);
350 }
351
352 /**
353 * Converts the given principal collection the byte array that will be persisted to be 'remembered' later.
354 * <p/>
355 * This implementation first {@link #serialize(org.apache.shiro.subject.PrincipalCollection) serializes} the
356 * principals to a byte array and then {@link #encrypt(byte[]) encrypts} that byte array.
357 *
358 * @param principals the {@code PrincipalCollection} to convert to a byte array
359 * @return the representative byte array to be persisted for remember me functionality.
360 */
361 protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
362 byte[] bytes = serialize(principals);
363 if (getCipherService() != null) {
364 bytes = encrypt(bytes);
365 }
366 return bytes;
367 }
368
369 /**
370 * Persists the identity bytes to a persistent store for retrieval later via the
371 * {@link #getRememberedSerializedIdentity(SubjectContext)} method.
372 *
373 * @param subject the Subject for which the identity is being serialized.
374 * @param serialized the serialized bytes to be persisted.
375 */
376 protected abstract void rememberSerializedIdentity(Subject subject, byte[] serialized);
377
378 /**
379 * Implements the interface method by first {@link #getRememberedSerializedIdentity(SubjectContext) acquiring}
380 * the remembered serialized byte array. Then it {@link #convertBytesToPrincipals(byte[], SubjectContext) converts}
381 * them and returns the re-constituted {@link PrincipalCollection}. If no remembered principals could be
382 * obtained, {@code null} is returned.
383 * <p/>
384 * If any exceptions are thrown, the {@link #onRememberedPrincipalFailure(RuntimeException, SubjectContext)} method
385 * is called to allow any necessary post-processing (such as immediately removing any previously remembered
386 * values for safety).
387 *
388 * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
389 * is being used to construct a {@link Subject} instance.
390 * @return the remembered principals or {@code null} if none could be acquired.
391 */
392 public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
393 PrincipalCollection principals = null;
394 byte[] bytes = null;
395 try {
396 bytes = getRememberedSerializedIdentity(subjectContext);
397 //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
398 if (bytes != null && bytes.length > 0) {
399 principals = convertBytesToPrincipals(bytes, subjectContext);
400 }
401 } catch (RuntimeException re) {
402 principals = onRememberedPrincipalFailure(re, subjectContext);
403 } finally {
404 ByteUtils.wipe(bytes);
405 }
406
407 return principals;
408 }
409
410 /**
411 * Based on the given subject context data, retrieves the previously persisted serialized identity, or
412 * {@code null} if there is no available data. The context map is usually populated by a {@link Subject.Builder}
413 * implementation. See the {@link SubjectFactory} class constants for Shiro's known map keys.
414 *
415 * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
416 * is being used to construct a {@link Subject} instance. To be used to assist with data
417 * lookup.
418 * @return the previously persisted serialized identity, or {@code null} if there is no available data for the
419 * Subject.
420 */
421 protected abstract byte[] getRememberedSerializedIdentity(SubjectContext subjectContext);
422
423 /**
424 * If a {@link #getCipherService() cipherService} is available, it will be used to first decrypt the byte array.
425 * Then the bytes are then {@link #deserialize(byte[]) deserialized} and then returned.
426 *
427 * @param bytes the bytes to decrypt if necessary and then deserialize.
428 * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
429 * is being used to construct a {@link Subject} instance.
430 * @return the deserialized and possibly decrypted principals
431 */
432 protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
433 if (getCipherService() != null) {
434 bytes = decrypt(bytes);
435 }
436 return deserialize(bytes);
437 }
438
439 /**
440 * Called when an exception is thrown while trying to retrieve principals. The default implementation logs a
441 * warning message and forgets ('unremembers') the problem identity by calling
442 * {@link #forgetIdentity(SubjectContext) forgetIdentity(context)} and then immediately re-throws the
443 * exception to allow the calling component to react accordingly.
444 * <p/>
445 * This method implementation never returns an
446 * object - it always rethrows, but can be overridden by subclasses for custom handling behavior.
447 * <p/>
448 * This most commonly would be called when an encryption key is updated and old principals are retrieved that have
449 * been encrypted with the previous key.
450 *
451 * @param e the exception that was thrown.
452 * @param context the contextual data, usually provided by a {@link Subject.Builder} implementation, that
453 * is being used to construct a {@link Subject} instance.
454 * @return nothing - the original {@code RuntimeException} is propagated in all cases.
455 */
456 protected PrincipalCollection onRememberedPrincipalFailure(RuntimeException e, SubjectContext context) {
457
458 if (LOGGER.isWarnEnabled()) {
459 String message = "There was a failure while trying to retrieve remembered principals. This could be due to a "
460 + "configuration problem or corrupted principals. This could also be due to a recently "
461 + "changed encryption key, if you are using a shiro.ini file, this property would be "
462 + "'securityManager.rememberMeManager.cipherKey'"
463 + "see: http://shiro.apache.org/web.html#Web-RememberMeServices. "
464 + "The remembered identity will be forgotten and not used for this request.";
465 LOGGER.warn(message);
466 }
467 forgetIdentity(context);
468 //propagate - security manager implementation will handle and warn appropriately
469 throw e;
470 }
471
472 /**
473 * Encrypts the byte array by using the configured {@link #getCipherService() cipherService}.
474 *
475 * @param serialized the serialized object byte array to be encrypted
476 * @return an encrypted byte array returned by the configured {@link #getCipherService () cipher}.
477 */
478 protected byte[] encrypt(byte[] serialized) {
479 byte[] value = serialized;
480 CipherService cipherService = getCipherService();
481 if (cipherService != null) {
482 ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
483 value = byteSource.getBytes();
484 }
485 return value;
486 }
487
488 /**
489 * Decrypts the byte array using the configured {@link #getCipherService() cipherService}.
490 *
491 * @param encrypted the encrypted byte array to decrypt
492 * @return the decrypted byte array returned by the configured {@link #getCipherService () cipher}.
493 */
494 protected byte[] decrypt(byte[] encrypted) {
495 byte[] serialized = encrypted;
496 CipherService cipherService = getCipherService();
497 if (cipherService != null) {
498 ByteSourceBroker broker = cipherService.decrypt(encrypted, getDecryptionCipherKey());
499 serialized = broker.getClonedBytes();
500 }
501 return serialized;
502 }
503
504 /**
505 * Serializes the given {@code principals} by serializing them to a byte array by using the
506 * {@link #getSerializer() serializer}'s {@link Serializer#serialize(Object) serialize} method.
507 *
508 * @param principals the principal collection to serialize to a byte array
509 * @return the serialized principal collection in the form of a byte array
510 */
511 protected byte[] serialize(PrincipalCollection principals) {
512 return getSerializer().serialize(principals);
513 }
514
515 /**
516 * Deserializes the given byte array by using the {@link #getSerializer() serializer}'s
517 * {@link Serializer#deserialize deserialize} method.
518 *
519 * @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array
520 * @return the deserialized (reconstituted) {@code PrincipalCollection}
521 */
522 protected PrincipalCollection deserialize(byte[] serializedIdentity) {
523 return getSerializer().deserialize(serializedIdentity);
524 }
525
526 /**
527 * Reacts to a failed login by immediately {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgetting} any
528 * previously remembered identity. This is an additional security feature to prevent any remnant identity data
529 * from being retained in case the authentication attempt is not being executed by the expected user.
530 *
531 * @param subject the subject which executed the failed login attempt
532 * @param token the authentication token resulting in a failed login attempt - ignored by this implementation
533 * @param ae the exception thrown as a result of the failed login attempt - ignored by this implementation
534 */
535 public void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae) {
536 forgetIdentity(subject);
537 }
538
539 /**
540 * Reacts to a subject logging out of the application and immediately
541 * {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgets} any previously stored identity and returns.
542 *
543 * @param subject the subject logging out.
544 */
545 public void onLogout(Subject subject) {
546 forgetIdentity(subject);
547 }
548 }