You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2010/02/11 23:03:15 UTC

svn commit: r909163 - in /incubator/shiro/trunk: core/src/main/java/org/apache/shiro/mgt/ web/src/main/java/org/apache/shiro/web/ web/src/test/java/org/apache/shiro/web/

Author: lhazlewood
Date: Thu Feb 11 22:03:14 2010
New Revision: 909163

URL: http://svn.apache.org/viewvc?rev=909163&view=rev
Log:
SHIRO-109 - Changed RememberMeManager interface and implementations to not require threadlocal data

Modified:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/RememberMeManager.java
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/WebRememberMeManager.java
    incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/AbstractWebSecurityManagerTest.java
    incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/WebRememberMeManagerTest.java

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java?rev=909163&r1=909162&r2=909163&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java Thu Feb 11 22:03:14 2010
@@ -27,19 +27,20 @@
 import org.apache.shiro.crypto.BlowfishCipher;
 import org.apache.shiro.crypto.Cipher;
 import org.apache.shiro.io.DefaultSerializer;
-import org.apache.shiro.io.SerializationException;
 import org.apache.shiro.io.Serializer;
 import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Map;
 
 /**
- * Abstract implementation of the <code>RememberMeManager</code> interface that handles
+ * Abstract implementation of the {@code RememberMeManager} interface that handles
  * {@link #setSerializer(org.apache.shiro.io.Serializer) serialization} and
  * {@link #setCipher(org.apache.shiro.crypto.Cipher) encryption} of the remembered user identity.
  * <p/>
- * The remembered identity storage location is implementation-specific.
+ * The remembered identity storage location and details are left to subclasses.
  *
  * @author Les Hazlewood
  * @author Jeremy Haile
@@ -47,75 +48,242 @@
  */
 public abstract class AbstractRememberMeManager implements RememberMeManager {
 
-    //TODO - complete JavaDoc
-
     /**
      * private inner log instance.
      */
     private static final Logger log = LoggerFactory.getLogger(AbstractRememberMeManager.class);
 
-    private Serializer<PrincipalCollection> serializer = new DefaultSerializer<PrincipalCollection>();
-    private Cipher cipher = new BlowfishCipher();
-    private byte[] encryptionCipherKey = null;
-    private byte[] decryptionCipherKey = null;
+    /**
+     * Serializer to use for converting PrincipalCollection instances to/from byte arrays
+     */
+    private Serializer<PrincipalCollection> serializer;
+
+    /**
+     * Cipher to use for encrypting/decrypting serialized byte arrays for added security
+     */
+    private Cipher cipher;
+
+    /**
+     * Cipher encryption key to use with the Cipher when encrypting data
+     */
+    private byte[] encryptionCipherKey;
 
+    /**
+     * Cipher decryption key to use with the Cipher when decrypting data
+     */
+    private byte[] decryptionCipherKey;
+
+    /**
+     * Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and
+     * a {@link BlowfishCipher BlowfishCipher} as the {@link #getCipher() cipher}.
+     */
     public AbstractRememberMeManager() {
+        this.serializer = new DefaultSerializer<PrincipalCollection>();
+        this.cipher = new BlowfishCipher();
     }
 
+    /**
+     * Returns the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
+     * persistent remember me storage.
+     * <p/>
+     * Unless overridden by the {@link #setSerializer} method, the default instance is a
+     * {@link org.apache.shiro.io.DefaultSerializer}.
+     *
+     * @return the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
+     *         persistent remember me storage.
+     */
     public Serializer<PrincipalCollection> getSerializer() {
         return serializer;
     }
 
+    /**
+     * Sets the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
+     * persistent remember me storage.
+     * <p/>
+     * Unless overridden by this method, the default instance is a {@link DefaultSerializer}.
+     *
+     * @param serializer the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances
+     *                   for persistent remember me storage.
+     */
     public void setSerializer(Serializer<PrincipalCollection> serializer) {
         this.serializer = serializer;
     }
 
+    /**
+     * Returns the {@code Cipher} to use for encrypting and decrypting serialized identity data to prevent easy
+     * inspection of Subject identity data.
+     * <p/>
+     * Unless overridden by the {@link #setCipher} method, the default instance is a {@link BlowfishCipher}.
+     *
+     * @return the {@code Cipher} to use for encrypting and decrypting serialized identity data to prevent easy
+     *         inspection of Subject identity data
+     */
     public Cipher getCipher() {
         return cipher;
     }
 
+    /**
+     * Sets the {@code Cipher} to use for encrypting and decrypting serialized identity data to prevent easy
+     * inspection of Subject identity data.
+     * <p/>
+     * If the cipher is an symmetric cipher (using the same key for both encryption and decryption), you
+     * should set your key via one of the three following methods:
+     * <ul>
+     * <li>{@link #setCipherKey(byte[])}</li>
+     * <li>{@link #setCipherKeyBase64(String)}, or</li>
+     * <li>{@link #setCipherKeyHex(String)}</li>
+     * </ul>
+     * <p/>
+     * If the cipher is an asymmetric cipher (different keys for encryption and decryption, such as public/private key
+     * pairs), you should set your encryption key via one of these methods:
+     * <ul>
+     * <li>{@link #setEncryptionCipherKey(byte[])}</li>
+     * <li>{@link #setEncryptionCipherKeyHex(String)}, or</li>
+     * <li>{@link #setEncryptionCipherKeyBase64(String)}</li>
+     * </ul>
+     * Similarly, you can set the decryption key via one of these methods:
+     * <ul>
+     * <li>{@link #setDecryptionCipherKey(byte[])}</li>
+     * <li>{@link #setDecryptionCipherKeyHex(String)}, or</li>
+     * <li>{@link #setDecryptionCipherKeyBase64(String)}</li>
+     * </ul>
+     * <p/>
+     * <b>N.B.</b> Unless overridden by this method, the default Cipher instance is a
+     * {@link BlowfishCipher}.  Shiro's {@code BlowfishCipher} already has a configured symmetric key to use for
+     * encryption and decryption, but it is recommended to provide your own for added security.  See the
+     * {@link BlowfishCipher} class-level JavaDoc for more information and why it might be good to provide your own.
+     *
+     * @param cipher the {@code Cipher} to use for encrypting and decrypting serialized identity data to prevent easy
+     *               inspection of Subject identity data.
+     */
     public void setCipher(Cipher cipher) {
         this.cipher = cipher;
     }
 
+    /**
+     * Returns the cipher key to use for encryption operations.
+     *
+     * @return the cipher key to use for encryption operations.
+     * @see #setCipher for a description of the various {@code get/set*Key} methods.
+     */
     public byte[] getEncryptionCipherKey() {
         return encryptionCipherKey;
     }
 
+    /**
+     * Sets the encryption key to use for encryption operations.  If setting the key via text configuration mechanisms,
+     * the {@link #setEncryptionCipherKeyHex(String) encryptionCipherKeyHex} or
+     * {@link #setEncryptionCipherKeyBase64(String) encryptionCipherKeyBase64} methods are probably more convenient.
+     *
+     * @param encryptionCipherKey the encryption key to use for encryption operations.
+     * @see #setCipher for a description of the various {@code get/set*Key} methods.
+     */
     public void setEncryptionCipherKey(byte[] encryptionCipherKey) {
         this.encryptionCipherKey = encryptionCipherKey;
     }
 
+    /**
+     * Convenience method that allows configuration of the encryption {@link Cipher cipher} key by specifying a
+     * {@code hex}-encoded string.  The string is {@code hex}-decoded and the resulting byte array is used
+     * as the {@link #setEncryptionCipherKey(byte[]) encryption cipher key}.
+     *
+     * @param hex hex-encoded encryption cipher key to decode into the raw encryption cipher key bytes.
+     * @see #setCipher for a description of the various {@code get/set*Key} methods.
+     */
     public void setEncryptionCipherKeyHex(String hex) {
         setEncryptionCipherKey(Hex.decode(hex));
     }
 
+    /**
+     * Convenience method that allows configuration of the encryption {@link Cipher cipher} key by specifying a
+     * {@code BASE 64}-encoded string.  The string is {@code BASE 64}-decoded and the resulting byte array is used
+     * as the {@link #setEncryptionCipherKey(byte[]) cipher key}.
+     *
+     * @param base64 base64-encoded encryption cipher key to decode into the raw encryption cipher key bytes
+     * @see #setCipher for a description of the various {@code get/set*Key} methods.
+     */
     public void setEncryptionCipherKeyBase64(String base64) {
         setEncryptionCipherKey(Base64.decode(base64));
     }
 
+    /**
+     * Returns the decryption cipher key to use for decryption operations.
+     *
+     * @return the cipher key to use for decryption operations.
+     * @see #setCipher for a description of the various {@code get/set*Key} methods.
+     */
     public byte[] getDecryptionCipherKey() {
         return decryptionCipherKey;
     }
 
+    /**
+     * Sets the decryption key to use for decryption operations.  If setting the key via text configuration mechanisms,
+     * the {@link #setDecryptionCipherKeyHex(String) decryptionCipherKeyHex} or
+     * {@link #setDecryptionCipherKeyBase64(String) decryptionCipherKeyBase64} methods are probably more convenient.
+     *
+     * @param decryptionCipherKey the decryption key to use for decryption operations.
+     * @see #setCipher for a description of the various {@code get/set*Key} methods.
+     */
     public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
         this.decryptionCipherKey = decryptionCipherKey;
     }
 
