You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

这个方案只是临时解决,没法从根源解决类加载器问题,还是推荐前两种方案。

验证小技巧

  1. 重启应用后,重新获取Token并调用接口,看异常是否消失
  2. 打开Redis客户端,查看auth:{你的token}键的内容:如果是JSON格式,说明序列化配置生效;如果是二进制内容,就是JDK序列化的
  3. 调试时可以打印类加载器信息:println(principal.javaClass.classLoader == this.javaClass.classLoader),如果返回true,说明类加载器一致了

内容的提问来源于stack exchange,提问作者shredding

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:45:35