View Javadoc
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 }