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 * @since 0.9
063 */
064 public 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 * The following Base64 string was generated by auto-generating an AES Key:
073 * <pre>
074 * AesCipherService aes = new AesCipherService();
075 * byte[] key = aes.generateNewKey().getEncoded();
076 * String base64 = Base64.encodeToString(key);
077 * </pre>
078 * The value of 'base64' was copied-n-pasted here:
079 */
080 private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
081
082 /**
083 * Serializer to use for converting PrincipalCollection instances to/from byte arrays
084 */
085 private Serializer<PrincipalCollection> serializer;
086
087 /**
088 * Cipher to use for encrypting/decrypting serialized byte arrays for added security
089 */
090 private CipherService cipherService;
091
092 /**
093 * Cipher encryption key to use with the Cipher when encrypting data
094 */
095 private byte[] encryptionCipherKey;
096
097 /**
098 * Cipher decryption key to use with the Cipher when decrypting data
099 */
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 }