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