• 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 {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 默认序列化器的默认值
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 反序列化过程

    默认使用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);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 报错来源
    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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    代理IP和Socks5代理:跨界电商与全球爬虫的关键技术
    java毕业设计房屋中介网络平台Mybatis+系统+数据库+调试部署
    创建Prism项目
    单调栈-84. 柱状图中最大的矩形
    web漏洞-xml外部实体注入(XXE)
    使用C++的CCF-CSP满分解决方案 202104-2 邻域均值
    ERP管理系统:企业升级的秘密武器
    linux apt-get安装Jenkins
    Java:实现两个数字的LCM最小公倍数算法(附完整源码)
    【常见的六大排序算法】插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序
  • 原文地址:https://blog.csdn.net/xhom_w/article/details/137970181