问题解决参考文章如下:
- https://my.oschina.net/klblog/blog/5559133
- https://blog.csdn.net/qq_37421368/article/details/124850449
问题复现
由于一些原因,登录的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