Redis session repository with Jackson cannot serialize BadCredentialException

See original GitHub issue

I just migrate my application to spring-boot 2 / Spring 5 and I have difficulties to configure the session cache. I use spring-security, spring-session-data-redis and jackson2. When I use a good login/password, everything works fine. The session is stored in Redis with a Json format. But if I enter a wrong password, the application try to store the BadCredentialException and ActiveDirectoryAuthenticationException in Redis (I have 2 AuthenticationProvider : PreAuthenticatedAuthenticationProvider and ActiveDirectoryLdapAuthenticationProvider).

The problem is that Jackson cannot serialize the BadCredentialException.

org.springframework.data.redis.serializer.SerializationException: Could not read JSON: The class with org.springframework.security.authentication.BadCredentialsException and name of org.springframework.security.authentication.BadCredentialsException is not whitelisted. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details; nested exception is java.lang.IllegalArgumentException: The class with org.springframework.security.authentication.BadCredentialsException and name of org.springframework.security.authentication.BadCredentialsException is not whitelisted. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details
	at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:132)
	at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:110)
	at org.springframework.data.redis.core.AbstractOperations.deserializeHashValue(AbstractOperations.java:354)
	at org.springframework.data.redis.core.AbstractOperations.deserializeHashMap(AbstractOperations.java:298)
	at org.springframework.data.redis.core.DefaultHashOperations.entries(DefaultHashOperations.java:233)
	at org.springframework.data.redis.core.DefaultBoundHashOperations.entries(DefaultBoundHashOperations.java:172)
	at org.springframework.session.data.redis.RedisOperationsSessionRepository.getSession(RedisOperationsSessionRepository.java:429)
	at org.springframework.session.data.redis.RedisOperationsSessionRepository.findById(RedisOperationsSessionRepository.java:398)
	at org.springframework.session.data.redis.RedisOperationsSessionRepository.findById(RedisOperationsSessionRepository.java:245)
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.lambda$getRequestedSessionId$0(SessionRepositoryFilter.java:359)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
	at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1351)
	at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
	at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getRequestedSessionId(SessionRepositoryFilter.java:360)
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.isRequestedSessionIdValid(SessionRepositoryFilter.java:268)
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:230)
	at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:196)
	at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:149)
	at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:81)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:472)
	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:395)
	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:316)
	at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:395)
	at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:254)
	at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:349)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:175)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)

Here is my configuration :

@Configuration
@EnableRedisHttpSession
public class SessionRepositoryConfig implements BeanClassLoaderAware {

    @Value("${spring.redis.host}")
    private String redisHostName;

    @Value("${spring.redis.port}")
    private Integer redisPort;

    @Value("${session.max-inactive-time}")
    private Integer sessionMaxInactiveTime;

    private ClassLoader loader;

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.loader = classLoader;
    }

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        JedisClientConfiguration jedisConfig = JedisClientConfiguration.builder().usePooling().build();
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration(redisHostName, redisPort);
        return new JedisConnectionFactory(redisConfig, jedisConfig);
    }

    private ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModules(SecurityJackson2Modules.getModules(this.loader));
        return mapper;
    }

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer(objectMapper()));
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(jedisConnectionFactory);
        return template;
    }

    @Bean
    public RedisOperationsSessionRepository sessionRepository(RedisTemplate<Object, Object> redisTemplate) {
        RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(redisTemplate);
        sessionRepository.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
        sessionRepository.setDefaultMaxInactiveInterval(sessionMaxInactiveTime);
        return sessionRepository;
    }
}

Is it possible to configure Spring to store the session only if the login is a success ?

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
vpaviccommented, Mar 6, 2018

Thanks for the report @YLombardi.

I’ve reproduced the issue using our Redis with JSON serialization Boot sample app and this diff:

diff --git a/samples/boot/redis-json/src/main/java/sample/web/HomeController.java b/samples/boot/redis-json/src/main/java/sample/web/HomeController.java
index ae8150e7..d73e2254 100644
--- a/samples/boot/redis-json/src/main/java/sample/web/HomeController.java
+++ b/samples/boot/redis-json/src/main/java/sample/web/HomeController.java
@@ -15,8 +15,11 @@
  */
 package sample.web;
 
+import java.util.UUID;
+
 import javax.servlet.http.HttpServletRequest;
 
+import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.stereotype.Controller;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -34,6 +37,7 @@ public class HomeController {
                        HttpServletRequest request) {
                if (!ObjectUtils.isEmpty(key) && !ObjectUtils.isEmpty(value)) {
                        request.getSession().setAttribute(key, value);
+                       request.getSession().setAttribute(UUID.randomUUID().toString(), new BadCredentialsException("test"));
                }
                return "home";
        }

This should perhaps be handled by Spring Security, WDYT @rwinch? Same error was reported in spring-projects/spring-security#4370 (comment).

0reactions
vpaviccommented, Mar 9, 2018

Closing since this has been addressed on Spring Security side by spring-projects/spring-security#5087.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Spring Boot with Session/Redis Serialization Error with Bad ...
The Java object to be cached must implement the serializable interface, because spring will serialize the object and store it in redis.
Read more >
Handling Deserialization errors in Spring Redis Sessions
This approach ensures that every time a de-serialization error is thrown while trying to read an object from the session, that object is...
Read more >
Storing Json Formatted Spring Session in Redis - Rustam's Blog
Anything that is related to session attribute should be JSON serializable, otherwise, it will be keep throwing and errors. Lets assume we have ......
Read more >
JacksonJsonRedisSerializer (Spring Data Redis 1.1.1 ...
RedisSerializer that can read and write JSON using Jackson's ObjectMapper . ... Note:Null objects are serialized as empty arrays and vice versa.
Read more >
spring-projects/spring-session - Gitter
Does Hazelcast support using Jackson instead of native serialization? ... I have a java application and I use spring-session backed by redis for...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found