Spring-session: Unreadable session after serialization

Created on 9 Feb 2016  路  10Comments  路  Source: spring-projects/spring-session

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.

stack-overflow

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} creationTime it returns unreadable value

All 10 comments

@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). It store a readable data in the Redis database but when I try to access the application, it create some SerializationException and the application crash :

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cnsilvan picture cnsilvan  路  6Comments

pch851130 picture pch851130  路  6Comments

hantsy picture hantsy  路  3Comments

ysjiang4869 picture ysjiang4869  路  4Comments

michael-wirth picture michael-wirth  路  9Comments