I'm following this tutorial to add spring session (through redis) to my application. http://www.baeldung.com/spring-session
I add the dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
and the SessionConfig.java class
@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
}
I've done this on a blank project and things work great. But my current project has some custom user objects with a custom UserDetailsService and that's where things go wrong.
Sat Apr 07 11:21:49 EDT 2018
There was an unexpected error (type=Internal Server Error, status=500).
Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: com.kreyzon.dollar.entity.User
Now, I've looked around and found suggestions to implement this:
@Bean
public RedisTemplate<Object, Object> sessionRedisTemplate (
RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
That did not work (like didn't even seem to be recognized). I also tried this
@Bean(name = {"defaultRedisSerializer", "springSessionDefaultRedisSerializer"})
RedisSerializer<Object> defaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
Now, this actually did something but still causing errors
Sat Apr 07 11:39:21 EDT 2018
There was an unexpected error (type=Internal Server Error, status=500).
Could not read JSON: Can not construct instance of org.springframework.security.authentication.UsernamePasswordAuthenticationToken: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?) at [Source: [B@4dc133; line: 1, column: 184] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.security.authentication.UsernamePasswordAuthenticationToken: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?) at [Source: [B@4dc133; line: 1, column: 184] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"])
I've also explored other serializers like Jackson2JsonRedisSerializer
and GenericJackson2JsonRedisSerializer
but getting the same errors..
all help appreciated, thank you.
update:
I've also tried addeding this custom serializer
public class CustomRedisSerializer implements RedisSerializer<Object> {
private Converter<Object, byte[]> serializer = new SerializingConverter();
private Converter<byte[], Object> deserializer = new DeserializingConverter();
static final byte[] EMPTY_ARRAY = new byte[0];
public Object deserialize(byte[] bytes) {
if (isEmpty(bytes)) {
return null;
}
try {
return deserializer.convert(bytes);
} catch (Exception ex) {
throw new SerializationException("Cannot deserialize", ex);
}
}
public byte[] serialize(Object object) {
if (object == null) {
return EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {
return EMPTY_ARRAY;
//TODO add logic here to only return EMPTY_ARRAY for known conditions
// else throw the SerializationException
// throw new SerializationException("Cannot serialize", ex);
}
}
private boolean isEmpty(byte[] data) {
return (data == null || data.length == 0);
}
}
SECOND UPDATE:
So I added implements Serializable
to all my custom objects. That worked, go figure! (for some reason not a single post I found on the subject suggested that)
Now, my last issue persists. I'm using LDAP for authentication, and if the authentication fails, it returns an object LdapCtx
which is not serializable and throws an error
Sat Apr 07 12:35:14 EDT 2018
There was an unexpected error (type=Internal Server Error, status=500).
Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: com.sun.jndi.ldap.LdapCtx
Is there any way to force this class to be Serializable?
UPDATE (10/22/2018):
This issue has been resolved https://github.com/spring-projects/spring-security/issues/5378
If you're using serialization mechanism that relies on standard Java serialization, such as JdkSerializationRedisSerializer
, you have to ensure that everything that gets stored in session implements Serializable
. As you've experienced yourself, this is fairly straightforward for classes that are a part of your project, but can be a challenge for 3rd party classes.
I'd suggest you to look closer into JSON based serialization i.e. Jackson2JsonRedisSerializer
. There is a sample app withing the Spring Session codebase that demonstrates this approach.
This particular samples uses SecurityJackson2Modules
to serialize Spring Security's objects. To be able to serialize your 3rd party classes with Jackson based JSON serialization, you should do something similar to what Spring Security provides.