【问题标题】:Spring Boot with Session/Redis Serialization Error with Bad Active Directory Ldap Credentials带有错误 Active Directory Ldap 凭据的 Session/Redis 序列化错误的 Spring Boot
【发布时间】:2015-12-21 10:49:47
【问题描述】:

您好,我是 Spring 和 Java 新手,我正在尝试按照本教程 https://spring.io/guides/tutorials/spring-security-and-angular-js/ 中的描述实现网关身份验证服务器

我让一切正常,然后尝试对我们公司的 Ldap 服务器实施身份验证。如果我使用有效的用户名和密码,它就可以工作。当我使用无效凭据时,应用程序错误。

我不在工作,所以我没有确切的错误,但它返回一个 ldap 错误 (com.sun.jndi.ldap.LdapCtx) 并且 Redis 正在尝试对其进行序列化。

我的配置中是否缺少某些内容。根据我的阅读,我认为我应该寻找一种方法来包装/扩展类并实现 Serializable,但我不确定使用 Spring Boot 执行此操作的侵入性最小的方法。

非常感谢任何帮助。

谢谢,

迈克·科瓦尔斯基

PS 到目前为止,我主要从事动态语言和框架的工作(Javascript/Node、Php/Laravel)

以下是我认为安全配置的相关部分:

@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;
  }

}

这是使用无效凭据的错误的一部分:

2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] o.s.security.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] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@44258b05
2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] o.s.security.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] o.s.security.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] o.s.security.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] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/login'; against '/logout'
2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] o.s.security.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] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/login'; against '/login'
2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] w.a.UsernamePasswordAuthenticationFilter : Request is to process authentication
2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] o.s.s.authentication.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] w.a.UsernamePasswordAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] w.a.UsernamePasswordAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication
2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] w.a.UsernamePasswordAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.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] o.s.s.web.DefaultRedirectStrategy        : Redirecting to '/login?error'
2015-09-24 15:07:31.115 DEBUG 6552 --- [nio-8080-exec-3] w.c.HttpSessionSecurityContextRepository : 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] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2015-09-24 15:07:31.148 ERROR 6552 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[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

【问题讨论】:

    标签: java active-directory redis ldap spring-ldap


    【解决方案1】:

    要缓存的Java对象必须实现serializable接口,因为spring会将对象序列化后存储到redis中。

    例如public class Store implement Serializable

    这里的小故事是确保你在你的类上实现可序列化的接口。

    我希望这会有所帮助。祝你好运。

    【讨论】:

    • 应该是实现而不是实现
    • @CameronWard Serializable 应该被实现。
    【解决方案2】:

    我想出了解决这个问题的办法。我愿意接受任何改进答案的建议。

    解决方案不完整,因为我需要专门寻找 com.sun.jndi.ldap.LdapCtx 在序列化失败时输入,这样我就可以处理该特定情况并将SerializationException 扔给所有其他情况。但我认为这个总体思路可能对任何被阻止的人有用。

    现在,当使用无效凭据(例如错误的用户名或错误的密码)时,应用程序会返回登录页面而不是崩溃 :)

    我添加了一些RedisConfiguration 来替换正在使用的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;
      }
    }
    

    这是我对RedisSerializer&lt;Object&gt; 的实现(LdapFailAwareRedisObjectSerializer 来自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】:

      这对我使用类后效果很好 org.springframework.core.serializer.support.DeserializingConverter 和 org.springframework.core.serializer.support.SerializingConverter

      /**
       * @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);
      }
      }
      

      【讨论】:

        猜你喜欢
        • 2015-12-04
        • 2020-04-08
        • 2019-12-08
        • 1970-01-01
        • 2011-11-21
        • 1970-01-01
        • 2013-09-15
        • 2014-12-25
        • 2019-05-21
        相关资源
        最近更新 更多