New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring Session and Dev Tools Cause ClassCastException #3805

Closed
rwinch opened this Issue Aug 21, 2015 · 11 comments

Comments

Projects
None yet
7 participants
@rwinch
Member

rwinch commented Aug 21, 2015

If Spring Session Redis and Spring Boot Dev Tools are being used and a user defined class is persisted into HttpSession a ClassCastException will occur when trying to load it.

The problem is that Spring Session uses Spring Data Redis to load the class on each request. Spring Data Redis eventually uses DefaultDeserializer which uses an ObjectInputStream to load the class from the system ClassLoader.

This means that the value restored from session has a class defined by the system ClassLoader. The application itself loads the same class from Spring Boot's RestartClassLoader. Since the two classes are loaded from different ClassLoaders they cannot be cast from one to the other.

One possible solution is for Spring Redis to use a mechanism to customize the ObjectInputStream ClassLoader to use the ThreadLocal.getThread().getContextClassLoader(). For example, it could use ConfigurableObjectInputStream

I believe one alternative is that if DefaultDeserializer were loaded using RestartClassLoader, then the ObjectInputStream should use the RestartClassLoader.

Related (possibly replaces this issue): SPR-13409

@snicoll

This comment has been minimized.

Show comment
Hide comment
@snicoll

snicoll Aug 22, 2015

Member

Similar problem with other caches whose content survive the application refresh.

Member

snicoll commented Aug 22, 2015

Similar problem with other caches whose content survive the application refresh.

@rwinch

This comment has been minimized.

Show comment
Hide comment
@rwinch

rwinch Aug 30, 2015

Member

@snicoll Would SPR-13409 help with cache too?

Member

rwinch commented Aug 30, 2015

@snicoll Would SPR-13409 help with cache too?

@snicoll

This comment has been minimized.

Show comment
Hide comment
@snicoll

snicoll Aug 31, 2015

Member

probably not. Each cache implementation has its own way of deserializing cached data but I guess we could maybe tune the config when devtools runs. I am not sure if that's a huge problem though, persistent caching in dev mode seems like a lousy option.

We had an issue with our SpringOne demo that I need to dig a bit more. I'll create a separate issue if necessary. Thanks!

Member

snicoll commented Aug 31, 2015

probably not. Each cache implementation has its own way of deserializing cached data but I guess we could maybe tune the config when devtools runs. I am not sure if that's a huge problem though, persistent caching in dev mode seems like a lousy option.

We had an issue with our SpringOne demo that I need to dig a bit more. I'll create a separate issue if necessary. Thanks!

@philwebb

This comment has been minimized.

Show comment
Hide comment
@philwebb

philwebb Nov 12, 2015

Member

Unfortunately I've not found a way to work around this type of issue without making changes in the things that call ObjectInputStream. For now we're probably going to need to leave this as a known limitation.

Member

philwebb commented Nov 12, 2015

Unfortunately I've not found a way to work around this type of issue without making changes in the things that call ObjectInputStream. For now we're probably going to need to leave this as a known limitation.

@xoned1

This comment has been minimized.

Show comment
Hide comment
@xoned1

xoned1 Nov 30, 2015

So if you want Redis, it's not possible to use the dev-tools anymore? I'm facing the same error, which took me hours to find out (spring beginner).

xoned1 commented Nov 30, 2015

So if you want Redis, it's not possible to use the dev-tools anymore? I'm facing the same error, which took me hours to find out (spring beginner).

@wilkinsona

This comment has been minimized.

Show comment
Hide comment
@wilkinsona

wilkinsona Nov 30, 2015

Member

So if you want Redis, it's not possible to use the dev-tools anymore

As things stand you can't use Spring Session backed by Redis and DevTools together.

I'm facing the same error, which took me hours to find out (spring beginner).

I'm sorry that you spent hours finding this out. It's certainly not the experience that we want beginners to have. This is mentioned in the known limitations section of the documentation, but perhaps you didn't see the description or it didn't mean much too you?

I'm wondering if we would be better failing fast when we detect the Spring Session, Redis, and DevTools combination with a message explaining that it won't work.

Member

wilkinsona commented Nov 30, 2015

So if you want Redis, it's not possible to use the dev-tools anymore

As things stand you can't use Spring Session backed by Redis and DevTools together.

I'm facing the same error, which took me hours to find out (spring beginner).

I'm sorry that you spent hours finding this out. It's certainly not the experience that we want beginners to have. This is mentioned in the known limitations section of the documentation, but perhaps you didn't see the description or it didn't mean much too you?

I'm wondering if we would be better failing fast when we detect the Spring Session, Redis, and DevTools combination with a message explaining that it won't work.

@xoned1

This comment has been minimized.

Show comment
Hide comment
@xoned1

xoned1 Nov 30, 2015

Thanks for your reply. Yes, you're right. The documentation was the answer, but to be honest, if you start with spring there is so much you take notice from and try to remember. I don't think it's a doc update worth.

xoned1 commented Nov 30, 2015

Thanks for your reply. Yes, you're right. The documentation was the answer, but to be honest, if you start with spring there is so much you take notice from and try to remember. I don't think it's a doc update worth.

@rwinch

This comment has been minimized.

Show comment
Hide comment
@rwinch

rwinch Nov 30, 2015

Member

