This is an automated email from the ASF dual-hosted git repository. lprimak pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/shiro.git
commit e441ea37009a426b122c53b0120ae5879d00e354 Author: lprimak <[email protected]> AuthorDate: Sat Jun 20 14:03:37 2026 -0500 enh(web): rememberMe cookie expiration is checked on the server --- .../shiro/mgt/AbstractRememberMeManager.java | 77 ++++++++++++++++------ .../lang/io/ClassResolvingObjectInputStream.java | 12 +++- .../apache/shiro/lang/io/DefaultSerializer.java | 8 ++- .../org/apache/shiro/lang/util/ClassUtils.java | 27 ++++++-- .../shiro/web/mgt/CookieRememberMeManager.java | 30 ++++++++- .../shiro/web/mgt/CookieRememberMeManagerTest.java | 76 +++++++++++++++++---- 6 files changed, 186 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java b/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java index e1ea74bfc..da4de8962 100644 --- a/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java +++ b/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java @@ -25,17 +25,25 @@ import org.apache.shiro.authc.RememberMeAuthenticationToken; import org.apache.shiro.crypto.cipher.AesCipherService; import org.apache.shiro.crypto.cipher.ByteSourceBroker; import org.apache.shiro.crypto.cipher.CipherService; +import org.apache.shiro.lang.io.ClassResolvingObjectInputStream; import org.apache.shiro.lang.io.DefaultSerializer; import org.apache.shiro.lang.io.Serializer; import org.apache.shiro.lang.util.ByteSource; import org.apache.shiro.lang.util.ByteUtils; -import org.apache.shiro.lang.util.ClassUtils; +import org.apache.shiro.lang.util.ClassUtils.ClassLoaderAccessor; +import org.apache.shiro.lang.util.ClassUtils.ExceptionIgnoringAccessor; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.SubjectContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.Serial; +import java.io.Serializable; +import java.time.Instant; import java.util.function.Supplier; /** @@ -66,16 +74,32 @@ import java.util.function.Supplier; * @since 0.9 */ public abstract class AbstractRememberMeManager implements RememberMeManager { + protected record RememberedIdentity(PrincipalCollection principals, Instant creationTime) implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + } /** * private inner log instance. */ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRememberMeManager.class); + private static final ClassLoaderAccessor ADDITIONAL_CL_ACCESSOR = new ExceptionIgnoringAccessor() { + @Override + protected ClassLoader doGetClassLoader() { + return AbstractRememberMeManager.class.getClassLoader(); + } + }; + /** * Serializer to use for converting PrincipalCollection instances to/from byte arrays */ - private Serializer<PrincipalCollection> serializer = new DefaultSerializer<>(); + private Serializer<RememberedIdentity> serializer = new DefaultSerializer<>() { + @Override + protected ObjectInputStream createObjectInputStream(InputStream inputStream) throws IOException { + return new ClassResolvingObjectInputStream(inputStream, ADDITIONAL_CL_ACCESSOR); + } + }; /** * Cipher to use for encrypting/decrypting serialized byte arrays for added security @@ -121,7 +145,7 @@ public abstract class AbstractRememberMeManager implements RememberMeManager { * @return the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for * persistent remember me storage. */ - public Serializer<PrincipalCollection> getSerializer() { + public Serializer<RememberedIdentity> getSerializer() { return serializer; } @@ -134,7 +158,7 @@ public abstract class AbstractRememberMeManager implements RememberMeManager { * @param serializer the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances * for persistent remember me storage. */ - public void setSerializer(Serializer<PrincipalCollection> serializer) { + public void setSerializer(Serializer<RememberedIdentity> serializer) { this.serializer = serializer; } @@ -352,14 +376,14 @@ public abstract class AbstractRememberMeManager implements RememberMeManager { /** * 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 + * This implementation first {@link #serialize(RememberedIdentity) 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); + byte[] bytes = serialize(new RememberedIdentity(principals, now())); if (getCipherService() != null) { bytes = encrypt(bytes); } @@ -433,7 +457,28 @@ public abstract class AbstractRememberMeManager implements RememberMeManager { if (getCipherService() != null) { bytes = decrypt(bytes); } - return deserialize(bytes); + RememberedIdentity remembered = deserialize(bytes); + return checkExpiration(remembered); + } + + /** + * Checks the given remembered identity for expiration. The default implementation does not perform any expiration + * checks and simply returns the principals. Subclasses can override this method to perform expiration checks based on the + * {@code creationTime} property of the {@code RememberedIdentity} + * @param remembered identity + * @return PrincipalCollection + */ + protected PrincipalCollection checkExpiration(RememberedIdentity remembered) { + return remembered.principals(); + } + + /** + * Returns the current time as an {@link Instant}. Subclasses can override this method to provide a different time, + * such as for testing + * @return the current time as an {@link Instant} + */ + protected Instant now() { + return Instant.now(); } /** @@ -508,13 +553,8 @@ public abstract class AbstractRememberMeManager implements RememberMeManager { * @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) { - ClassUtils.setAdditionalClassLoader(AbstractRememberMeManager.class.getClassLoader()); - try { - return getSerializer().serialize(principals); - } finally { - ClassUtils.removeAdditionalClassLoader(); - } + protected byte[] serialize(RememberedIdentity principals) { + return getSerializer().serialize(principals); } /** @@ -524,13 +564,8 @@ public abstract class AbstractRememberMeManager implements RememberMeManager { * @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array * @return the deserialized (reconstituted) {@code PrincipalCollection} */ - protected PrincipalCollection deserialize(byte[] serializedIdentity) { - ClassUtils.setAdditionalClassLoader(AbstractRememberMeManager.class.getClassLoader()); - try { - return getSerializer().deserialize(serializedIdentity); - } finally { - ClassUtils.removeAdditionalClassLoader(); - } + protected RememberedIdentity deserialize(byte[] serializedIdentity) { + return getSerializer().deserialize(serializedIdentity); } /** diff --git a/lang/src/main/java/org/apache/shiro/lang/io/ClassResolvingObjectInputStream.java b/lang/src/main/java/org/apache/shiro/lang/io/ClassResolvingObjectInputStream.java index 1f7f0c73d..538f92b6e 100644 --- a/lang/src/main/java/org/apache/shiro/lang/io/ClassResolvingObjectInputStream.java +++ b/lang/src/main/java/org/apache/shiro/lang/io/ClassResolvingObjectInputStream.java @@ -19,6 +19,7 @@ package org.apache.shiro.lang.io; import org.apache.shiro.lang.util.ClassUtils; +import org.apache.shiro.lang.util.ClassUtils.ClassLoaderAccessor; import org.apache.shiro.lang.util.UnknownClassException; import java.io.IOException; @@ -33,9 +34,16 @@ import java.io.ObjectStreamClass; * @since 1.2 */ public class ClassResolvingObjectInputStream extends ObjectInputStream { + private final ClassLoaderAccessor additionalClassLoader; public ClassResolvingObjectInputStream(InputStream inputStream) throws IOException { + this(inputStream, null); + } + + public ClassResolvingObjectInputStream(InputStream inputStream, + ClassLoaderAccessor additionalClassLoader) throws IOException { super(inputStream); + this.additionalClassLoader = additionalClassLoader; } /** @@ -48,9 +56,9 @@ public class ClassResolvingObjectInputStream extends ObjectInputStream { * @throws ClassNotFoundException if the class could not be found in any known ClassLoader */ @Override - protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException { + protected Class<?> resolveClass(ObjectStreamClass osc) throws ClassNotFoundException { try { - return ClassUtils.forName(osc.getName()); + return ClassUtils.forName(osc.getName(), additionalClassLoader); } catch (UnknownClassException e) { throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", e); } diff --git a/lang/src/main/java/org/apache/shiro/lang/io/DefaultSerializer.java b/lang/src/main/java/org/apache/shiro/lang/io/DefaultSerializer.java index a06f7a142..ba6d89041 100644 --- a/lang/src/main/java/org/apache/shiro/lang/io/DefaultSerializer.java +++ b/lang/src/main/java/org/apache/shiro/lang/io/DefaultSerializer.java @@ -23,6 +23,7 @@ import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -33,7 +34,6 @@ import java.io.ObjectOutputStream; * @since 0.9 */ public class DefaultSerializer<T> implements Serializer<T> { - /** * This implementation serializes the Object by using an {@link ObjectOutputStream} backed by a * {@link ByteArrayOutputStream}. The {@code ByteArrayOutputStream}'s backing byte array is returned. @@ -80,7 +80,7 @@ public class DefaultSerializer<T> implements Serializer<T> { ByteArrayInputStream bais = new ByteArrayInputStream(serialized); BufferedInputStream bis = new BufferedInputStream(bais); try { - ObjectInputStream ois = new ClassResolvingObjectInputStream(bis); + ObjectInputStream ois = createObjectInputStream(bis); @SuppressWarnings({"unchecked"}) T deserialized = (T) ois.readObject(); ois.close(); @@ -90,4 +90,8 @@ public class DefaultSerializer<T> implements Serializer<T> { throw new SerializationException(msg, e); } } + + protected ObjectInputStream createObjectInputStream(InputStream inputStream) throws IOException { + return new ClassResolvingObjectInputStream(inputStream); + } } diff --git a/lang/src/main/java/org/apache/shiro/lang/util/ClassUtils.java b/lang/src/main/java/org/apache/shiro/lang/util/ClassUtils.java index 2a9b8243b..ef00fadd1 100644 --- a/lang/src/main/java/org/apache/shiro/lang/util/ClassUtils.java +++ b/lang/src/main/java/org/apache/shiro/lang/util/ClassUtils.java @@ -194,8 +194,25 @@ public final class ClassUtils { * @return the located class * @throws UnknownClassException if the class cannot be found. */ - @SuppressWarnings("unchecked") public static <T> Class<T> forName(String fqcn) throws UnknownClassException { + return forName(fqcn, null); + } + + /** + * Attempts to load the specified class name from the current thread's + * {@link Thread#getContextClassLoader() context class loader}, then the + * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application + * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order. If any of them cannot locate + * the specified class, an <code>UnknownClassException</code> is thrown (our RuntimeException equivalent of + * the JRE's <code>ClassNotFoundException</code>. + * + * @param fqcn the fully qualified class name to load + * @param additionalClassLoader accessor to override additional class loader + * @return the located class + * @throws UnknownClassException if the class cannot be found. + */ + @SuppressWarnings("unchecked") + public static <T> Class<T> forName(String fqcn, ClassLoaderAccessor additionalClassLoader) throws UnknownClassException { Class<?> clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); if (clazz == null) { @@ -211,7 +228,8 @@ public final class ClassUtils { LOGGER.trace("Unable to load class named [" + fqcn + "] from the org.apache.shiro.lang ClassLoader. Trying the additionally set ClassLoader..."); } - clazz = ADDITIONAL_CL_ACCESSOR.loadClass(fqcn); + clazz = additionalClassLoader != null ? additionalClassLoader.loadClass(fqcn) + : ADDITIONAL_CL_ACCESSOR.loadClass(fqcn); } if (clazz == null) { @@ -337,7 +355,7 @@ public final class ClassUtils { /** * @since 1.0 */ - private interface ClassLoaderAccessor { + public interface ClassLoaderAccessor { Class<?> loadClass(String fqcn); InputStream getResourceStream(String name); @@ -348,8 +366,7 @@ public final class ClassUtils { /** * @since 1.0 */ - private abstract static class ExceptionIgnoringAccessor implements ClassLoaderAccessor { - + public abstract static class ExceptionIgnoringAccessor implements ClassLoaderAccessor { public Class<?> loadClass(String fqcn) { Class<?> clazz = null; ClassLoader cl = getClassLoader(); diff --git a/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java b/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java index 8f83b432d..d84f85f83 100644 --- a/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java +++ b/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java @@ -18,10 +18,12 @@ */ package org.apache.shiro.web.mgt; +import java.time.Instant; import java.util.function.Supplier; import org.apache.shiro.lang.codec.Base64; import org.apache.shiro.mgt.AbstractRememberMeManager; +import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.SubjectContext; import org.apache.shiro.web.servlet.Cookie; @@ -38,7 +40,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import static org.apache.shiro.session.mgt.DefaultSessionManager.SECURE_COOKIE_DISABLED; - /** * Remembers a Subject's identity by saving the Subject's {@link Subject#getPrincipals() principals} to a {@link Cookie} * for later retrieval. @@ -72,7 +73,6 @@ import static org.apache.shiro.session.mgt.DefaultSessionManager.SECURE_COOKIE_D * @since 1.0 */ public class CookieRememberMeManager extends AbstractRememberMeManager { - /** * The default name of the underlying rememberMe cookie which is {@code rememberMe}. */ @@ -139,6 +139,7 @@ public class CookieRememberMeManager extends AbstractRememberMeManager { * @param subject the Subject for which the identity is being serialized. * @param serialized the serialized bytes to be persisted. */ + @Override protected void rememberSerializedIdentity(Subject subject, byte[] serialized) { if (!WebUtils.isHttp(subject)) { @@ -191,6 +192,7 @@ public class CookieRememberMeManager extends AbstractRememberMeManager { * lookup. * @return a previously serialized identity byte array or {@code null} if the byte array could not be acquired. */ + @Override protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) { if (!WebUtils.isHttp(subjectContext)) { @@ -246,6 +248,28 @@ public class CookieRememberMeManager extends AbstractRememberMeManager { } } + @Override + protected PrincipalCollection checkExpiration(RememberedIdentity identity) { + int maxAge = getCookie().getMaxAge(); + + // Negative maxAge means no explicit persistence lifetime is configured + // (e.g. session cookie semantics), so nothing to enforce here. + if (maxAge < 0) { + return identity.principals(); + } + + Instant expiresAt = identity.creationTime().plusSeconds(maxAge); + if (now().isAfter(expiresAt)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Remembered identity expired at [{}] based on cookie maxAge [{}] seconds.", + expiresAt, maxAge); + } + return null; + } + + return identity.principals(); + } + /** * Sometimes a user agent will send the rememberMe cookie value without padding, * most likely because {@code =} is a separator in the cookie header. @@ -276,6 +300,7 @@ public class CookieRememberMeManager extends AbstractRememberMeManager { * * @param subject the subject instance for which identity data should be forgotten from the underlying persistence */ + @Override protected void forgetIdentity(Subject subject) { if (WebUtils.isHttp(subject)) { HttpServletRequest request = WebUtils.getHttpRequest(subject); @@ -293,6 +318,7 @@ public class CookieRememberMeManager extends AbstractRememberMeManager { * * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation */ + @Override public void forgetIdentity(SubjectContext subjectContext) { if (WebUtils.isHttp(subjectContext)) { HttpServletRequest request = WebUtils.getHttpRequest(subjectContext); diff --git a/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java b/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java index e28d6037a..60043aecf 100644 --- a/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java +++ b/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java @@ -35,7 +35,9 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.time.Instant; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; @@ -54,6 +56,19 @@ import static org.junit.jupiter.api.Assertions.assertThrows; * @since 1.0 */ class CookieRememberMeManagerTest { + //The following base64 string was determined from the log output of the above 'onSuccessfulLogin' test. + //This will have to change any time the PrincipalCollection implementation changes: + @SuppressWarnings("checkstyle:ConstantName") + private static + final String userPCAesBase64 = "nKSpN8HWb1LN9LpM/Q78UDuKDHpqTBOVH651SqQfhAfdGbm3XyWBwjUesqQVX+c5xe8ewLiqXtcBE" + + "DfAnmnMWNPPVKNtHrAGN081gSd/UGuCDIIjy7JnlKQNFe7hkVSAQAaNEyOYKKs5mZtaOQiuzieTAIp2DaFJdwZ5eKodZIBN8Tc" + + "rDG9S5DWW6kEHOSW37Vxk+FRL3My1/bDh4VTa84lYChAEUdBLBxnTPYXQrVt9HAQavqRhE1KJiXcku+LEE0DzqtE3qkaO1C6vH" + + "It2gmzri8ULdFnWQr0C0dXSe0KkcMDfpuEwluMQOAjSsLjv0W066xZkV6FYWV6KpFQjSUDRyJODL8t/q3aP5bNA73FCq7eXLc2" + + "p98bn1ord/Q8yIYts4MtXMiJpjVW8zM6CBbc1L2ipQnwqH5NcRDSk3HcGq61/COzy5oAJHN1yIoowMfRhV+in1z/N2PANjzw6H" + + "hZslDvZo4bMwSu+qwpiXQ1z3vGtdqX8W+ch0GaivJSXjP3gC6CpMn+WeYJL+rp5y9lRj4LOoAIVHKnYEn7zjp26W/l7dks5eyo" + + "l6GwlUsHy3KtKigdepfE5WCUmXk6aBBQOSS7cMMmHjDJnOf+dgtkvSbxCZySAXCQi4ONgSrX8wBFuGCTEdKUDUogAAJ5HpZ0Fs" + + "BdaUE3NYaeo5ptXbzFfCFUosqu0"; + private static final String CIPHER_KEY = "kPH+bIxk5D2deZiIxcaaaA=="; @Test void onSuccessfulLogin() { @@ -65,6 +80,7 @@ class CookieRememberMeManagerTest { expect(mockSubject.getServletResponse()).andReturn(mockResponse).anyTimes(); CookieRememberMeManager mgr = new CookieRememberMeManager(); + mgr.setCipherKey(Base64.decode(CIPHER_KEY)); org.apache.shiro.web.servlet.Cookie cookie = createMock(org.apache.shiro.web.servlet.Cookie.class); mgr.setCookie(cookie); @@ -133,16 +149,6 @@ class CookieRememberMeManagerTest { expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null); - //The following base64 string was determined from the log output of the above 'onSuccessfulLogin' test. - //This will have to change any time the PrincipalCollection implementation changes: - final String userPCAesBase64 = "0o6DCfePYTjK4q579qzUFEfkeGRvbBOdKHp2y8/nGAltt1Vz8uW0Z8igeO" - + "Tq/yBmcw25f3Q0ui/Leg3x0iQZWhw9Bbu0mFHmHsGxEd6mPwtUpSegIjyX5c/kZpqnb7QLdajPWiczX8P" - + "Oc2Eku5+8ye1u38Y8uKlklHxcYCPh0pRiDSBxfjPsLaDfOpGbmPjZd4SVg68i/++TvUjqBNJyb+pDix3f" - + "PeuPvReWGcE50iovezVZrEfDOAQ0cZYW35ShypMWOmE9yZnb+p8++StDyAUegryyuIa4pjuRzfMh9D+sN" - + "F9tm/EnDC1VCer2S/a0AGlWAQiM7jrWt1sNinZcKIrvShaWI21tONJt8WhozNS2H72lk4p92rfLNHeglT" - + "xObxIYxLfTI9KiToSe1nYmpQmbBO8x1wWDkWBG//EqRvhgbIfQVqJp12T0fJC1nFuZuVhw/ZanaAZGDk8" - + "7aLMiw3T6FBZtWaspgvfH+0TJrTD8Ra386ekNXNN8JW8="; - Cookie[] cookies = new Cookie[] { new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCAesBase64) }; @@ -150,8 +156,14 @@ class CookieRememberMeManagerTest { expect(mockRequest.getCookies()).andReturn(cookies); replay(mockRequest); - CookieRememberMeManager mgr = new CookieRememberMeManager(); - mgr.setCipherKey(Base64.decode("kPH+bIxk5D2deZiIxcaaaA==")); + CookieRememberMeManager mgr = new CookieRememberMeManager() { + @Override + protected Instant now() { + // Return a fixed time to ensure the test is repeatable and not affected by time-based expiration + return Instant.parse("2021-01-01T00:00:00Z"); + } + }; + mgr.setCipherKey(Base64.decode(CIPHER_KEY)); PrincipalCollection collection = mgr.getRememberedPrincipals(context); verify(mockRequest); @@ -161,6 +173,46 @@ class CookieRememberMeManagerTest { assertThat(collection.iterator().next()).isEqualTo("user"); } + @Test + void ensureRememberMeExpires() { + HttpServletRequest mockRequest = createMock(HttpServletRequest.class); + HttpServletResponse mockResponse = createMock(HttpServletResponse.class); + WebSubjectContext context = new DefaultWebSubjectContext(); + context.setServletRequest(mockRequest); + context.setServletResponse(mockResponse); + + AtomicReference<Instant> instant = new AtomicReference<>(); + CookieRememberMeManager mgr = new CookieRememberMeManager() { + @Override + protected Instant now() { + return instant.get(); + } + + @Override + protected PrincipalCollection checkExpiration(RememberedIdentity remembered) { + instant.set(remembered.creationTime().plusSeconds(getCookie().getMaxAge() + 1)); + return super.checkExpiration(remembered); + } + }; + mgr.setCipherKey(Base64.decode(CIPHER_KEY)); + mgr.getCookie().setMaxAge(5); + + expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null); + + Cookie[] cookies = new Cookie[] { + new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCAesBase64) + }; + + expect(mockRequest.getCookies()).andReturn(cookies); + replay(mockRequest); + + PrincipalCollection collection = mgr.getRememberedPrincipals(context); + + verify(mockRequest); + + assertThat(collection).isNull(); + } + @Test void getRememberedPrincipalsNoMoreDefaultCipher() { assertThatExceptionOfType(CryptoException.class).isThrownBy(() -> {
