RedisHttpSession反序列化UID问题跟踪

1.RedisHttpSession配置
package com.visy.configure;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=100)
public class ConfigRedisSession {
    
}
2.报错信息:
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:
com.vz.common.model.User;
local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = -6369326306393228118
3.问题跟踪
  • 注解源码
package org.springframework.session.data.redis.config.annotation.web.http;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RedisHttpSessionConfiguration.class})
@Configuration
public @interface EnableRedisHttpSession {
    int maxInactiveIntervalInSeconds() default 1800;

    String redisNamespace() default "";

    RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
}
  • Redis序列化器配置
package org.springframework.session.data.redis.config.annotation.web.http;

@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration implements EmbeddedValueResolverAware, ImportAware {
    private Integer maxInactiveIntervalInSeconds = 1800;
    private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
    private String redisNamespace = "";
    private RedisFlushMode redisFlushMode;
    private RedisSerializer<Object> defaultRedisSerializer;
    private Executor redisTaskExecutor;
    private Executor redisSubscriptionExecutor;
    private StringValueResolver embeddedValueResolver;

    public RedisHttpSessionConfiguration() {
        this.redisFlushMode = RedisFlushMode.ON_SAVE;
    }

    @Bean
    public RedisTemplate<Object, Object> sessionRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        if (this.defaultRedisSerializer != null) {
        	//如果存在默认序列化器则使用
            template.setDefaultSerializer(this.defaultRedisSerializer);
        }

        template.setConnectionFactory(connectionFactory);
        return template;
    }

	//设置默认序列化器,寻找名称为”springSessionDefaultRedisSerializer“的RedisSerializer注入
	@Autowired( required = false)
    @Qualifier("springSessionDefaultRedisSerializer")
    public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
        this.defaultRedisSerializer = defaultRedisSerializer;
    }
}
  • 默认序列化器的默认值
package org.springframework.data.redis.core;

public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
	public void afterPropertiesSet() {
        super.afterPropertiesSet();
        boolean defaultUsed = false;
        if (this.defaultSerializer == null) {
        	//默认序列化器是JdkSerializationRedisSerializer
            this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
        }

        if (this.enableDefaultSerializer) {
            if (this.keySerializer == null) {
                this.keySerializer = this.defaultSerializer;
                defaultUsed = true;
            }

            if (this.valueSerializer == null) {
                this.valueSerializer = this.defaultSerializer;
                defaultUsed = true;
            }

            if (this.hashKeySerializer == null) {
                this.hashKeySerializer = this.defaultSerializer;
                defaultUsed = true;
            }

            if (this.hashValueSerializer == null) {
                this.hashValueSerializer = this.defaultSerializer;
                defaultUsed = true;
            }
        }

        if (this.enableDefaultSerializer && defaultUsed) {
            Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
        }

        if (this.scriptExecutor == null) {
            this.scriptExecutor = new DefaultScriptExecutor(this);
        }

        this.initialized = true;
    }
}
  • 反序列化过程

默认使用JdkSerializationRedisSerializer反序列化的过程

package org.springframework.data.redis.serializer;

public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {
	public Object deserialize(byte[] bytes) {
        if (SerializationUtils.isEmpty(bytes)) {
            return null;
        } else {
            try {
            	//反序列化
                return this.deserializer.convert(bytes);
            } catch (Exception var3) {
                throw new SerializationException("Cannot deserialize", var3);
            }
        }
    }
}
package org.springframework.core.serializer.support;

public class DeserializingConverter implements Converter<byte[], Object> {
    public Object convert(byte[] source) {
        ByteArrayInputStream byteStream = new ByteArrayInputStream(source);

        try {
        	//反序列化
            return this.deserializer.deserialize(byteStream);
        } catch (Throwable var4) {
            throw new SerializationFailedException("Failed to deserialize payload. Is the byte array a result of corresponding serialization for " + this.deserializer.getClass().getSimpleName() + "?", var4);
        }
    }
}
package org.springframework.core.serializer;

public class DefaultDeserializer implements Deserializer<Object> {
    public Object deserialize(InputStream inputStream) throws IOException {
        ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
        try {
        	//读取对象
            return objectInputStream.readObject();
        } catch (ClassNotFoundException var4) {
            throw new NestedIOException("Failed to deserialize object type", var4);
        }
    }
}
package java.io;

