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