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 * Serializer to use for converting PrincipalCollection instances to/from byte arrays 73 */ 74 private Serializer<PrincipalCollection> serializer; 75 76 /** 77 * Cipher to use for encrypting/decrypting serialized byte arrays for added security 78 */ 79 private CipherService cipherService; 80 81 /** 82 * Cipher encryption key to use with the Cipher when encrypting data 83 */ 84 private byte[] encryptionCipherKey; 85 86 /** 87 * Cipher decryption key to use with the Cipher when decrypting data 88 */ 89 private byte[] decryptionCipherKey; 90 91 /** 92 * Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and 93 * an {@link AesCipherService} as the {@link #getCipherService() cipherService}. 94 */ 95 public AbstractRememberMeManager() { 96 this.serializer = new DefaultSerializer<PrincipalCollection>(); 97 AesCipherService cipherService = new AesCipherService(); 98 this.cipherService = cipherService; 99 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 * warning 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 443 if (log.isWarnEnabled()) { 444 String message = "There was a failure while trying to retrieve remembered principals. This could be due to a " + 445 "configuration problem or corrupted principals. This could also be due to a recently " + 446 "changed encryption key, if you are using a shiro.ini file, this property would be " + 447 "'securityManager.rememberMeManager.cipherKey' see: http://shiro.apache.org/web.html#Web-RememberMeServices. " + 448 "The remembered identity will be forgotten and not used for this request."; 449 log.warn(message); 450 } 451 forgetIdentity(context); 452 //propagate - security manager implementation will handle and warn appropriately 453 throw e; 454 } 455 456 /** 457 * Encrypts the byte array by using the configured {@link #getCipherService() cipherService}. 458 * 459 * @param serialized the serialized object byte array to be encrypted 460 * @return an encrypted byte array returned by the configured {@link #getCipherService () cipher}. 461 */ 462 protected byte[] encrypt(byte[] serialized) { 463 byte[] value = serialized; 464 CipherService cipherService = getCipherService(); 465 if (cipherService != null) { 466 ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey()); 467 value = byteSource.getBytes(); 468 } 469 return value; 470 } 471 472 /** 473 * Decrypts the byte array using the configured {@link #getCipherService() cipherService}. 474 * 475 * @param encrypted the encrypted byte array to decrypt 476 * @return the decrypted byte array returned by the configured {@link #getCipherService () cipher}. 477 */ 478 protected byte[] decrypt(byte[] encrypted) { 479 byte[] serialized = encrypted; 480 CipherService cipherService = getCipherService(); 481 if (cipherService != null) { 482 ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey()); 483 serialized = byteSource.getBytes(); 484 } 485 return serialized; 486 } 487 488 /** 489 * Serializes the given {@code principals} by serializing them to a byte array by using the 490 * {@link #getSerializer() serializer}'s {@link Serializer#serialize(Object) serialize} method. 491 * 492 * @param principals the principal collection to serialize to a byte array 493 * @return the serialized principal collection in the form of a byte array 494 */ 495 protected byte[] serialize(PrincipalCollection principals) { 496 return getSerializer().serialize(principals); 497 } 498 499 /** 500 * De-serializes the given byte array by using the {@link #getSerializer() serializer}'s 501 * {@link Serializer#deserialize deserialize} method. 502 * 503 * @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array 504 * @return the de-serialized (reconstituted) {@code PrincipalCollection} 505 */ 506 protected PrincipalCollection deserialize(byte[] serializedIdentity) { 507 return getSerializer().deserialize(serializedIdentity); 508 } 509 510 /** 511 * Reacts to a failed login by immediately {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgetting} any 512 * previously remembered identity. This is an additional security feature to prevent any remenant identity data 513 * from being retained in case the authentication attempt is not being executed by the expected user. 514 * 515 * @param subject the subject which executed the failed login attempt 516 * @param token the authentication token resulting in a failed login attempt - ignored by this implementation 517 * @param ae the exception thrown as a result of the failed login attempt - ignored by this implementation 518 */ 519 public void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae) { 520 forgetIdentity(subject); 521 } 522 523 /** 524 * Reacts to a subject logging out of the application and immediately 525 * {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgets} any previously stored identity and returns. 526 * 527 * @param subject the subject logging out. 528 */ 529 public void onLogout(Subject subject) { 530 forgetIdentity(subject); 531 } 532 }