001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.shiro.mgt;
020    
021    import org.apache.shiro.authc.AuthenticationException;
022    import org.apache.shiro.authc.AuthenticationInfo;
023    import org.apache.shiro.authc.AuthenticationToken;
024    import org.apache.shiro.authc.RememberMeAuthenticationToken;
025    import org.apache.shiro.codec.Base64;
026    import org.apache.shiro.crypto.AesCipherService;
027    import org.apache.shiro.crypto.CipherService;
028    import org.apache.shiro.io.DefaultSerializer;
029    import org.apache.shiro.io.Serializer;
030    import org.apache.shiro.subject.PrincipalCollection;
031    import org.apache.shiro.subject.Subject;
032    import org.apache.shiro.subject.SubjectContext;
033    import org.apache.shiro.util.ByteSource;
034    import org.slf4j.Logger;
035    import org.slf4j.LoggerFactory;
036    
037    /**
038     * Abstract implementation of the {@code RememberMeManager} interface that handles
039     * {@link #setSerializer(org.apache.shiro.io.Serializer) serialization} and
040     * {@link #setCipherService encryption} of the remembered user identity.
041     * <p/>
042     * The remembered identity storage location and details are left to subclasses.
043     * <h2>Default encryption key</h2>
044     * This implementation uses an {@link AesCipherService AesCipherService} for strong encryption by default.  It also
045     * uses a default generated symmetric key to both encrypt and decrypt data.  As AES is a symmetric cipher, the same
046     * {@code key} is used to both encrypt and decrypt data, BUT NOTE:
047     * <p/>
048     * Because Shiro is an open-source project, if anyone knew that you were using Shiro's default
049     * {@code key}, they could download/view the source, and with enough effort, reconstruct the {@code key}
050     * and decode encrypted data at will.
051     * <p/>
052     * Of course, this key is only really used to encrypt the remembered {@code PrincipalCollection} which is typically
053     * a user id or username.  So if you do not consider that sensitive information, and you think the default key still
054     * makes things 'sufficiently difficult', then you can ignore this issue.
055     * <p/>
056     * However, if you do feel this constitutes sensitive information, it is recommended that you provide your own
057     * {@code key} via the {@link #setCipherKey setCipherKey} method to a key known only to your application,
058     * guaranteeing that no third party can decrypt your data.  You can generate your own key by calling the
059     * {@code CipherService}'s {@link org.apache.shiro.crypto.AesCipherService#generateNewKey() generateNewKey} method
060     * and using that result as the {@link #setCipherKey cipherKey} configuration attribute.
061     *
062     * @author Les Hazlewood
063     * @author Jeremy Haile
064     * @since 0.9
065     */
066    public abstract class AbstractRememberMeManager implements RememberMeManager {
067    
068        /**
069         * private inner log instance.
070         */
071        private static final Logger log = LoggerFactory.getLogger(AbstractRememberMeManager.class);
072    
073        /**
074         * The following Base64 string was generated by auto-generating an AES Key:
075         * <pre>
076         * AesCipherService aes = new AesCipherService();
077         * byte[] key = aes.generateNewKey().getEncoded();
078         * String base64 = Base64.encodeToString(key);
079         * </pre>
080         * The value of 'base64' was copied-n-pasted here:
081         */
082        private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
083    
084        /**
085         * Serializer to use for converting PrincipalCollection instances to/from byte arrays
086         */
087        private Serializer<PrincipalCollection> serializer;
088    
089        /**
090         * Cipher to use for encrypting/decrypting serialized byte arrays for added security
091         */
092        private CipherService cipherService;
093    
094        /**
095         * Cipher encryption key to use with the Cipher when encrypting data
096         */
097        private byte[] encryptionCipherKey;
098    
099        /**
100         * Cipher decryption key to use with the Cipher when decrypting data
101         */
102        private byte[] decryptionCipherKey;
103    
104        /**
105         * Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and
106         * an {@link AesCipherService} as the {@link #getCipherService() cipherService}.
107         */
108        public AbstractRememberMeManager() {
109            this.serializer = new DefaultSerializer<PrincipalCollection>();
110            this.cipherService = new AesCipherService();
111            setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
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.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 != null && (token instanceof RememberMeAuthenticationToken) &&
279                    ((RememberMeAuthenticationToken) token).isRememberMe();
280        }
281    
282        /**
283         * Reacts to the successful login attempt by first always {@link #forgetIdentity(Subject) forgetting} any previously
284         * stored identity.  Then if the {@code token}
285         * {@link #isRememberMe(org.apache.shiro.authc.AuthenticationToken) is a RememberMe} token, the associated identity
286         * will be {@link #rememberIdentity(org.apache.shiro.subject.Subject, org.apache.shiro.authc.AuthenticationToken, 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 (log.isDebugEnabled()) {
302                    log.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            try {
395                byte[] bytes = getRememberedSerializedIdentity(subjectContext);
396                //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
397                if (bytes != null && bytes.length > 0) {
398                    principals = convertBytesToPrincipals(bytes, subjectContext);
399                }
400            } catch (RuntimeException re) {
401                principals = onRememberedPrincipalFailure(re, subjectContext);
402            }
403    
404            return principals;
405        }
406    
407        /**
408         * Based on the given subject context data, retrieves the previously persisted serialized identity, or
409         * {@code null} if there is no available data.  The context map is usually populated by a {@link Subject.Builder}
410         * implementation.  See the {@link SubjectFactory} class constants for Shiro's known map keys.
411         *
412         * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
413         *                       is being used to construct a {@link Subject} instance.  To be used to assist with data
414         *                       lookup.
415         * @return the previously persisted serialized identity, or {@code null} if there is no available data for the
416         *         Subject.
417         */
418        protected abstract byte[] getRememberedSerializedIdentity(SubjectContext subjectContext);
419    
420        /**
421         * If a {@link #getCipherService() cipherService} is available, it will be used to first decrypt the byte array.
422         * Then the bytes are then {@link #deserialize(byte[]) deserialized} and then returned.
423         *
424         * @param bytes          the bytes to decrypt if necessary and then deserialize.
425         * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
426         *                       is being used to construct a {@link Subject} instance.
427         * @return the de-serialized and possibly decrypted principals
428         */
429        protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
430            if (getCipherService() != null) {
431                bytes = decrypt(bytes);
432            }
433            return deserialize(bytes);
434        }
435    
436        /**
437         * Called when an exception is thrown while trying to retrieve principals.  The default implementation logs a
438         * debug message and forgets ('unremembers') the problem identity by calling
439         * {@link #forgetIdentity(SubjectContext) forgetIdentity(context)} and then immediately re-throws the
440         * exception to allow the calling component to react accordingly.
441         * <p/>
442         * This method implementation never returns an
443         * object - it always rethrows, but can be overridden by subclasses for custom handling behavior.
444         * <p/>
445         * This most commonly would be called when an encryption key is updated and old principals are retrieved that have
446         * been encrypted with the previous key.
447         *
448         * @param e       the exception that was thrown.
449         * @param context the contextual data, usually provided by a {@link Subject.Builder} implementation, that
450         *                is being used to construct a {@link Subject} instance.
451         * @return nothing - the original {@code RuntimeException} is propagated in all cases.
452         */
453        protected PrincipalCollection onRememberedPrincipalFailure(RuntimeException e, SubjectContext context) {
454            if (log.isDebugEnabled()) {
455                log.debug("There was a failure while trying to retrieve remembered principals.  This could be due to a " +
456                        "configuration problem or corrupted principals.  This could also be due to a recently " +
457                        "changed encryption key.  The remembered identity will be forgotten and not used for this " +
458                        "request.", e);
459            }
460            forgetIdentity(context);
461            //propagate - security manager implementation will handle and warn appropriately
462            throw e;
463        }
464    
465        /**
466         * Encrypts the byte array by using the configured {@link #getCipherService() cipherService}.
467         *
468         * @param serialized the serialized object byte array to be encrypted
469         * @return an encrypted byte array returned by the configured {@link #getCipherService () cipher}.
470         */
471        protected byte[] encrypt(byte[] serialized) {
472            byte[] value = serialized;
473            CipherService cipherService = getCipherService();
474            if (cipherService != null) {
475                ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
476                value = byteSource.getBytes();
477            }
478            return value;
479        }
480    
481        /**
482         * Decrypts the byte array using the configured {@link #getCipherService() cipherService}.
483         *
484         * @param encrypted the encrypted byte array to decrypt
485         * @return the decrypted byte array returned by the configured {@link #getCipherService () cipher}.
486         */
487        protected byte[] decrypt(byte[] encrypted) {
488            byte[] serialized = encrypted;
489            CipherService cipherService = getCipherService();
490            if (cipherService != null) {
491                ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
492                serialized = byteSource.getBytes();
493            }
494            return serialized;
495        }
496    
497        /**
498         * Serializes the given {@code principals} by serializing them to a byte array by using the
499         * {@link #getSerializer() serializer}'s {@link Serializer#serialize(Object) serialize} method.
500         *
501         * @param principals the principal collection to serialize to a byte array
502         * @return the serialized principal collection in the form of a byte array
503         */
504        protected byte[] serialize(PrincipalCollection principals) {
505            return getSerializer().serialize(principals);
506        }
507    
508        /**
509         * De-serializes the given byte array by using the {@link #getSerializer() serializer}'s
510         * {@link Serializer#deserialize deserialize} method.
511         *
512         * @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array
513         * @return the de-serialized (reconstituted) {@code PrincipalCollection}
514         */
515        protected PrincipalCollection deserialize(byte[] serializedIdentity) {
516            return getSerializer().deserialize(serializedIdentity);
517        }
518    
519        /**
520         * Reacts to a failed login by immediately {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgetting} any
521         * previously remembered identity.  This is an additional security feature to prevent any remenant identity data
522         * from being retained in case the authentication attempt is not being executed by the expected user.
523         *
524         * @param subject the subject which executed the failed login attempt
525         * @param token   the authentication token resulting in a failed login attempt - ignored by this implementation
526         * @param ae      the exception thrown as a result of the failed login attempt - ignored by this implementation
527         */
528        public void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae) {
529            forgetIdentity(subject);
530        }
531    
532        /**
533         * Reacts to a subject logging out of the application and immediately
534         * {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgets} any previously stored identity and returns.
535         *
536         * @param subject the subject logging out.
537         */
538        public void onLogout(Subject subject) {
539            forgetIdentity(subject);
540        }
541    }