public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants {
	public final Object readObject()
        throws IOException, ClassNotFoundException {
        return readObject(Object.class);
    }
	
	private final Object readObject(Class<?> type) throws IOException, ClassNotFoundException {
        if (enableOverride) {
            return readObjectOverride();
        }

        if (! (type == Object.class || type == String.class))
            throw new AssertionError("internal error");

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(type, false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }

	private Object readObject0(Class<?> type, boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * Fix for 4360508: stream is currently at the end of a field
                 * value block written via default serialization; since there
                 * is no terminating TC_ENDBLOCKDATA tag, simulate
                 * end-of-custom-data behavior explicitly.
                 */
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }

        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }

        depth++;
        totalObjectRefs++;
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();

                case TC_REFERENCE:
                    // check the type of the existing object
                    return type.cast(readHandle(unshared));

                case TC_CLASS:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast a class to java.lang.String");
                    }
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast a class to java.lang.String");
                    }
                    return readClassDesc(unshared);

                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));

                case TC_ARRAY:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an array to java.lang.String");
                    }
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an enum to java.lang.String");
                    }
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an object to java.lang.String");
                    }
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an exception to java.lang.String");
                    }
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);

                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }

                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

	private ObjectStreamClass readClassDesc(boolean unshared) throws IOException {
        byte tc = bin.peekByte();
        ObjectStreamClass descriptor;
        switch (tc) {
            case TC_NULL:
                descriptor = (ObjectStreamClass) readNull();
                break;
            case TC_REFERENCE:
                descriptor = (ObjectStreamClass) readHandle(unshared);
                // Should only reference initialized class descriptors
                descriptor.checkInitialized();
                break;
            case TC_PROXYCLASSDESC:
                descriptor = readProxyDesc(unshared);
                break;
            case TC_CLASSDESC:
                descriptor = readNonProxyDesc(unshared);
                break;
            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
        if (descriptor != null) {
            validateDescriptor(descriptor);
        }
        return descriptor;
    }

	private ObjectStreamClass readNonProxyDesc(boolean unshared) throws IOException {
        if (bin.readByte() != TC_CLASSDESC) {
            throw new InternalError();
        }

        ObjectStreamClass desc = new ObjectStreamClass();
        int descHandle = handles.assign(unshared ? unsharedMarker : desc);
        passHandle = NULL_HANDLE;

        ObjectStreamClass readDesc = null;
        try {
            readDesc = readClassDescriptor();
        } catch (ClassNotFoundException ex) {
            throw (IOException) new InvalidClassException(
                "failed to read class descriptor").initCause(ex);
        }

        Class<?> cl = null;
        ClassNotFoundException resolveEx = null;
        bin.setBlockDataMode(true);
        final boolean checksRequired = isCustomSubclass();
        try {
            if ((cl = resolveClass(readDesc)) == null) {
                resolveEx = new ClassNotFoundException("null class");
            } else if (checksRequired) {
                ReflectUtil.checkPackageAccess(cl);
            }
        } catch (ClassNotFoundException ex) {
            resolveEx = ex;
        }

        // Call filterCheck on the class before reading anything else
        filterCheck(cl, -1);

        skipCustomData();

        try {
            totalObjectRefs++;
            depth++;
            desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));
        } finally {
            depth--;
        }

        handles.finish(descHandle);
        passHandle = descHandle;

        return desc;
    }
}
package java.io;

