Spring Boot with Session / Redis serialization error with bad Ldap Active Directory credentials

Hi I am new to Spring and Java, I am trying to implement a gateway authentication server as described in this lesson https://spring.io/guides/tutorials/spring-security-and-angular-js/

I got everything that worked, and then tried to implement authentication on our company's Ldap server. It works if I use a valid username and password. When I use the wrong credentials, application errors.

I am not working, so I have no exact error, but it returns the ldap error (com.sun.jndi.ldap.LdapCtx), and Redis is trying to serialize it.

Something I am missing in my configuration. From what I read, I think I should look for a way to wrap / extend the class and implement Serializable, but I'm not sure of the least invasive way to do this using Spring Boot.

Any help is greatly appreciated.

Thanks,

Mike Kowalski

PS I work mainly in dynamic languages ​​and frameworks so far (Javascript / Node, Php / Laravel)

Here is what I consider relevant parts of a security configuration:

@Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .defaultSuccessUrl("/") .loginPage("/login") .permitAll() .and() .logout() .logoutSuccessUrl("/logout") .permitAll(); http .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .csrf().csrfTokenRepository(csrfTokenRepository()) .and() .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class); } @Override protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception { authManagerBuilder .authenticationProvider(activeDirectoryLdapAuthenticationProvider()) .userDetailsService(userDetailsService()); } @Bean public AuthenticationManager authenticationManager() { return new ProviderManager( Arrays.asList(activeDirectoryLdapAuthenticationProvider()) ); } @Bean public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() { ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider( "XXX.XXX", "ldaps://XXX.XXX:636"); provider.setConvertSubErrorCodesToExceptions(true); provider.setUseAuthenticationRequestCredentials(true); return provider; } private Filter csrfHeaderFilter() { return new OncePerRequestFilter() { @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain ) throws ServletException, IOException { CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class .getName()); if (csrf != null) { Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN"); String token = csrf.getToken(); if (cookie == null || token != null && !token.equals(cookie.getValue())) { cookie = new Cookie("XSRF-TOKEN", token); cookie.setPath("/"); response.addCookie(cookie); } } filterChain.doFilter(request, response); } }; } private CsrfTokenRepository csrfTokenRepository() { HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); repository.setHeaderName("X-XSRF-TOKEN"); return repository; } } 

Here is the part of the error that uses invalid credentials:

 2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] ossecurity.web.FilterChainProxy : /login at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter' 2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] osswheader.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.se curity.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@ 44258b05 2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] ossecurity.web.FilterChainProxy : /login at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter' 2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] ossecurity.web.FilterChainProxy : /login at position 5 of 13 in additional filter chain; firing Filter: '' 2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] ossecurity.web.FilterChainProxy : /login at position 6 of 13 in additional filter chain; firing Filter: 'LogoutFilter' 2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] osswumatcher.AntPathRequestMatcher : Checking match of request : '/login'; against '/logout' 2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] ossecurity.web.FilterChainProxy : /login at position 7 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter' 2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] osswumatcher.AntPathRequestMatcher : Checking match of request : '/login'; against '/login' 2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] waUsernamePasswordAuthenticationFilter : Request is to process authentication 2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] ossauthentication.ProviderManager : Authentication attempt using org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider 2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] ctiveDirectoryLdapAuthenticationProvider : Processing authentication request for user: admin 2015-09-24 15:07:31.113 DEBUG 6552 --- [nio-8080-exec-3] ctiveDirectoryLdapAuthenticationProvider : Authentication for admin@countrycurtains.local failed:javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 525, vece] 2015-09-24 15:07:31.113 INFO 6552 --- [nio-8080-exec-3] ctiveDirectoryLdapAuthenticationProvider : Active Directory authentication failed: User was not found in directory 2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] waUsernamePasswordAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials 2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] waUsernamePasswordAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication 2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] waUsernamePasswordAuthenticationFilter : Delegating to authentication failure handler org.springframework.se curity.web.authentication.SimpleUrlAuthenticationFailureHandler@ 28626d9a 2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] .a.SimpleUrlAuthenticationFailureHandler : Redirecting to /login?error 2015-09-24 15:07:31.115 DEBUG 6552 --- [nio-8080-exec-3] ossweb.DefaultRedirectStrategy : Redirecting to '/login?error' 2015-09-24 15:07:31.115 DEBUG 6552 --- [nio-8080-exec-3] wcHttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession. 2015-09-24 15:07:31.139 DEBUG 6552 --- [nio-8080-exec-3] sswcSecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed 2015-09-24 15:07:31.148 ERROR 6552 --- [nio-8080-exec-3] oaccC[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: com.sun.jndi.ldap.LdapCtx at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:52) at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:146) at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:128) at org.springframework.data.redis.core.DefaultBoundHashOperations.putAll(DefaultBoundHashOperations.java:85) at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveDelta(RedisOperationsSessionRepository.java:409) at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.access$000(RedisOperationsSessionRepository.java:331) at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:211) at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:141) at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:193) at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:169) at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:127) at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:65) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:68) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478) 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) Caused by: org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: com.sun.jndi.ldap.LdapCtx at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:67) at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:34) at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:50) ... 40 common frames omitted Caused by: java.io.NotSerializableException: com.sun.jndi.ldap.LdapCtx at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441) at java.lang.Throwable.writeObject(Throwable.java:985) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:44) at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:62) ... 42 common frames omitted 
+6
source share
3 answers

I figured out a solution to this problem. I am open to any suggestions for improving the answer.

The solution is not complete, because I need com.sun.jndi.ldap.LdapCtx when serialization fails, so I can handle this specific case and throw a SerializationException in everyone else. But I thought that the general idea could be useful for everyone who is blocked by this.

Now, when using invalid credentials (for example, "Bad username" or "Invalid password"), the application returns to the login page, and does not explode :)

I added some RedisConfiguration to replace the RedisTemplate Spring session.

 import com.gateway.utils.LdapFailAwareRedisObjectSerializer; @Configuration public class RedisConfiguration { @Primary @Bean public RedisTemplate<String,ExpiringSession> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, ExpiringSession> template = new RedisTemplate<String, ExpiringSession>(); template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new LdapFailAwareRedisObjectSerializer()); template.setConnectionFactory(connectionFactory); return template; } } 

Here is my implementation of RedisSerializer<Object> ( LdapFailAwareRedisObjectSerializer , which is obtained from here )

 public class LdapFailAwareRedisObjectSerializer implements RedisSerializer<Object> { private Converter<Object, byte[]> serializer = new SerializingConverter(); private Converter<byte[], Object> deserializer = new DeserializingConverter(); static final byte[] EMPTY_ARRAY = new byte[0]; public Object deserialize(byte[] bytes) { if (isEmpty(bytes)) { return null; } try { return deserializer.convert(bytes); } catch (Exception ex) { throw new SerializationException("Cannot deserialize", ex); } } public byte[] serialize(Object object) { if (object == null) { return EMPTY_ARRAY; } try { return serializer.convert(object); } catch (Exception ex) { return EMPTY_ARRAY; //TODO add logic here to only return EMPTY_ARRAY for known conditions // else throw the SerializationException // throw new SerializationException("Cannot serialize", ex); } } private boolean isEmpty(byte[] data) { return (data == null || data.length == 0); } } 
+3
source

This just works fine for me after using the org.springframework.core.serializer.support.DeserializingConverter and org.springframework.core.serializer.support.SerializingConverter classes

 /** * @author Meron Abraha 12/18/17 */ public class CustomRedisSerializer implements RedisSerializer<Object> { private Converter<Object, byte[]> serializer = new SerializingConverter(); private Converter<byte[], Object> deserializer = new DeserializingConverter(); static final byte[] EMPTY_ARRAY = new byte[0]; public Object deserialize(byte[] bytes) { if (isEmpty(bytes)) { return null; } try { return deserializer.convert(bytes); } catch (Exception ex) { throw new SerializationException("Cannot deserialize", ex); } } public byte[] serialize(Object object) { if (object == null) { return EMPTY_ARRAY; } try { return serializer.convert(object); } catch (Exception ex) { return EMPTY_ARRAY; } } private boolean isEmpty(byte[] data) { return (data == null || data.length == 0); } } 
+2
source

A cached Java object must implement a serializable interface since Spring will serialize the object and store it in Redis.

e.g. public class Store implement Serializable

A short story here: make sure you implement a serializable interface in your class.

I hope this helps. Good luck.

0
source

Source: https://habr.com/ru/post/1232151/


All Articles