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);
}