+    /**
+     * Convenience method that allows configuration of the decryption {@link Cipher cipher} key by specifying a
+     * {@code hex}-encoded string.  The string is {@code hex}-decoded and the resulting byte array is used
+     * as the {@link #setDecryptionCipherKey(byte[]) decryption cipher key}.
+     *
+     * @param hex hex-encoded decryption cipher key to decode into the raw decryption cipher key bytes.
+     * @see #setCipher for a description of the various {@code get/set*Key} methods.
+     */
     public void setDecryptionCipherKeyHex(String hex) {
         setDecryptionCipherKey(Hex.decode(hex));
     }
 
+    /**
+     * Convenience method that allows configuration of the decryption {@link Cipher cipher} key by specifying a
+     * {@code BASE 64}-encoded string.  The string is {@code BASE 64}-decoded and the resulting byte array is used
+     * as the {@link #setDecryptionCipherKey(byte[]) cipher key}.
+     *
+     * @param base64 base64-encoded decryption cipher key to decode into the raw decryption cipher key bytes
+     * @see #setCipher for a description of the various {@code get/set*Key} methods.
+     */
     public void setDecryptionCipherKeyBase64(String base64) {
         setDecryptionCipherKey(Base64.decode(base64));
     }
 
+    /**
+     * Convenience method that returns the cipher key to use for <em>both</em> encryption and decryption.
+     * <p/>
+     * <b>N.B.</b> This method can only be called if the underlying {@link #getCipher() cipher} is a symmetric cipher
+     * which by definition uses the same key for both encryption and decryption.  If using an asymmetric cipher
+     * (such as a public/private key pair), you cannot use this method, and should instead use the
+     * {@link #getEncryptionCipherKey()} and {@link #getDecryptionCipherKey()} methods individually.
+     * <p/>
+     * The default {@link BlowfishCipher} instance is a symmetric cipher, so this method can be used if you are using
+     * the default.
+     *
+     * @return the symmetric cipher key used for both encryption and decryption.
+     */
     public byte[] getCipherKey() {
         //Since this method should only be used with symmetric ciphers
         //(where the enc and dec keys are the same), either is fine, just return one of them:
         return getEncryptionCipherKey();
     }
 
+    /**
+     * Convenience method that sets the cipher key to use for <em>both</em> encryption and decryption.
+     * <p/>
+     * <b>N.B.</b> This method can only be called if the underlying {@link #getCipher() cipher} is a symmetric cipher
+     * which by definition uses the same key for both encryption and decryption.  If using an asymmetric cipher
+     * (such as a public/private key pair), you cannot use this method, and should instead use the
+     * {@link #setEncryptionCipherKey(byte[])} and {@link #setDecryptionCipherKey(byte[])} methods individually.
+     * <p/>
+     * The default {@link BlowfishCipher} instance is a symmetric cipher, so this method can be used if you are using
+     * the default.
+     *
+     * @param cipherKey the symmetric cipher key to use for both encryption and decryption.
+     */
     public void setCipherKey(byte[] cipherKey) {
         //Since this method should only be used in symmetric ciphers
         //(where the enc and dec keys are the same), set it on both:
@@ -123,34 +291,99 @@
         setDecryptionCipherKey(cipherKey);
     }
 
