Skip to content

Redis session repository with Jackson cannot serialize BadCredentialException #1013

Closed
@YLombardi

Description

@YLombardi

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 ?

Metadata

Metadata

Assignees

Labels

for: stack-overflowA question that's better suited to stackoverflow.com

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions