Spring OAuth2切换RedisTokenStore后出现ClassCastException问题求助
嘿,这个问题我刚接触Spring OAuth2+Redis的时候也踩过坑,其实核心是类加载器不一致加上Redis序列化的问题,咱们一步步理清楚:
为什么会出现同一个类转不了的ClassCastException?
JVM判断两个类是否是同一个类型,不仅要看全限定类名,还要看加载它的类加载器。如果你的User类被两个不同的类加载器加载了,哪怕类名完全一样,JVM也会认为是两个不同的类型,自然就抛出ClassCastException了。
切换到RedisTokenStore后出现这个问题,本质是:
- 默认的RedisTokenStore用JDK序列化存储OAuth2Authentication对象
- 序列化时用的类加载器和反序列化时(从Redis取出来)用的类加载器不一致
- 导致反序列化出来的
principal实例,和你当前代码里的User类不属于同一类型
具体解决方案
方案1:统一Redis序列化的类加载器(快速修复)
给RedisTokenStore配置自定义的JDK序列化器,强制使用当前应用的类加载器来反序列化:
@Bean fun redisTokenStore(redisConnectionFactory: RedisConnectionFactory): RedisTokenStore { val tokenStore = RedisTokenStore(redisConnectionFactory) // 自定义序列化器,指定当前上下文的类加载器 val jdkSerializer = JdkSerializationRedisSerializer() jdkSerializer.classLoader = this.javaClass.classLoader // 给认证信息和用户认证信息都设置这个序列化器 tokenStore.setAuthenticationSerializer(jdkSerializer) tokenStore.setUserAuthenticationSerializer(jdkSerializer) return tokenStore }
这样反序列化时就会用你的应用类加载器,避免类加载器不匹配的问题。
方案2:改用JSON序列化(更推荐,长期可靠)
JDK序列化本身就容易有类加载器、版本兼容的问题,换成Jackson JSON序列化会更稳妥,直接把对象转成JSON存储,反序列化时明确指定目标类:
@Bean fun redisTokenStore(redisConnectionFactory: RedisConnectionFactory): RedisTokenStore { val tokenStore = RedisTokenStore(redisConnectionFactory) // 配置Jackson序列化器,指定User类 val jacksonSerializer = Jackson2JsonRedisSerializer(User::class.java) val objectMapper = ObjectMapper().apply { registerModule(KotlinModule()) // 必须加,支持Kotlin数据类的序列化 visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY) enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL) // 保留类型信息,避免反序列化类型丢失 } jacksonSerializer.setObjectMapper(objectMapper) // 替换默认的序列化器 tokenStore.setAuthenticationSerializer(jacksonSerializer) tokenStore.setUserAuthenticationSerializer(jacksonSerializer) return tokenStore }
注意:如果你的User是普通Kotlin类,记得实现Serializable接口;如果是数据类,默认已经支持序列化。
方案3:临时规避(不推荐,仅应急)
如果暂时没法修改序列化配置,可以先做类型检查,再通过反射或者Bean拷贝来获取属性:
val securityAuth = SecurityContextHolder.getContext().authentication if (securityAuth is OAuth2Authentication) { val principal = securityAuth.userAuthentication.principal // 先判断类名是否一致(避免类加载器不同的情况) if (principal.javaClass.name == User::class.java.name) { // 方式1:用反射获取属性 val username = principal.javaClass.getMethod("getUsername").invoke(principal) as String // 方式2:用BeanUtils拷贝成目标类实例 val targetUser = BeanUtils.copyProperties(principal, User::class.java) } }
这个方案只是临时解决,没法从根源解决类加载器问题,还是推荐前两种方案。
验证小技巧
- 重启应用后,重新获取Token并调用接口,看异常是否消失
- 打开Redis客户端,查看
auth:{你的token}键的内容:如果是JSON格式,说明序列化配置生效;如果是二进制内容,就是JDK序列化的 - 调试时可以打印类加载器信息:
println(principal.javaClass.classLoader == this.javaClass.classLoader),如果返回true,说明类加载器一致了
内容的提问来源于stack exchange,提问作者shredding
相关产品推荐
相关产品推荐