+    /**
+     * Convenience method that allows configuration of the (symmetric) {@link Cipher cipher} key by specifying a
+     * {@code hex}-encoded string.  The string is {@code hex}-decoded and the resulting byte array is used
+     * as the {@link #setCipherKey(byte[]) cipher key}.
+     * <p/>
+     * <b>N.B.</b> This is a convenience method to set <em>both</em> the {@link Cipher} encryption key and the
+     * decryption key and should only be called if using a symmetric cipher.  If using an asymmetric cipher (such
+     * as a public/private key pair) you cannot
+     * call this method and instead should use the {@link #setEncryptionCipherKeyHex(String)} and
+     * {@link #setDecryptionCipherKeyHex(String)} methods instead.
+     * <p/>
+     * The default {@link BlowfishCipher} instance is a symmetric cipher, so this method can be used if you are using
+     * the default.
+     *
+     * @param hex hex-encoded symmetric cipher key to decode into the raw cipher key bytes.
+     */
     public void setCipherKeyHex(String hex) {
         setCipherKey(Hex.decode(hex));
     }
 
+    /**
+     * Convenience method that allows configuration of the (symmetric) {@link Cipher cipher} key by specifying a
+     * {@code BASE 64}-encoded string.  The string is {@code BASE 64}-decoded and the resulting byte array is used
+     * as the {@link #setCipherKey(byte[]) cipher key}.
+     * <p/>
+     * <b>N.B.</b> This is a convenience method to set <em>both</em> the {@link Cipher} encryption key and the
+     * decryption key and should only be called if using a symmetric cipher.  If using an asymmetric cipher, you cannot
+     * call this method and instead should use the {@link #setEncryptionCipherKeyBase64(String)} and
+     * {@link #setDecryptionCipherKeyBase64(String)} methods instead.
+     * <p/>
+     * The default {@link BlowfishCipher} instance is a symmetric cipher, so this method can be used if you are using
+     * the default.
+     *
+     * @param base64 base64-encoded symmetric cipher key to decode into the raw cipher key bytes.
+     */
     public void setCipherKeyBase64(String base64) {
         setCipherKey(Base64.decode(base64));
     }
 
-    // Abstract methods to be implemented by subclasses
-    protected abstract void rememberSerializedIdentity(byte[] serialized);
-
-    protected abstract byte[] getSerializedRememberedIdentity();
-
-    protected abstract void forgetIdentity();
+    /**
+     * Forgets (removes) any remembered identity data for the subject being built by the specified {@code context}
+     * argument.  The context map is usually populated by a {@link Subject.Builder} implementation.  See the
+     * {@link SubjectFactory} class constants for Shiro's known map keys.
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.
+     */
+    protected abstract void forgetIdentity(Map subjectContext);
 
+    /**
+     * Forgets (removes) any remembered identity data for the specified {@link Subject} instance.
+     *
+     * @param subject the subject instance for which identity data should be forgotten from the underlying persistence
+     *                mechanism.
+     */
+    protected abstract void forgetIdentity(Subject subject);
 
+    /**
+     * Determines whether or not remember me services should be performed for the specified token.  This method returns
+     * {@code true} iff:
+     * <ol>
+     * <li>The token is not {@code null} and</li>
+     * <li>The token is an {@code instanceof} {@link RememberMeAuthenticationToken} and</li>
+     * <li>{@code token}.{@link org.apache.shiro.authc.RememberMeAuthenticationToken#isRememberMe() isRememberMe()} is
+     * {@code true}</li>
+     * </ol>
+     *
+     * @param token the authentication token submitted during the successful authentication attempt.
+     * @return true if remember me services should be performed as a result of the successful authentication attempt.
+     */
     protected boolean isRememberMe(AuthenticationToken token) {
         return token != null && (token instanceof RememberMeAuthenticationToken) &&
                 ((RememberMeAuthenticationToken) token).isRememberMe();
     }
 
-    public void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info) {
+    /**
+     * Reacts to the successful login attempt by first always {@link #forgetIdentity(Subject) forgetting} any previously
+     * stored identity.  Then if the {@code token}
+     * {@link #isRememberMe(org.apache.shiro.authc.AuthenticationToken) is a RememberMe} token, the associated identity
+     * will be {@link #rememberIdentity(org.apache.shiro.subject.Subject, org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) remembered}
+     * for later retrieval during a new user session.
+     *
+     * @param subject the subject for which the principals are being remembered.
+     * @param token   the token that resulted in a successful authentication attempt.
+     * @param info    the authentication info resulting from the successful authentication attempt.
+     */
+    public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
         //always clear any previous identity:
-        forgetIdentity(token);
+        forgetIdentity(subject);
 
-        //reset it if necessary:
+        //now save the new identity:
         if (isRememberMe(token)) {
-            rememberIdentity(token, info);
+            rememberIdentity(subject, token, info);
         } else {
             if (log.isDebugEnabled()) {
                 log.debug("AuthenticationToken did not indicate RememberMe is requested.  " +
@@ -159,83 +392,163 @@
         }
     }
 
-    public void rememberIdentity(AuthenticationToken submittedToken, AuthenticationInfo successfullyAuthenticated) {
-        rememberIdentity(successfullyAuthenticated);
+    /**
+     * Remembers a subject-unique identity for retrieval later.  This implementation first
+     * {@link #getIdentityToRemember resolves} the exact
+     * {@link PrincipalCollection principals} to remember.  It then remembers the principals by calling
+     * {@link #rememberIdentity(org.apache.shiro.subject.Subject, org.apache.shiro.subject.PrincipalCollection)}.
+     * <p/>
+     * This implementation ignores the {@link AuthenticationToken} argument, but it is available to subclasses if
+     * necessary for custom logic.
+     *
+     * @param subject   the subject for which the principals are being remembered.
+     * @param token     the token that resulted in a successful authentication attempt.
+     * @param authcInfo the authentication info resulting from the successful authentication attempt.
+     */
+    public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
+        PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
+        rememberIdentity(subject, principals);
     }
 
-    public void rememberIdentity(AuthenticationInfo successfullyAuthenticated) {
-        PrincipalCollection principals = getIdentityToRemember(successfullyAuthenticated);
-        rememberIdentity(principals);
+    /**
+     * Returns {@code info}.{@link org.apache.shiro.authc.AuthenticationInfo#getPrincipals() getPrincipals()} and
+     * ignores the {@link Subject} argument.
+     *
+     * @param subject the subject for which the principals are being remembered.
+     * @param info    the authentication info resulting from the successful authentication attempt.
+     * @return the {@code PrincipalCollection} to remember.
+     */
+    protected PrincipalCollection getIdentityToRemember(Subject subject, AuthenticationInfo info) {
+        return info.getPrincipals();
     }
 
-    protected PrincipalCollection getIdentityToRemember(AuthenticationInfo info) {
-        return info.getPrincipals();
+    /**
+     * Remembers the specified account principals by first
+     * {@link #convertPrincipalsToBytes(org.apache.shiro.subject.PrincipalCollection) converting} them to a byte
+     * array and then {@link #rememberSerializedIdentity(org.apache.shiro.subject.Subject, byte[]) remembers} that
+     * byte array.
+     *
+     * @param subject           the subject for which the principals are being remembered.
+     * @param accountPrincipals the principals to remember for retrieval later.
+     */
+    protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
+        byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
+        rememberSerializedIdentity(subject, bytes);
     }
 
-    protected void rememberIdentity(PrincipalCollection accountPrincipals) {
-        try {
-            byte[] bytes = serialize(accountPrincipals);
-            if (getCipher() != null) {
-                bytes = encrypt(bytes);
-            }
-            rememberSerializedIdentity(bytes);
-        } catch (SerializationException se) {
-            if (log.isWarnEnabled()) {
-                log.warn("Unable to serialize account principals [" + accountPrincipals + "].  Identity " +
-                        "cannot be remembered!  This is a non fatal exception as RememberMe identity services " +
-                        "are not considered critical and execution can continue as normal.  But please " +
-                        "investigate and resolve to prevent seeing this message again.", se);
-            }
+    /**
+     * Converts the given principal collection the byte array that will be persisted to be 'remembered' later.
+     * <p/>
+     * This implementation first {@link #serialize(org.apache.shiro.subject.PrincipalCollection) serializes} the
+     * principals to a byte array and then {@link #encrypt(byte[]) encrypts} that byte array.
+     *
+     * @param principals the {@code PrincipalCollection} to convert to a byte array
+     * @return the representative byte array to be persisted for remember me functionality.
+     */
+    protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
+        byte[] bytes = serialize(principals);
+        if (getCipher() != null) {
+            bytes = encrypt(bytes);
         }
+        return bytes;
     }
 
-    public PrincipalCollection getRememberedPrincipals() {
+    /**
+     * Persists the identity bytes to a persistent store for retrieval later via the
+     * {@link #getRememberedSerializedIdentity(java.util.Map)} method.
+     *
+     * @param subject    the Subject for which the identity is being serialized.
+     * @param serialized the serialized bytes to be persisted.
+     */
+    protected abstract void rememberSerializedIdentity(Subject subject, byte[] serialized);
+
+    /**
+     * Implements the interface method by first {@link #getRememberedSerializedIdentity(java.util.Map) acquiring}
+     * the remembered serialized byte array.  Then it {@link #convertBytesToPrincipals(byte[], java.util.Map) converts}
+     * them and returns the re-constituted {@link PrincipalCollection}.  If no remembered principals could be
+     * obtained, {@code null} is returned.
+     * <p/>
+     * If any exceptions are thrown, the {@link #onRememberedPrincipalFailure(RuntimeException, java.util.Map)} method
+     * is called to allow any necessary post-processing (such as immediately removing any previously remembered
+     * values for safety).
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.
+     * @return the remembered principals or {@code null} if none could be acquired.
+     */
+    public PrincipalCollection getRememberedPrincipals(Map subjectContext) {
         try {
+            byte[] bytes = getRememberedSerializedIdentity(subjectContext);
+            return convertBytesToPrincipals(bytes, subjectContext);
+        } catch (RuntimeException re) {
+            return onRememberedPrincipalFailure(re, subjectContext);
+        }
+    }
 
-            PrincipalCollection principals = null;
-            byte[] bytes = getSerializedRememberedIdentity();
-            if (bytes != null) {
-                if (getCipher() != null) {
-                    bytes = decrypt(bytes);
-                }
-                try {
-                    principals = deserialize(bytes);
-                } catch (SerializationException e) {
-                    if (log.isWarnEnabled()) {
-                        log.warn("Unable to deserialize stored identity byte array.  Remembered identity " +
-                                "cannot be reconstituted!  This is a non fatal exception as RememberMe identity services " +
-                                "are not considered critical and execution can continue as normal, but please " +
-                                "investigate and resolve to prevent seeing this message again.", e);
-                    }
-                }
-            }
-            return principals;
+    /**
+     * Based on the given subject context data, retrieves the previously persisted serialized identity, or
+     * {@code null} if there is no available data.  The context map is usually populated by a {@link Subject.Builder}
+     * implementation.  See the {@link SubjectFactory} class constants for Shiro's known map keys.
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.  To be used to assist with data
+     *                       lookup.
+     * @return the previously persisted serialized identity, or {@code null} if there is no available data for the
+     *         Subject.
+     */
+    protected abstract byte[] getRememberedSerializedIdentity(Map subjectContext);
 
-        } catch (Exception e) {
-            return onRememberedPrincipalFailure(e);
+    /**
+     * If a {@link #getCipher() cipher} is available, it will be used to first decrypt the byte array.  Then the
+     * bytes are then {@link #deserialize(byte[]) deserialized} and then returned.
+     *
+     * @param bytes          the bytes to decrypt if necessary and then deserialize.
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.
+     * @return the de-serialized and possibly decrypted principals
+     */
+    protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, Map subjectContext) {
+        if (getCipher() != null) {
+            bytes = decrypt(bytes);
         }
+        return deserialize(bytes);
     }
 
     /**
      * Called when an exception is thrown while trying to retrieve principals.  The default implementation logs a
-     * warning and forgets ('unremembers') the problem identity by calling {@link #forgetIdentity() forgetIdentity()}.
-     * This most commonly would occur when an encryption key is updated and old principals are retrieved that have
-     * been encrypted with the previous key.\
-     *
-     * @param e the exception that was thrown.
-     * @return <code>null</code> in all cases.
-     */
-    protected PrincipalCollection onRememberedPrincipalFailure(Exception e) {
-        if (log.isWarnEnabled()) {
-            log.warn("There was a failure while trying to retrieve remembered principals.  This could be due to a " +
+     * debug message and forgets ('unremembers') the problem identity by calling
+     * {@link #forgetIdentity(java.util.Map) forgetIdentity(context)} and then immediately re-throws the
+     * exception to allow the calling component to react accordingly.
+     * <p/>
+     * This method implementation never returns an
+     * object - it always rethrows, but can be overridden by subclasses for custom handling behavior.
+     * <p/>
+     * This most commonly would be called when an encryption key is updated and old principals are retrieved that have
+     * been encrypted with the previous key.
+     *
+     * @param e       the exception that was thrown.
+     * @param context the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                is being used to construct a {@link Subject} instance.
+     * @return nothing - the original {@code RuntimeException} is propagated in all cases.
+     */
+    protected PrincipalCollection onRememberedPrincipalFailure(RuntimeException e, Map context) {
+        if (log.isDebugEnabled()) {
+            log.debug("There was a failure while trying to retrieve remembered principals.  This could be due to a " +
                     "configuration problem or corrupted principals.  This could also be due to a recently " +
                     "changed encryption key.  The remembered identity will be forgotten and not used for this " +
                     "request.", e);
         }
-        forgetIdentity();
-        return null;
+        forgetIdentity(context);
+        //propagate - security manager implementation will handle and warn appropriately
+        throw e;
     }
 
+    /**
+     * Encrypts the byte array by using the configured {@link #getCipher() cipher}.
+     *
+     * @param serialized the serialized object byte array to be encrypted
+     * @return an encrypted byte array returned by the configured {@link #getCipher() cipher}.
+     */
     protected byte[] encrypt(byte[] serialized) {
         byte[] value = serialized;
         Cipher cipher = getCipher();
@@ -245,6 +558,12 @@
         return value;
     }
 
+    /**
+     * Decrypts the byte array using the configured {@link #getCipher() cipher}.
+     *
+     * @param encrypted the encrypted byte array to decrypt
+     * @return the decrypted byte array returned by the configured {@link #getCipher() cipher}.
+     */
     protected byte[] decrypt(byte[] encrypted) {
         byte[] serialized = encrypted;
         Cipher cipher = getCipher();
@@ -254,29 +573,48 @@
         return serialized;
     }
 
-
+    /**
+     * Serializes the given {@code principals} by serializing them to a byte array by using the
+     * {@link #getSerializer() serializer}'s {@link Serializer#serialize(Object) serialize} method.
+     *
+     * @param principals the principal collection to serialize to a byte array
+     * @return the serialized principal collection in the form of a byte array
+     */
     protected byte[] serialize(PrincipalCollection principals) {
         return getSerializer().serialize(principals);
     }
 
+    /**
+     * De-serializes the given byte array by using the {@link #getSerializer() serializer}'s
+     * {@link Serializer#deserialize deserialize} method.
+     *
+     * @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array
+     * @return the de-serialized (reconstituted) {@code PrincipalCollection}
+     */
     protected PrincipalCollection deserialize(byte[] serializedIdentity) {
         return getSerializer().deserialize(serializedIdentity);
     }
 
-    public void onFailedLogin(AuthenticationToken token, AuthenticationException ae) {
-        forgetIdentity(token, ae);
-    }
-
-    public void onLogout(PrincipalCollection subjectPrincipals) {
-        forgetIdentity();
-    }
-
-    protected void forgetIdentity(AuthenticationToken token, AuthenticationException ae) {
-        forgetIdentity(token);
+    /**
+     * Reacts to a failed login by immediately {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgetting} any
+     * previously remembered identity.  This is an additional security feature to prevent any remenant identity data
+     * from being retained in case the authentication attempt is not being executed by the expected user.
+     *
+     * @param subject the subject which executed the failed login attempt
+     * @param token   the authentication token resulting in a failed login attempt - ignored by this implementation
+     * @param ae      the exception thrown as a result of the failed login attempt - ignored by this implementation
+     */
+    public void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae) {
+        forgetIdentity(subject);
     }
 
-    protected void forgetIdentity(AuthenticationToken token) {
-        forgetIdentity();
+    /**
+     * Reacts to a subject logging out of the application and immediately
+     * {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgets} any previously stored identity and returns.
+     *
+     * @param subject the subject logging out.
+     */
+    public void onLogout(Subject subject) {
+        forgetIdentity(subject);
     }
-
 }

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java?rev=909163&r1=909162&r2=909163&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java Thu Feb 11 22:03:14 2010
@@ -220,11 +220,11 @@
         getSubjectBinder().bind(subject);
     }
 
-    protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info) {
+    protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
         RememberMeManager rmm = getRememberMeManager();
         if (rmm != null) {
             try {
-                rmm.onSuccessfulLogin(token, info);
+                rmm.onSuccessfulLogin(subject, token, info);
             } catch (Exception e) {
                 if (log.isWarnEnabled()) {
                     String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
@@ -242,11 +242,11 @@
         }
     }
 
-    protected void rememberMeFailedLogin(AuthenticationToken token, AuthenticationException ex) {
+    protected void rememberMeFailedLogin(AuthenticationToken token, AuthenticationException ex, Subject subject) {
         RememberMeManager rmm = getRememberMeManager();
         if (rmm != null) {
             try {
-                rmm.onFailedLogin(token, ex);
+                rmm.onFailedLogin(subject, token, ex);
             } catch (Exception e) {
                 if (log.isWarnEnabled()) {
                     String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
@@ -258,16 +258,16 @@
         }
     }
 
-    protected void rememberMeLogout(PrincipalCollection subjectPrincipals) {
+    protected void rememberMeLogout(Subject subject) {
         RememberMeManager rmm = getRememberMeManager();
         if (rmm != null) {
             try {
-                rmm.onLogout(subjectPrincipals);
+                rmm.onLogout(subject);
             } catch (Exception e) {
                 if (log.isWarnEnabled()) {
                     String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
                             "] threw an exception during onLogout for subject with principals [" +
-                            subjectPrincipals + "]";
+                            (subject != null ? subject.getPrincipals() : null) + "]";
                     log.warn(msg, e);
                 }
             }
@@ -289,10 +289,9 @@
         AuthenticationInfo info;
         try {
             info = authenticate(token);
-            onSuccessfulLogin(token, info);
         } catch (AuthenticationException ae) {
             try {
-                onFailedLogin(token, ae);
+                onFailedLogin(token, ae, subject);
             } catch (Exception e) {
                 if (log.isInfoEnabled()) {
                     log.info("onFailedLogin(AuthenticationToken,AuthenticationException) method threw an " +
@@ -301,22 +300,25 @@
             }
             throw ae; //propagate
         }
-        Subject replaced = createSubject(token, info, subject);
+
+        Subject loggedIn = createSubject(token, info, subject);
         //TODO - is binding necessary anymore?  Shouldn't the Builders or Builder callers do this now?
-        bind(replaced);
-        return replaced;
+        bind(loggedIn);
+
+        onSuccessfulLogin(token, info, loggedIn);
+        return loggedIn;
     }
 
-    protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info) {
-        rememberMeSuccessfulLogin(token, info);
+    protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
+        rememberMeSuccessfulLogin(token, info, subject);
     }
 
-    protected void onFailedLogin(AuthenticationToken token, AuthenticationException ae) {
-        rememberMeFailedLogin(token, ae);
+    protected void onFailedLogin(AuthenticationToken token, AuthenticationException ae, Subject subject) {
+        rememberMeFailedLogin(token, ae, subject);
     }
 
-    protected void beforeLogout(PrincipalCollection subjectIdentifier) {
-        rememberMeLogout(subjectIdentifier);
+    protected void beforeLogout(Subject subject) {
+        rememberMeLogout(subject);
     }
 
     /**
@@ -405,10 +407,10 @@
                 context.put(SubjectFactory.SESSION, session);
             } catch (InvalidSessionException e) {
                 onInvalidSessionId(sessionId, e);
-                log.debug("Context referenced sessionId {} is invalid.  Ignoring and creating an anonymous " +
+                log.debug("Referenced sessionId {} is invalid.  Ignoring and creating an anonymous " +
                         "(session-less) Subject instance.", sessionId);
                 if (log.isTraceEnabled()) {
-                    log.trace("Exception resulting from referenced invalid sessionId " + sessionId, e);
+                    log.trace("Exception resulting from invalid referenced sessionId " + sessionId, e);
                 }
             }
         }
@@ -456,7 +458,7 @@
      * <ol>
      * <li>Check the context to see if it already {@link #containsIdentity(java.util.Map) contains an identity}.  If
      * so, this method does nothing and returns the method argument unaltered.</li>
-     * <li>Check for a RememberMe identity by calling {@link #getRememberedIdentity()}.  If that method returns a
+     * <li>Check for a RememberMe identity by calling {@link #getRememberedIdentity}.  If that method returns a
      * non-null value, create a <em>copy</em> of the method argument, and place the remembered {@link PrincipalCollection}
      * in the copied context map under the {@link SubjectFactory#PRINCIPALS} key and return that copied context.</li>
      * </ol>
@@ -470,7 +472,7 @@
     protected Map resolvePrincipals(Map context) {
         if (!containsIdentity(context)) {
             log.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");
-            PrincipalCollection principals = getRememberedIdentity();
+            PrincipalCollection principals = getRememberedIdentity(context);
             if (principals != null) {
                 log.debug("Found remembered PrincipalCollection.  Adding to the context to be used " +
                         "for subject construction by the SubjectFactory.");
@@ -516,13 +518,13 @@
             throw new IllegalArgumentException("Subject method argument cannot be null.");
         }
 
-        PrincipalCollection principals = subject.getPrincipals();
+        beforeLogout(subject);
 
+        PrincipalCollection principals = subject.getPrincipals();
         if (principals != null && !principals.isEmpty()) {
             if (log.isDebugEnabled()) {
                 log.debug("Logging out subject with primary principal {}" + principals.getPrimaryPrincipal());
             }
-            beforeLogout(principals);
             Authenticator authc = getAuthenticator();
             if (authc instanceof LogoutAware) {
                 ((LogoutAware) authc).onLogout(principals);
@@ -577,11 +579,11 @@
         getSubjectBinder().unbind(subject);
     }
 
-    protected PrincipalCollection getRememberedIdentity() {
+    protected PrincipalCollection getRememberedIdentity(Map subjectContext) {
         RememberMeManager rmm = getRememberMeManager();
         if (rmm != null) {
             try {
-                return rmm.getRememberedPrincipals();
+                return rmm.getRememberedPrincipals(subjectContext);
             } catch (Exception e) {
                 if (log.isWarnEnabled()) {
                     String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/RememberMeManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/RememberMeManager.java?rev=909163&r1=909162&r2=909163&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/RememberMeManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/RememberMeManager.java Thu Feb 11 22:03:14 2010
@@ -22,7 +22,9 @@
 import org.apache.shiro.authc.AuthenticationInfo;
 import org.apache.shiro.authc.AuthenticationToken;
 import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
 
+import java.util.Map;
 
 /**
  * A RememberMeManager is responsible for remembering a Subject's identity across that Subject's sessions with
@@ -33,13 +35,48 @@
  */
 public interface RememberMeManager {
 
-    //TODO - complete JavaDoc
-
-    PrincipalCollection getRememberedPrincipals();
-
-    void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info);
-
-    void onFailedLogin(AuthenticationToken token, AuthenticationException ae);
-
-    void onLogout(PrincipalCollection subjectPrincipals);
+    /**
+     * Based on the specified subject context map being used to build a Subject instance, returns any previously
+     * remembered principals for the subject for automatic identity association (aka 'Remember Me').
+     * <p/>
+     * The context map is usually populated by a {@link Subject.Builder} implementation.
+     * See the {@link SubjectFactory} class constants for Shiro's known map keys.
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.
+     * @return he remembered principals or {@code null} if none could be acquired.
+     * @since 1.0
+     */
+    PrincipalCollection getRememberedPrincipals(Map subjectContext);
+
+    /**
+     * Reacts to a successful authentication attempt, typically saving the principals to be retrieved ('remembered')
+     * for future system access.
+     *
+     * @param subject the subject that executed a successful authentication attempt
+     * @param token   the authentication token submitted resulting in a successful authentication attempt
+     * @param info    the authenticationInfo returned as a result of the successful authentication attempt
+     * @since 1.0
+     */
+    void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info);
+
+    /**
+     * Reacts to a failed authentication attempt, typically by forgetting any previously remembered principals for the
+     * Subject.
+     *
+     * @param subject the subject that executed the failed authentication attempt
+     * @param token   the authentication token submitted resulting in the failed authentication attempt
+     * @param ae      the authentication exception thrown as a result of the failed authentication attempt
+     * @since 1.0
+     */
+    void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae);
+
+    /**
+     * Reacts to a Subject logging out of the application, typically by forgetting any previously remembered
+     * principals for the Subject.
+     *
+     * @param subject the subject logging out.
+     * @since 1.0
+     */
+    void onLogout(Subject subject);
 }

Modified: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java?rev=909163&r1=909162&r2=909163&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java (original)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java Thu Feb 11 22:03:14 2010
@@ -22,7 +22,7 @@
 import org.apache.shiro.mgt.SubjectFactory;
 import org.apache.shiro.realm.Realm;
 import org.apache.shiro.session.mgt.SessionManager;
-import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.LifecycleUtils;
 import org.apache.shiro.web.attr.CookieAttribute;
 import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
@@ -30,6 +30,7 @@
 import org.apache.shiro.web.session.DefaultWebSessionManager;
 import org.apache.shiro.web.session.ServletContainerSessionManager;
 import org.apache.shiro.web.session.WebSessionManager;
+import org.apache.shiro.web.subject.WebSubject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -242,14 +243,14 @@
     }
 
     @Override
-    protected void beforeLogout(PrincipalCollection subjectIdentifier) {
-        super.beforeLogout(subjectIdentifier);
-        //also ensure a request attribute is set so the Subject is not reacquired later during the request:
-        removeRequestIdentity();
+    protected void beforeLogout(Subject subject) {
+        super.beforeLogout(subject);
+        removeRequestIdentity(subject);
     }
 
-    protected void removeRequestIdentity() {
-        ServletRequest request = WebUtils.getServletRequest();
+    protected void removeRequestIdentity(Subject subject) {
+        WebSubject webSubject = (WebSubject) subject;
+        ServletRequest request = webSubject.getServletRequest();
         if (request != null) {
             request.setAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY, Boolean.TRUE);
         }

Modified: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/WebRememberMeManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/WebRememberMeManager.java?rev=909163&r1=909162&r2=909163&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/WebRememberMeManager.java (original)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/WebRememberMeManager.java Thu Feb 11 22:03:14 2010
@@ -20,14 +20,19 @@
 
 import org.apache.shiro.codec.Base64;
 import org.apache.shiro.mgt.AbstractRememberMeManager;
+import org.apache.shiro.mgt.SubjectFactory;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.CollectionUtils;
 import org.apache.shiro.web.attr.CookieAttribute;
 import org.apache.shiro.web.attr.WebAttribute;
 import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.subject.WebSubject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import java.util.Map;
 
 
 /**
@@ -218,16 +223,25 @@
         getCookieAttribute().setComment(comment);
     }
 
-    protected void rememberSerializedIdentity(byte[] serialized) {
-        ServletRequest request = WebUtils.getRequiredServletRequest();
-        ServletResponse response = WebUtils.getRequiredServletResponse();
+    protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
+        WebSubject webSubject = (WebSubject) subject;
+        ServletRequest request = webSubject.getServletRequest();
+        ServletResponse response = webSubject.getServletResponse();
         //base 64 encode it and store as a cookie:
         String base64 = Base64.encodeToString(serialized);
         getIdentityAttribute().storeValue(base64, request, response);
     }
 
-    protected boolean isIdentityRemoved() {
-        ServletRequest request = WebUtils.getServletRequest();
+    private ServletRequest getServletRequest(Map subjectContext) {
+        return (ServletRequest) subjectContext.get(SubjectFactory.SERVLET_REQUEST);
+    }
+
+    private ServletResponse getServletResponse(Map subjectContext) {
+        return (ServletResponse) subjectContext.get(SubjectFactory.SERVLET_RESPONSE);
+    }
+
+    protected boolean isIdentityRemoved(Map subjectContext) {
+        ServletRequest request = getServletRequest(subjectContext);
         if (request != null) {
             Boolean removed = (Boolean) request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY);
             return removed != null && removed;
@@ -235,13 +249,21 @@
         return false;
     }
 
-    protected byte[] getSerializedRememberedIdentity() {
-        if (isIdentityRemoved()) {
+    protected byte[] getRememberedSerializedIdentity(Map subjectContext) {
+        if (CollectionUtils.isEmpty(subjectContext)) {
+            if (log.isTraceEnabled()) {
+                log.trace("Null or empty subject context map - unable to retrieve request/response pair to obtain " +
+                        "a request-based identity.  Returning null.");
+            }
             return null;
         }
 
-        ServletRequest request = WebUtils.getRequiredServletRequest();
-        ServletResponse response = WebUtils.getRequiredServletResponse();
+        if (isIdentityRemoved(subjectContext)) {
+            return null;
+        }
+
+        ServletRequest request = getServletRequest(subjectContext);
+        ServletResponse response = getServletResponse(subjectContext);
         String base64 = getIdentityAttribute().retrieveValue(request, response);
         if (base64 != null) {
             base64 = ensurePadding(base64);
@@ -281,9 +303,20 @@
     }
 
 
-    protected void forgetIdentity() {
-        ServletRequest request = WebUtils.getRequiredServletRequest();
-        ServletResponse response = WebUtils.getRequiredServletResponse();
+    protected void forgetIdentity(Subject subject) {
+        WebSubject webSubject = (WebSubject) subject;
+        ServletRequest request = webSubject.getServletRequest();
+        ServletResponse response = webSubject.getServletResponse();
+        forgetIdentity(request, response);
+    }
+
+    protected void forgetIdentity(Map subjectContext) {
+        ServletRequest request = getServletRequest(subjectContext);
+        ServletResponse response = getServletResponse(subjectContext);
+        forgetIdentity(request, response);
+    }
+
+    protected void forgetIdentity(ServletRequest request, ServletResponse response) {
         getIdentityAttribute().removeValue(request, response);
     }
 }

Modified: incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/AbstractWebSecurityManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/AbstractWebSecurityManagerTest.java?rev=909163&r1=909162&r2=909163&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/AbstractWebSecurityManagerTest.java (original)
+++ incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/AbstractWebSecurityManagerTest.java Thu Feb 11 22:03:14 2010
@@ -40,8 +40,6 @@
 
     protected Subject newSubject(SecurityManager sm, ServletRequest request, ServletResponse response) {
         ThreadContext.bind(sm);
-        WebUtils.bind(request);
-        WebUtils.bind(response);
         WebSubject subject = new WebSubject.Builder(sm, request, response).buildWebSubject();
         WebSubjectThreadState threadState = new WebSubjectThreadState(subject);
         threadState.bind();

Modified: incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/WebRememberMeManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/WebRememberMeManagerTest.java?rev=909163&r1=909162&r2=909163&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/WebRememberMeManagerTest.java (original)
+++ incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/WebRememberMeManagerTest.java Thu Feb 11 22:03:14 2010
@@ -21,16 +21,23 @@
 import org.apache.shiro.authc.AuthenticationInfo;
 import org.apache.shiro.authc.SimpleAuthenticationInfo;
 import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.io.SerializationException;
+import org.apache.shiro.mgt.SubjectFactory;
 import org.apache.shiro.subject.PrincipalCollection;
 import org.apache.shiro.subject.SimplePrincipalCollection;
 import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
-import static org.easymock.EasyMock.*;
-import static org.junit.Assert.assertTrue;
+import org.apache.shiro.web.subject.WebSubject;
 import org.junit.Test;
 
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 /**
  * TODO - class javadoc
@@ -42,10 +49,12 @@
 
     @Test
     public void onSuccessfulLogin() {
+
         HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
-        WebUtils.bind(mockRequest);
         HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
-        WebUtils.bind(mockResponse);
+        WebSubject mockSubject = createNiceMock(WebSubject.class);
+        expect(mockSubject.getServletRequest()).andReturn(mockRequest).anyTimes();
+        expect(mockSubject.getServletResponse()).andReturn(mockResponse).anyTimes();
 
         WebRememberMeManager mgr = new WebRememberMeManager();
         UsernamePasswordToken token = new UsernamePasswordToken("user", "secret");
@@ -55,18 +64,21 @@
         expect(mockRequest.getCookies()).andReturn(null);
         expect(mockRequest.getContextPath()).andReturn("/");
 
+        replay(mockSubject);
         replay(mockRequest);
-        mgr.onSuccessfulLogin(token, account);
+        mgr.onSuccessfulLogin(mockSubject, token, account);
         verify(mockRequest);
+        verify(mockSubject);
     }
 
     // SHIRO-69
     @Test
     public void getRememberedPrincipals() {
         HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
-        WebUtils.bind(mockRequest);
         HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
-        WebUtils.bind(mockResponse);
+        Map<String,Object> context = new HashMap<String,Object>();
+        context.put(SubjectFactory.SERVLET_REQUEST, mockRequest);
+        context.put(SubjectFactory.SERVLET_RESPONSE, mockResponse);
 
         expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
 
@@ -89,7 +101,7 @@
         replay(mockRequest);
 
         WebRememberMeManager mgr = new WebRememberMeManager();
-        PrincipalCollection collection = mgr.getRememberedPrincipals();
+        PrincipalCollection collection = mgr.getRememberedPrincipals(context);
 
         verify(mockRequest);
 
@@ -101,10 +113,12 @@
     // SHIRO-69
     @Test
     public void getRememberedPrincipalsDecryptionError() {
-        HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
-        WebUtils.bind(mockRequest);
-        HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
-        WebUtils.bind(mockResponse);
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+
+        Map<String,Object> context = new HashMap<String,Object>();
+        context.put(SubjectFactory.SERVLET_REQUEST, mockRequest);
+        context.put(SubjectFactory.SERVLET_RESPONSE, mockResponse);
 
         expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
 
@@ -114,11 +128,19 @@
                 new Cookie(WebRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCBlowfishBase64)
         };
 
-        expect(mockRequest.getCookies()).andReturn(cookies);
+        expect(mockRequest.getCookies()).andReturn(cookies).anyTimes();
         replay(mockRequest);
 
         WebRememberMeManager mgr = new WebRememberMeManager();
-        PrincipalCollection collection = mgr.getRememberedPrincipals();
+        PrincipalCollection collection = null;
+
+        SerializationException se = null;
+        try {
+            collection = mgr.getRememberedPrincipals(context);
+        } catch (SerializationException expected) {
+            se = expected;
+        }
+        assertNotNull(se);
 
         verify(mockRequest);
 
@@ -129,9 +151,10 @@
     @Test
     public void onLogout() {
         HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
-        WebUtils.bind(mockRequest);
         HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
-        WebUtils.bind(mockResponse);
+        WebSubject mockSubject = createNiceMock(WebSubject.class);
+        expect(mockSubject.getServletRequest()).andReturn(mockRequest).anyTimes();
+        expect(mockSubject.getServletResponse()).andReturn(mockResponse).anyTimes();
 
         Cookie cookie = new Cookie(WebRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, "");
         cookie.setMaxAge(0);
@@ -143,11 +166,13 @@
 
         replay(mockRequest);
         replay(mockResponse);
+        replay(mockSubject);
 
         PrincipalCollection pc = new SimplePrincipalCollection("user", "test");
         WebRememberMeManager mgr = new WebRememberMeManager();
-        mgr.onLogout(pc);
+        mgr.onLogout(mockSubject);
 
+        verify(mockSubject);
         verify(mockRequest);
         verify(mockResponse);
     }