首页 > 数据库 >记spring-security升级,引发的redis反序列化不一致问题

记spring-security升级,引发的redis反序列化不一致问题

时间:2023-04-07 13:57:48浏览次数:44  
标签:core java spring redis springframework apache org 序列化 ApplicationFilterChain

问题解决参考文章如下:

问题复现

由于一些原因,登录的token由旧版本的微服务存入的redis,另一个新版本的微服务需要取出数据校验
springboot 版本升级 导致spring-security-core升级 redis反序列化版本不一致
依赖版本变化
springboot 2.0.9.RELEASE => 2.6.6.RELEASE
spring-security-core 5.0.12.RELEASE => 5.0.12.RELEASE

报错如下
org.springframework.data.redis.serializer.SerializationException: Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.InvalidClassException: org.springframework.security.authentication.UsernamePasswordAuthenticationToken; local class incompatible: stream classdesc serialVersionUID = 500, local class serialVersionUID = 560
	at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.deserialize(JdkSerializationRedisSerializer.java:84)
	at org.springframework.data.redis.core.AbstractOperations.deserializeHashValue(AbstractOperations.java:380)
	at org.springframework.data.redis.core.AbstractOperations.deserializeHashMap(AbstractOperations.java:324)
	at org.springframework.data.redis.core.DefaultHashOperations.entries(DefaultHashOperations.java:309)
	at org.springframework.data.redis.core.DefaultBoundHashOperations.entries(DefaultBoundHashOperations.java:223)
	at org.springframework.session.data.redis.RedisIndexedSessionRepository.getSession(RedisIndexedSessionRepository.java:457)
	at org.springframework.session.data.redis.RedisIndexedSessionRepository.findById(RedisIndexedSessionRepository.java:429)
	at org.springframework.session.data.redis.RedisIndexedSessionRepository.findById(RedisIndexedSessionRepository.java:251)
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getRequestedSession(SessionRepositoryFilter.java:356)
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:290)
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:193)
	at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:244)
	at org.springframework.security.web.context.HttpSessionSecurityContextRepository.loadContext(HttpSessionSecurityContextRepository.java:118)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:98)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:142)
	at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:834)
解决思路

更换redis对session数据反序列化策略
由于security-core的org.springframework.security.core.context里的 SecurityContextImpl在不同版本下serialVersionUID不一致,因此参考第二篇引用文章,通过反射将序列化版本进行修改,使得redis锁使用的JdkSerializationRedisSerializer可以顺利序列化

最终代码

SpringSessionConfig.java



import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ConfigurableObjectInputStream;
import org.springframework.core.NestedIOException;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.lang.Nullable;

import java.io.ByteArrayInputStream;


@Configuration
public class SpringSessionConfig implements BeanClassLoaderAware {

  private ClassLoader loader;


  @Bean("springSessionDefaultRedisSerializer")
  //@ConditionalOnProperty(prefix = "spring.session", name = "store-type", havingValue = "redis")
  public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
    return new JdkSerializationRedisSerializer(loader){
      @Override
      public Object deserialize(@Nullable byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
          return null;
        } else {

          try {
            // 注意这里使用我们重写的 CompatibleInputStream
            ConfigurableObjectInputStream objectInputStream = new CompatibleInputStream(new ByteArrayInputStream(bytes), loader);
            try {
              return objectInputStream.readObject();
            } catch (ClassNotFoundException var4) {
              throw new NestedIOException("Failed to deserialize object type", var4);
            }finally {
              objectInputStream.close();
            }
          } catch (Exception e) {
            throw new SerializationException("Cannot deserialize", e);
          }
        }
      }
    };
  }
 
  @Override
  public void setBeanClassLoader(ClassLoader classLoader) {
    this.loader = classLoader;
  }
}

CompatibleInputStream.java


import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ConfigurableObjectInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Field;

@Slf4j
public class CompatibleInputStream  extends ConfigurableObjectInputStream {

     public CompatibleInputStream(InputStream in, ClassLoader classLoader) throws IOException {
          super(in, classLoader);
     }

     public CompatibleInputStream(InputStream in, ClassLoader classLoader, boolean acceptProxyClasses) throws IOException {
          super(in, classLoader, acceptProxyClasses);
     }

     @Override
     protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
          ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
          Class localClass;
          try {
               localClass = Class.forName(resultClassDescriptor.getName());
          } catch (ClassNotFoundException e) {
               log.error("No local class for " + resultClassDescriptor.getName(), e);
               return resultClassDescriptor;
          }
          ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
          if (localClassDescriptor != null) {
               final long localSUID = localClassDescriptor.getSerialVersionUID();
               final long streamSUID = resultClassDescriptor.getSerialVersionUID();
               if (streamSUID != localSUID) {
                    log.debug("streamSUID = {} localSUID = {}", streamSUID, localSUID);
                    // 注意 这里是关键 通过反射强制修改 ObjectStreamClass
                    // 的 suid 属性
                    try {
                         Field field = resultClassDescriptor.getClass().getDeclaredField("suid");
                         field.setAccessible(true);
                         field.set(resultClassDescriptor, localSUID);
                    } catch (Exception e) {
                         e.printStackTrace();
                    }
                    // resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization*/
               }
          }
          return resultClassDescriptor;

     }
}

标签:core,java,spring,redis,springframework,apache,org,序列化,ApplicationFilterChain
From: https://www.cnblogs.com/minbin/p/17295823.html

相关文章

  • 【Spring AOP基础使用:认识AOP,AOP作用,核心概念,AOP实现】
    本文纲要一、了解AOP1、认识AOP2、AOP作用3、AOP核心概念二、AOP快速入门1、基础准备2、AOP实现3、总结三、AOP获取通知数据1、JoinPoint2、ProceedingJoinPoint3、获取通知数据的使用场景进入正文:一、了解AOP1、认识AOPAOP(AspectOrientedProgramming)面向切面编程,一种......
  • Redis三主三从集群
    Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。一组RedisCluster是由多个Redis实例组成,官方推荐使用6实例,其中3个为主节点,3个为从节点。一旦有主节点发生故障的时候,RedisCluster可以选举出对应的从节点成为新的主节点,继续对外服务,......
  • Redis-基础(逐步完善)
    Redis持久化RDB/AOF命令RDB(dump.rdb文件)适用于大规模数据恢复,且对数据完整性和一致性不高的情况把当前进程数据生成快照保存到硬盘的过程RDB文件的处理保存位置:dbfilename压缩:rdbcompression,默认开启LZF压缩,会消耗CPU校验:rdbchecksum,使用CRC64算法校验RDB持久化触发方式......
  • SpringBoot @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})注
    @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})该注解的作用是,排除自动注入数据源的配置,用exclude属性进行排除指定的类,在springBoot中使用多数据源时,加上@SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})DataSourceAutoConfigur......
  • SpringCloud 多个服务启动放在一个窗口下的设置
    进入.idea文件夹,在workspace.xml文件中加入如下配置即可<componentname="RunDashboard"><optionname="configurationTypes"><set><optionvalue="SpringBootApplicationConfigurationType"/></set&......
  • jackson序列化报 Null key for a Map not allowed in JSON (use a converting NullKey
    报错:"requestParam":null,"errorMsg":"org.springframework.http.converter.HttpMessageNotWritableException:CouldnotwriteJSON:NullkeyforaMapnotallowedinJSON(useaconvertingNullKeySerializer?);nestedexceptionisc......
  • JSON序列化和反序列化日期时间的处理
    JSON格式不直接支持日期和时间。DateTime值值显示为“/Date(700000+0500)/”形式的JSON字符串,其中第一个数字(在提供的示例中为700000)是GMT时区中自1970年1月1日午夜以来按正常时间(非夏令时)经过的毫秒数。该数字可以是负数,以表示之前的时间。示例中包括“+0500”的部分可选......
  • Spring 源码解析 --Bean 的初始化流程
    --Spring原理架构图   --容器刷新  ---Bean初始化  --Bean生命周期流程 ......
  • Java GenericObjectPool 对象池化技术--SpringBoot sftp 连接池工具类
    JavaBasePooledObjectFactory对象池化技术通常一个对象创建、销毁非常耗时的时候,我们不会频繁的创建和销毁它,而是考虑复用。复用对象的一种做法就是对象池,将创建好的对象放入池中维护起来,下次再用的时候直接拿池中已经创建好的对象继续用,这就是池化的思想。ApacheCommonsPoo......
  • Spring 源码阅读之标签解析
    全局目录.md引子1、容器最基本使用.md系列1-bean标签解析:2、XmlBeanFactory的类图介绍.md3、XmlBeanFactory对xml文件读取.md4、xml配置文件解析之【默认】命名空间【标签】的解析.md5、xml配置文件解析之【自定义】命名空间【标签】的解析.md系列2-bean获取:get......