Skip to content

Redis session repository with Jackson cannot serialize BadCredentialException #1013

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

Closed
YLombardi opened this issue Mar 2, 2018 · 5 comments
Assignees
Labels
for: stack-overflow A question that's better suited to stackoverflow.com

Comments

@YLombardi
Copy link

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 ?

@vpavic vpavic self-assigned this Mar 6, 2018
@vpavic vpavic added for: stack-overflow A question that's better suited to stackoverflow.com status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 6, 2018
@vpavic
Copy link
Contributor

vpavic commented 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).

@vpavic vpavic changed the title Spring Redis session repository with Jackson cannot serialize BadCredentialException Mar 6, 2018
@rwinch
Copy link
Member

rwinch commented Mar 7, 2018

@YLombardi Thanks for the report!

@vpavic Thanks for reaching out. I agree. Can you create a ticket or PR in Spring Security to track this?

In the meantime adding your own mapping for the missing classes should work around the issue.

@YLombardi
Copy link
Author

Thanks for your quick reply.

@rwinch How can I add my own mapping ? Do I have to create a Mixin for the class ?

@rwinch
Copy link
Member

rwinch commented Mar 7, 2018

@YLombardi Yes you need to create your own Mixin. If you get something working you can submit a PR which will speed up getting this merged into Spring Security

@vpavic
Copy link
Contributor

vpavic commented Mar 9, 2018

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

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

3 participants