记录Shiro的rememberMe Cookie序列化失败的情况的解决方案

本文探讨了在使用Shiro与Undertow服务器时遇到的序列化失败问题,详细分析了错误原因,涉及类加载器差异,并提供了解决方案,包括源码修改与版本更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    在开发的时候遇到一个十分诡异的情况,shiro打开了记住我的功能,请求的cookie也带上了,但是却总是抛出用户未登录的异常。无奈打开了所有的日志,发现每次请求都会抛出下面这个WARN
    排查调试之后我发现我遇到的序列化失败的原因就是因为使用了undertow当服务器,解决方法也很简单,换回tomcat就可以了。。。最简单粗暴的方法。
    下面列出我抛出的警告和发现问题的大致过程。

2019-07-10 20:19:05  WARN  org.apache.shiro.mgt.AbstractRememberMeManager(onRememberedPrincipalFailure:449) - 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, if you are using a shiro.ini file, this property would be 'securityManager.rememberMeManager.cipherKey' see: http://shiro.apache.org/web.html#Web-RememberMeServices. The remembered identity will be forgotten and not used for this request.
2019-07-10 20:19:05  WARN  org.apache.shiro.mgt.DefaultSecurityManager(getRememberedIdentity:617) - Delegate RememberMeManager instance of type [org.apache.shiro.web.mgt.CookieRememberMeManager] threw an exception during getRememberedPrincipals().
org.apache.shiro.io.SerializationException: Unable to deserialize argument byte array.
	at org.apache.shiro.io.DefaultSerializer.deserialize(DefaultSerializer.java:82) ~[shiro-core-1.4.0.jar:1.4.0]
	at org.apache.shiro.mgt.AbstractRememberMeManager.deserialize(AbstractRememberMeManager.java:507) ~[shiro-core-1.4.0.jar:1.4.0]
	at org.apache.shiro.mgt.AbstractRememberMeManager.convertBytesToPrincipals(AbstractRememberMeManager.java:421) ~[shiro-core-1.4.0.jar:1.4.0]
	at org.apache.shiro.mgt.AbstractRememberMeManager.getRememberedPrincipals(AbstractRememberMeManager.java:386) ~[shiro-core-1.4.0.jar:1.4.0]
	at org.apache.shiro.mgt.DefaultSecurityManager.getRememberedIdentity(DefaultSecurityManager.java:612) [shiro-core-1.4.0.jar:1.4.0]
	at org.apache.shiro.mgt.DefaultSecurityManager.resolvePrincipals(DefaultSecurityManager.java:500) [shiro-core-1.4.0.jar:1.4.0]
	at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:346) [shiro-core-1.4.0.jar:1.4.0]
	at org.apache.shiro.subject.Subject$Builder.buildSubject(Subject.java:845) [shiro-core-1.4.0.jar:1.4.0]
	at org.apache.shiro.web.subject.WebSubject$Builder.buildWebSubject(WebSubject.java:148) [shiro-web-1.4.0.jar:1.4.0]
	at org.apache.shiro.web.servlet.AbstractShiroFilter.createSubject(AbstractShiroFilter.java:292) [shiro-web-1.4.0.jar:1.4.0]
	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:359) [shiro-web-1.4.0.jar:1.4.0]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) [shiro-web-1.4.0.jar:1.4.0]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:64) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) [undertow-core-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) [undertow-core-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) [undertow-core-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104) [undertow-servlet-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:336) [undertow-core-1.4.25.Final.jar:1.4.25.Final]
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) [undertow-core-1.4.25.Final.jar:1.4.25.Final]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_171]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_171]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_171]
Caused by: java.io.StreamCorruptedException: invalid type code: 00
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1599) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1948) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1565) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2285) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2209) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2067) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) ~[?:1.8.0_171]
	at java.util.HashSet.readObject(HashSet.java:341) ~[?:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_171]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_171]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_171]
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1158) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2176) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2067) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) ~[?:1.8.0_171]
	at java.util.HashMap.readObject(HashMap.java:1409) ~[?:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_171]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_171]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_171]
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1158) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2176) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2067) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2285) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:561) ~[?:1.8.0_171]
	at org.apache.shiro.subject.SimplePrincipalCollection.readObject(SimplePrincipalCollection.java:295) ~[shiro-core-1.4.0.jar:1.4.0]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_171]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_171]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_171]
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1158) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2176) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2067) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571) ~[?:1.8.0_171]
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) ~[?:1.8.0_171]
	at org.apache.shiro.io.DefaultSerializer.deserialize(DefaultSerializer.java:77) ~[shiro-core-1.4.0.jar:1.4.0]
	... 57 more

    尝试了其给的官网方案,并没有什么用处。无奈只能打开调试,由于是解码的地方出错,在排除掉序列化后的数据格式错误的猜想之后,剩下的可能性也只有拿到的数据出错了。
    十分神奇的是我用tomcat当服务器时候就不会,而是用undertow就会出现这种情况,暂时没找出为什么使用undertow会出现这个问题,没有精力再继续往上调试了,在这里先记录一下。


2020.4.22 重新整理

    通过调试,发现shiro加载要序列化的类的时候,不同服务器使用的class loader 是不一样的。
    当使用tomcat的时候,classloader为TomcatEmbeddedWebappClassLoader,而使用undertow的时候,classloader为AppClassLoader,前者可以找到[C(char数组类型,原生类型),而后者不行,会抛出ClassNotFound错误,因此导致cookie还原登录信息失败。
    由于系统的ClassLoader的loadClass无法加载原生类型,而forName可以,因此我认为可以对shiro源码的ClassUtil类的代码进行一点修改,使用Class.forName函数(PS:修改ClassUtil后的shiro-lang包)。
    官方的1.6.1、2.0.0版本已修复

    private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor {

        public Class loadClass(String fqcn) {
            Class clazz = null;
            ClassLoader cl = getClassLoader();
            if (cl != null) {
                try {
                	// 使用forName加载
                    clazz = Class.forName(fqcn,false,cl);
//                    clazz = cl.loadClass(fqcn);
                } catch (ClassNotFoundException e) {
                    if (log.isTraceEnabled()) {
                        log.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]");
                    }
                }
            }
            return clazz;
        }
        
        // 省略其余代码
    }

### Apache Shiro序列化漏洞及 Cookie 加密机制 #### 一、反序列化漏洞原理 Apache Shiro框架中的记住密码功能(RememberMe)存在安全风险。当用户登录成功后,系统会生成一段由相关信息组成的cookie值。这段数据经历了序列化、AES加密以及Base64编码的过程[^2]。 在服务器接收到带有`RememberMe`键名的HTTP请求时,会对其中携带的数据执行一系列逆向操作:首先是Base64解码;接着利用固定的AES秘钥完成解密工作;最后一步则是Java对象流式的反序列化过程。然而,在此过程中并未对接收的内容实施有效的验证措施,这就使得攻击者能够精心构造恶意输入,从而触发远程命令执行(RCE)漏洞[^3]。 #### 二、Cookie 加密流程分析 对于通过RememberMe特性保存下来的认证状态而言: - **客户端存储形式**:以名为`RememberMe`的cookie形式存在于用户的浏览器环境中; - **内容结构**:其实际载荷包含了被序列化的用户身份信息片段,并且这些敏感资料已经过两层保护——先是采用了对称加密算法(AES),随后又进行了字符集转换(Base64)[^1]。 具体来说就是先将待传输的信息转化为字节数组并加以混淆处理,再将其映射到ASCII范围内便于网络传递的一串字符串上。这样的设计既保障了信息安全又能兼容大多数协议栈下的通信需求。 ```python import base64 from Crypto.Cipher import AES def encrypt_data(data, key): cipher = AES.new(key.encode(), AES.MODE_ECB) encrypted_bytes = cipher.encrypt(pad(data)) encoded_str = base64.b64encode(encrypted_bytes).decode('utf-8') return encoded_str def decrypt_and_deserialize(cookie_value, key): decoded_bytes = base64.b64decode(cookie_value) cipher = AES.new(key.encode(), AES.MODE_ECB) decrypted_bytes = unpad(cipher.decrypt(decoded_bytes)) # 假设这里有一个函数可以实现java对象的反序列化 deserialized_object = java_deserialization(decrypted_bytes) return deserialized_object ``` 需要注意的是上述代码仅为示意用途,真实场景下还需要考虑更多细节如填充模式的选择等。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值