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