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.crypto.cipher.AesCipherService; 26 import org.apache.shiro.crypto.cipher.ByteSourceBroker; 27 import org.apache.shiro.crypto.cipher.CipherService; 28 import org.apache.shiro.lang.io.DefaultSerializer; 29 import org.apache.shiro.lang.io.Serializer; 30 import org.apache.shiro.lang.util.ByteSource; 31 import org.apache.shiro.lang.util.ByteUtils; 32 import org.apache.shiro.subject.PrincipalCollection; 33 import org.apache.shiro.subject.Subject; 34 import org.apache.shiro.subject.SubjectContext; 35 import org.slf4j.Logger; 36 import org.slf4j.LoggerFactory; 37 38 import java.util.function.Supplier; 39 40 /** 41 * Abstract implementation of the {@code RememberMeManager} interface that handles 42 * {@link #setSerializer(Serializer) serialization} and 43 * {@link #setCipherService encryption} of the remembered user identity. 44 * <p/> 45 * The remembered identity storage location and details are left to subclasses. 46 * <h2>Default encryption key</h2> 47 * This implementation uses an {@link AesCipherService AesCipherService} for strong encryption by default. It also 48 * uses a default generated symmetric key to both encrypt and decrypt data. As AES is a symmetric cipher, the same 49 * {@code key} is used to both encrypt and decrypt data, BUT NOTE: 50 * <p/> 51 * Because Shiro is an open-source project, if anyone knew that you were using Shiro's default 52 * {@code key}, they could download/view the source, and with enough effort, reconstruct the {@code key} 53 * and decode encrypted data at will. 54 * <p/> 55 * Of course, this key is only really used to encrypt the remembered {@code PrincipalCollection} which is typically 56 * a user id or username. So if you do not consider that sensitive information, and you think the default key still 57 * makes things 'sufficiently difficult', then you can ignore this issue. 58 * <p/> 59 * However, if you do feel this constitutes sensitive information, it is recommended that you provide your own 60 * {@code key} via the {@link #setCipherKey setCipherKey} method to a key known only to your application, 61 * guaranteeing that no third party can decrypt your data. You can generate your own key by calling the 62 * {@code CipherService}'s {@link AesCipherService#generateNewKey() generateNewKey} method 63 * and using that result as the {@link #setCipherKey cipherKey} configuration attribute. 64 * 65 * @since 0.9 66 */ 67 public abstract class AbstractRememberMeManager implements RememberMeManager { 68 69 /** 70 * private inner log instance. 71 */ 72 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRememberMeManager.class); 73 74 /** 75 * Serializer to use for converting PrincipalCollection instances to/from byte arrays 76 */ 77 private Serializer<PrincipalCollection> serializer = new DefaultSerializer<>(); 78 79 /** 80 * Cipher to use for encrypting/decrypting serialized byte arrays for added security 81 */ 82 private CipherService cipherService = new AesCipherService(); 83 ; 84 85 /** 86 * Cipher encryption key to use with the Cipher when encrypting data 87 */ 88 private byte[] encryptionCipherKey; 89 90 /** 91 * Cipher decryption key to use with the Cipher when decrypting data 92 */ 93 private byte[] decryptionCipherKey; 94 95 /** 96 * Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and 97 * an {@link AesCipherService} as the {@link #getCipherService() cipherService}. 98 */ 99 public AbstractRememberMeManager() { 100 setCipherKey(((AesCipherService) cipherService).generateNewKey().getEncoded()); 101 } 102 103 /** 104 * Constructor. Pass keySupplier that supplies encryption key 105 * 106 * @param keySupplier 107 * @since 2.0 108 */ 109 public AbstractRememberMeManager(Supplier<byte[]> keySupplier) { 110 this(); 111 setCipherKey(keySupplier.get()); 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.lang.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 instanceof RememberMeAuthenticationToken && ((RememberMeAuthenticationToken) token).isRememberMe(); 279 } 280 281 /** 282 * Reacts to the successful login attempt by first always {@link #forgetIdentity(Subject) forgetting} any previously 283 * stored identity. Then if the {@code token} 284 * {@link #isRememberMe(org.apache.shiro.authc.AuthenticationToken) is a RememberMe} token, the associated identity 285 * will be {@link #rememberIdentity(org.apache.shiro.subject.Subject, org.apache.shiro.authc.AuthenticationToken, 286 * 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 (LOGGER.isDebugEnabled()) { 302 LOGGER.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 byte[] bytes = null; 395 try { 396 bytes = getRememberedSerializedIdentity(subjectContext); 397 //SHIRO-138 - only call convertBytesToPrincipals if bytes exist: 398 if (bytes != null && bytes.length > 0) { 399 principals = convertBytesToPrincipals(bytes, subjectContext); 400 } 401 } catch (RuntimeException re) { 402 principals = onRememberedPrincipalFailure(re, subjectContext); 403 } finally { 404 ByteUtils.wipe(bytes); 405 } 406 407 return principals; 408 } 409 410 /** 411 * Based on the given subject context data, retrieves the previously persisted serialized identity, or 412 * {@code null} if there is no available data. The context map is usually populated by a {@link Subject.Builder} 413 * implementation. See the {@link SubjectFactory} class constants for Shiro's known map keys. 414 * 415 * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that 416 * is being used to construct a {@link Subject} instance. To be used to assist with data 417 * lookup. 418 * @return the previously persisted serialized identity, or {@code null} if there is no available data for the 419 * Subject. 420 */ 421 protected abstract byte[] getRememberedSerializedIdentity(SubjectContext subjectContext); 422 423 /** 424 * If a {@link #getCipherService() cipherService} is available, it will be used to first decrypt the byte array. 425 * Then the bytes are then {@link #deserialize(byte[]) deserialized} and then returned. 426 * 427 * @param bytes the bytes to decrypt if necessary and then deserialize. 428 * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that 429 * is being used to construct a {@link Subject} instance. 430 * @return the deserialized and possibly decrypted principals 431 */ 432 protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) { 433 if (getCipherService() != null) { 434 bytes = decrypt(bytes); 435 } 436 return deserialize(bytes); 437 } 438 439 /** 440 * Called when an exception is thrown while trying to retrieve principals. The default implementation logs a 441 * warning message and forgets ('unremembers') the problem identity by calling 442 * {@link #forgetIdentity(SubjectContext) forgetIdentity(context)} and then immediately re-throws the 443 * exception to allow the calling component to react accordingly. 444 * <p/> 445 * This method implementation never returns an 446 * object - it always rethrows, but can be overridden by subclasses for custom handling behavior. 447 * <p/> 448 * This most commonly would be called when an encryption key is updated and old principals are retrieved that have 449 * been encrypted with the previous key. 450 * 451 * @param e the exception that was thrown. 452 * @param context the contextual data, usually provided by a {@link Subject.Builder} implementation, that 453 * is being used to construct a {@link Subject} instance. 454 * @return nothing - the original {@code RuntimeException} is propagated in all cases. 455 */ 456 protected PrincipalCollection onRememberedPrincipalFailure(RuntimeException e, SubjectContext context) { 457 458 if (LOGGER.isWarnEnabled()) { 459 String message = "There was a failure while trying to retrieve remembered principals. This could be due to a " 460 + "configuration problem or corrupted principals. This could also be due to a recently " 461 + "changed encryption key, if you are using a shiro.ini file, this property would be " 462 + "'securityManager.rememberMeManager.cipherKey'" 463 + "see: http://shiro.apache.org/web.html#Web-RememberMeServices. " 464 + "The remembered identity will be forgotten and not used for this request."; 465 LOGGER.warn(message); 466 } 467 forgetIdentity(context); 468 //propagate - security manager implementation will handle and warn appropriately 469 throw e; 470 } 471 472 /** 473 * Encrypts the byte array by using the configured {@link #getCipherService() cipherService}. 474 * 475 * @param serialized the serialized object byte array to be encrypted 476 * @return an encrypted byte array returned by the configured {@link #getCipherService () cipher}. 477 */ 478 protected byte[] encrypt(byte[] serialized) { 479 byte[] value = serialized; 480 CipherService cipherService = getCipherService(); 481 if (cipherService != null) { 482 ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey()); 483 value = byteSource.getBytes(); 484 } 485 return value; 486 } 487 488 /** 489 * Decrypts the byte array using the configured {@link #getCipherService() cipherService}. 490 * 491 * @param encrypted the encrypted byte array to decrypt 492 * @return the decrypted byte array returned by the configured {@link #getCipherService () cipher}. 493 */ 494 protected byte[] decrypt(byte[] encrypted) { 495 byte[] serialized = encrypted; 496 CipherService cipherService = getCipherService(); 497 if (cipherService != null) { 498 ByteSourceBroker broker = cipherService.decrypt(encrypted, getDecryptionCipherKey()); 499 serialized = broker.getClonedBytes(); 500 } 501 return serialized; 502 } 503 504 /** 505 * Serializes the given {@code principals} by serializing them to a byte array by using the 506 * {@link #getSerializer() serializer}'s {@link Serializer#serialize(Object) serialize} method. 507 * 508 * @param principals the principal collection to serialize to a byte array 509 * @return the serialized principal collection in the form of a byte array 510 */ 511 protected byte[] serialize(PrincipalCollection principals) { 512 return getSerializer().serialize(principals); 513 } 514 515 /** 516 * Deserializes the given byte array by using the {@link #getSerializer() serializer}'s 517 * {@link Serializer#deserialize deserialize} method. 518 * 519 * @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array 520 * @return the deserialized (reconstituted) {@code PrincipalCollection} 521 */ 522 protected PrincipalCollection deserialize(byte[] serializedIdentity) { 523 return getSerializer().deserialize(serializedIdentity); 524 } 525 526 /** 527 * Reacts to a failed login by immediately {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgetting} any 528 * previously remembered identity. This is an additional security feature to prevent any remnant identity data 529 * from being retained in case the authentication attempt is not being executed by the expected user. 530 * 531 * @param subject the subject which executed the failed login attempt 532 * @param token the authentication token resulting in a failed login attempt - ignored by this implementation 533 * @param ae the exception thrown as a result of the failed login attempt - ignored by this implementation 534 */ 535 public void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae) { 536 forgetIdentity(subject); 537 } 538 539 /** 540 * Reacts to a subject logging out of the application and immediately 541 * {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgets} any previously stored identity and returns. 542 * 543 * @param subject the subject logging out. 544 */ 545 public void onLogout(Subject subject) { 546 forgetIdentity(subject); 547 } 548 }