This can be resolved for Spring Session and Spring Data Redis once DATAREDIS-427 is resolved. Perhaps a bump to @christophstrobl could make that happen?

A workaround for this when using Spring Session 1.1.0.M1+ can be found in spring-state-securing-restful-apis. Using the latest GA is slightly more complex to ensure the custom RedisSerializer is used. Something like the following should work:

@Configuration
class SpringSessionConfig {

    @Bean
    public RedisSerializer<Object> defaultRedisSerializer() {
        return new BeanClassLoaderAwareJdkRedisSerializer();
    }

    @Autowired
    public void configureSessionRedisTemplate(RedisTemplate<String,ExpiringSession> sessionRedisTemplate, RedisSerializer<Object> serializer) {
        sessionRedisTemplate.setDefaultSerializer(serializer);
    }

    static class BeanClassLoaderAwareJdkRedisSerializer implements RedisSerializer<Object>, BeanClassLoaderAware {
        private Converter<Object, byte[]> serializer = new SerializingConverter();
        private Converter<byte[], Object> deserializer;

        @Override
        public void setBeanClassLoader(ClassLoader classLoader) {
            this.deserializer = new DeserializingConverter(new DefaultDeserializer(classLoader));
        }

        public Object deserialize(byte[] bytes) {
            if (ObjectUtils.isEmpty(bytes)) {
                return null;
            }

            try {
                Object result = deserializer.convert(bytes);
                return result;
            } catch (Exception ex) {
                throw new SerializationException("Cannot deserialize", ex);
            }
        }

        public byte[] serialize(Object object) {
            if (object == null) {
                return new byte[0];
            }
            try {
                return serializer.convert(object);
            } catch (Exception ex) {
                throw new SerializationException("Cannot serialize", ex);
            }
        }

    }
}
Member

rwinch commented Nov 30, 2015

This can be resolved for Spring Session and Spring Data Redis once DATAREDIS-427 is resolved. Perhaps a bump to @christophstrobl could make that happen?

A workaround for this when using Spring Session 1.1.0.M1+ can be found in spring-state-securing-restful-apis. Using the latest GA is slightly more complex to ensure the custom RedisSerializer is used. Something like the following should work:

@Configuration
class SpringSessionConfig {

    @Bean
    public RedisSerializer<Object> defaultRedisSerializer() {
        return new BeanClassLoaderAwareJdkRedisSerializer();
    }

    @Autowired
    public void configureSessionRedisTemplate(RedisTemplate<String,ExpiringSession> sessionRedisTemplate, RedisSerializer<Object> serializer) {
        sessionRedisTemplate.setDefaultSerializer(serializer);
    }

    static class BeanClassLoaderAwareJdkRedisSerializer implements RedisSerializer<Object>, BeanClassLoaderAware {
        private Converter<Object, byte[]> serializer = new SerializingConverter();
        private Converter<byte[], Object> deserializer;

        @Override
        public void setBeanClassLoader(ClassLoader classLoader) {
            this.deserializer = new DeserializingConverter(new DefaultDeserializer(classLoader));
        }

        public Object deserialize(byte[] bytes) {
            if (ObjectUtils.isEmpty(bytes)) {
                return null;
            }

            try {
                Object result = deserializer.convert(bytes);
                return result;
            } catch (Exception ex) {
                throw new SerializationException("Cannot deserialize", ex);
            }
        }

        public byte[] serialize(Object object) {
            if (object == null) {
                return new byte[0];
            }
            try {
                return serializer.convert(object);
            } catch (Exception ex) {
                throw new SerializationException("Cannot serialize", ex);
            }
        }

    }
}
@wilkinsona

This comment has been minimized.

Show comment
Hide comment
@wilkinsona

wilkinsona Dec 1, 2015

Member

While we're waiting on some changes in Spring Data Redis and Spring Session, we could probably auto-configure this workaround in DevTools.

Member

wilkinsona commented Dec 1, 2015

While we're waiting on some changes in Spring Data Redis and Spring Session, we could probably auto-configure this workaround in DevTools.

@eagle8625

This comment has been minimized.

Show comment
Hide comment
@eagle8625

eagle8625 Feb 23, 2017

I refer to this link to resolve my problem: http://stackoverflow.com/questions/2591779/cast-across-classloader, however I don't know why one class is read into ObjectOutputStream then written into ObjectInputStream can change its classloader.

eagle8625 commented Feb 23, 2017

I refer to this link to resolve my problem: http://stackoverflow.com/questions/2591779/cast-across-classloader, however I don't know why one class is read into ObjectOutputStream then written into ObjectInputStream can change its classloader.

@raysuliteanu

This comment has been minimized.

Show comment
Hide comment
@raysuliteanu

raysuliteanu Jul 19, 2017

FYI same problem with Apache Ignite; add an object to an Ignite cache in the restartedMain and get from cache in another thread (embedded Tomcat nio thread) with launcher classloader and get a CCE. Don't use devtools and problem goes away. This is on Boot 1.5.4 and Ignite 2.0.0

raysuliteanu commented Jul 19, 2017

FYI same problem with Apache Ignite; add an object to an Ignite cache in the restartedMain and get from cache in another thread (embedded Tomcat nio thread) with launcher classloader and get a CCE. Don't use devtools and problem goes away. This is on Boot 1.5.4 and Ignite 2.0.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment