I use Spring session with a Redis repository.
I also use the same Redis to store cache data.
It works fine but I the session attributes that are storred in Redis are unreadable.
When I try to read the content of the repository, I see this :
http://img11.hostingpics.net/pics/492480redisexpiration.png
Here is my configuration classes :
@Configuration
@EnableRedisHttpSession
public class SessionRepositoryConfig {
@Value("${spring.redis.host}")
private String redisHostName;
@Value("${spring.redis.port}")
private Integer redisPort;
@Value("${spring.redis.expiration.short}")
private Integer redisExpirationShort;
@Value("${spring.redis.expiration.medium}")
private Integer redisExpirationMedium;
@Value("${spring.redis.expiration.long}")
private Integer redisExpirationLong;
@Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisHostName);
factory.setPort(redisPort);
factory.setUsePool(true);
return factory;
}
@Bean
@Order(value = 0)
public FilterRegistrationBean sessionRepositoryFilterRegistration(SessionRepositoryFilter springSessionRepositoryFilter) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new DelegatingFilterProxy(springSessionRepositoryFilter));
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
return filterRegistrationBean;
}
}
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
@Primary
@Bean
public RedisTemplate<String,ExpiringSession> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ExpiringSession> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new LdapFailAwareRedisObjectSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
};
}
}
I use a custom serializer because I had an issue with the serialization of ldap information (http://stackoverflow.com/questions/32751094/spring-boot-with-session-redis-serialization-error-with-bad-active-directory-lda).
Any idea why I can't read content of the redis database ?
I think this is a serialization issue but I don't find any solution.
@YLombardi Thank you for your question :)
You cannot really read bytes in Redis until they are converted and the LdapFailAwareRedisObjectSerializer is writing the object to its JDK serialized version. You would need to change
template.setHashValueSerializer(new LdapFailAwareRedisObjectSerializer());
to use something that writes in a human readable format (i.e. JSON).
Does this help?
@rwinch Thank you for your quick answer.
I already try to change the HashValueSerializer to use a default serializer (GenericJackson2JsonRedisSerializer and JacksonJsonRedisSerializer
java.lang.RuntimeException: org.springframework.data.redis.serializer.SerializationException: Could not read JSON: No suitable constructor found for type [simple type, class org.springframework.security.web.savedrequest.DefaultSavedRequest]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: [B@53736193; line: 1, column: 78]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class org.springframework.security.web.savedrequest.DefaultSavedRequest]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: [B@53736193; line: 1, column: 78]
It sounds as though you need to handle non-default constructor on third party classes. For example http://stackoverflow.com/questions/11838039/jackson-3rd-party-class-with-no-default-constructor
Because of this problem I can't share spring app session with other non spring apps.
@Lev09 Have you tried using GenericJackson2JsonRedisSerializer? #387 Might be useful too
@rwinch I tried out this:
@Bean
RedisOperations<String, Object> redisTemplate(RedisConnectionFactory rcf) {
RedisTemplate<String, Object> rt = new RedisTemplate<String, Object>();
rt.setConnectionFactory(rcf);
rt.setKeySerializer(new StringRedisSerializer());
rt.setValueSerializer(new GenericJackson2JsonRedisSerializer());
rt.setHashKeySerializer(new StringRedisSerializer());
rt.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return rt;
}
but I am still getting not readable value:
"\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01S8-\t\xf2"
@Lev09 Are you certain that is on a new session? Where is the value at (is it a hash key, value, etc). What version of Spring Session are you on?
@rwinch I am using spring security with spring session 1.0.2.RELEASE, so when I log in, the session is created and saved to Redis automatically.
And I always restart my Redis container and check sessions via redis-cli, so I am sure that I am on new session.
When I am trying to read the "creationTime" session attribute with redis-cli hmget spring:session:sessions:{mySessionId} creationTime it returns unreadable value
I got json serialized session persitence to work using OxmSerializer + XStream + JettisonMappedXmlDriver
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
XStreamMarshaller xStreamMarshaller = new XStreamMarshaller();
xStreamMarshaller.setStreamDriver(new JettisonMappedXmlDriver());
return new OxmSerializer(xStreamMarshaller, xStreamMarshaller);
}
And redis gives me
$ redis-cli hmget spring:session:sessions:d3444c2f-77ab-4889-a2fe-90dee54af9be creationTime
1) "{\"long\":1488537096294}"
$ redis-cli hmget spring:session:sessions:d3444c2f-77ab-4889-a2fe-90dee54af9be sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN
1) "{\"org.springframework.security.web.csrf.DefaultCsrfToken\":{\"token\":\"7e892253-ef37-4971-ad36-3399056fa859\",\"parameterName\":\"_csrf\",\"headerName\":\"X-CSRF-TOKEN\"}}"
But, unfortunately jettison is not really maintained anymore.
GenericJackson2JsonRedisSerializer do not register spring security jackson2 modules to ObjectMapper, which is reason why org.springframework.security.web.savedrequest.DefaultSavedRequest cannot be deserialized.
This setup works with basic http authentication.
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(null)));
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
mapper.registerModules(SecurityJackson2Modules.getModules(this.getClass().getClassLoader()));
return new GenericJackson2JsonRedisSerializer(mapper);
}
But, i 've problems with OAuth2Authentication and OAuth2AuthenticationDetails types, that do not have jackson 2 mixins types. https://github.com/spring-projects/spring-security-oauth/issues/983
Most helpful comment
@rwinch I am using spring security with spring session 1.0.2.RELEASE, so when I log in, the session is created and saved to Redis automatically.
And I always restart my Redis container and check sessions via redis-cli, so I am sure that I am on new session.
When I am trying to read the "creationTime" session attribute with
redis-cli hmget spring:session:sessions:{mySessionId} creationTimeit returns unreadable value