Skip to content
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

Unreadable session after serialization #361

Open
YLombardi opened this issue Feb 9, 2016 · 10 comments
Open

Unreadable session after serialization #361

YLombardi opened this issue Feb 9, 2016 · 10 comments
Labels
for: stack-overflow A question that's better suited to stackoverflow.com

Comments

@YLombardi
Copy link

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.

@rwinch
Copy link
Member

rwinch commented Feb 9, 2016

@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 rwinch added the for: stack-overflow A question that's better suited to stackoverflow.com label Feb 9, 2016
@YLombardi
Copy link
Author

@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]

@rwinch
Copy link
Member

rwinch commented Feb 23, 2016

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

@Lev09
Copy link

Lev09 commented Mar 2, 2016

Because of this problem I can't share spring app session with other non spring apps.

@rwinch
Copy link
Member

rwinch commented Mar 2, 2016

@Lev09 Have you tried using GenericJackson2JsonRedisSerializer? #387 Might be useful too

@Lev09
Copy link

Lev09 commented Mar 2, 2016

@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"

@rwinch
Copy link
Member

rwinch commented Mar 2, 2016

@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?

@Lev09
Copy link

Lev09 commented Mar 3, 2016

@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

@patope
Copy link

patope commented Mar 3, 2017

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.

@patope
Copy link

patope commented Mar 3, 2017

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. spring-attic/spring-security-oauth#983

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: stack-overflow A question that's better suited to stackoverflow.com
Projects
None yet
Development

No branches or pull requests

4 participants