public class ObjectStreamClass implements Serializable {
	void initNonProxy(ObjectStreamClass model,
                      Class<?> cl,
                      ClassNotFoundException resolveEx,
                      ObjectStreamClass superDesc)
        throws InvalidClassException
    {
        long suid = Long.valueOf(model.getSerialVersionUID());
        ObjectStreamClass osc = null;
        if (cl != null) {
            osc = lookup(cl, true);
            if (osc.isProxy) {
                throw new InvalidClassException(
                        "cannot bind non-proxy descriptor to a proxy class");
            }
            if (model.isEnum != osc.isEnum) {
                throw new InvalidClassException(model.isEnum ?
                        "cannot bind enum descriptor to a non-enum class" :
                        "cannot bind non-enum descriptor to an enum class");
            }

            if (model.serializable == osc.serializable &&
                    !cl.isArray() &&
                    suid != osc.getSerialVersionUID()) {
                throw new InvalidClassException(osc.name,
                        "local class incompatible: " +
                                "stream classdesc serialVersionUID = " + suid +
                                ", local class serialVersionUID = " +
                                osc.getSerialVersionUID());
            }

            if (!classNamesEqual(model.name, osc.name)) {
                throw new InvalidClassException(osc.name,
                        "local class name incompatible with stream class " +
                                "name \"" + model.name + "\"");
            }

            if (!model.isEnum) {
                if ((model.serializable == osc.serializable) &&
                        (model.externalizable != osc.externalizable)) {
                    throw new InvalidClassException(osc.name,
                            "Serializable incompatible with Externalizable");
                }

                if ((model.serializable != osc.serializable) ||
                        (model.externalizable != osc.externalizable) ||
                        !(model.serializable || model.externalizable)) {
                    deserializeEx = new ExceptionInfo(
                            osc.name, "class invalid for deserialization");
                }
            }
        }

        this.cl = cl;
        this.resolveEx = resolveEx;
        this.superDesc = superDesc;
        name = model.name;
        this.suid = suid;
        isProxy = false;
        isEnum = model.isEnum;
        serializable = model.serializable;
        externalizable = model.externalizable;
        hasBlockExternalData = model.hasBlockExternalData;
        hasWriteObjectData = model.hasWriteObjectData;
        fields = model.fields;
        primDataSize = model.primDataSize;
        numObjFields = model.numObjFields;

        if (osc != null) {
            localDesc = osc;
            writeObjectMethod = localDesc.writeObjectMethod;
            readObjectMethod = localDesc.readObjectMethod;
            readObjectNoDataMethod = localDesc.readObjectNoDataMethod;
            writeReplaceMethod = localDesc.writeReplaceMethod;
            readResolveMethod = localDesc.readResolveMethod;
            if (deserializeEx == null) {
                deserializeEx = localDesc.deserializeEx;
            }
            domains = localDesc.domains;
            cons = localDesc.cons;
        }

        fieldRefl = getReflector(fields, localDesc);
        // reassign to matched fields so as to reflect local unshared settings
        fields = fieldRefl.getFields();
        initialized = true;
    }
}
  • 报错来源
if (model.serializable == osc.serializable &&
        !cl.isArray() &&
        suid != osc.getSerialVersionUID()) {
    throw new InvalidClassException(osc.name,
            "local class incompatible: " +
                    "stream classdesc serialVersionUID = " + suid +
                    ", local class serialVersionUID = " +
                    osc.getSerialVersionUID());
}
4.解决方案

自定义一个序列化器,不要使用JdkSerializationRedisSerializer
以下是官方给出的自定义默认序列化器的配置方法,点击可查看

@Configuration
public class SessionConfig implements BeanClassLoaderAware {

	private ClassLoader loader;

	@Bean
	public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
		//改用Jackson的序列化器
		return new GenericJackson2JsonRedisSerializer(objectMapper());
	}

	/**
	 * Customized {@link ObjectMapper} to add mix-in for class that doesn't have default
	 * constructors
	 * @return the {@link ObjectMapper} to use
	 */
	private ObjectMapper objectMapper() {
		ObjectMapper mapper = new ObjectMapper();
		mapper.registerModules(SecurityJackson2Modules.getModules(this.loader));
		return mapper;
	}

	/*
	 * @see
	 * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang
	 * .ClassLoader)
	 */
	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.loader = classLoader;
	}

}

相关推荐

  1. RedisHttpSession序列UID问题跟踪

    2024-04-23 01:16:01       14 阅读
  2. tomcat序列

    2024-04-23 01:16:01       36 阅读
  3. PHP序列

    2024-04-23 01:16:01       16 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-23 01:16:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-23 01:16:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-23 01:16:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-23 01:16:01       18 阅读

热门阅读

  1. 【Git】git revert 命令(撤销 commit 改动)

    2024-04-23 01:16:01       22 阅读
  2. 深度学习概念

    2024-04-23 01:16:01       12 阅读
  3. AJAX请求(axios篇)

    2024-04-23 01:16:01       14 阅读
  4. UDF小白入门

    2024-04-23 01:16:01       14 阅读
  5. git工具的安装及使用

    2024-04-23 01:16:01       12 阅读
  6. backtracking Leetcode 回溯算法题

    2024-04-23 01:16:01       11 阅读
  7. Linux文本处理三剑客:awk、grep和sed

    2024-04-23 01:16:01       13 阅读
  8. js高级 笔记03

    2024-04-23 01:16:01       11 阅读