带有Hazelcast支持的Spring Boot应用程序支持Active Directory登录失败的Spring会话序列化异常

时间:2016-07-14 20:42:17

标签: spring-security spring-boot hazelcast spring-ldap spring-session

我们有一个使用Hazecast支持的Spring Session的Spring Boot应用程序。该应用程序使用Spring Security与Active Directory进行身份验证。如果用户尝试使用无效凭据登录,则会引发序列化错误:

com.hazelcast.nio.serialization.HazelcastSerializationException: java.io.NotSerializableException: com.sun.jndi.ldap.LdapCtx
        at com.hazelcast.nio.serialization.SerializationServiceImpl.handleException(SerializationServiceImpl.java:380)
        at com.hazelcast.nio.serialization.SerializationServiceImpl.toData(SerializationServiceImpl.java:235)
        at com.hazelcast.nio.serialization.SerializationServiceImpl.toData(SerializationServiceImpl.java:207)
        at com.hazelcast.map.impl.MapServiceContextImpl.toData(MapServiceContextImpl.java:338)
        at com.hazelcast.map.impl.proxy.MapProxySupport.toData(MapProxySupport.java:1160)
        at com.hazelcast.map.impl.proxy.MapProxyImpl.put(MapProxyImpl.java:96)
        at org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration$ExpiringSessionMap.put(HazelcastHttpSessionConfiguration.java:112)
        at org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration$ExpiringSessionMap.put(HazelcastHttpSessionConfiguration.java:102)
        at org.springframework.session.MapSessionRepository.save(MapSessionRepository.java:72)
        at org.springframework.session.MapSessionRepository.save(MapSessionRepository.java:36)
        at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:194)
        at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:170)
        at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:128)
        at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:65)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:103)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
        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:141)
        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:522)
        at org.apache.coyote.ajp.AbstractAjpProcessor.process(AbstractAjpProcessor.java:868)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1502)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1458)
        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)

这似乎与Redis的另一个问题(Spring Boot with Session/Redis Serialization Error with Bad Active Directory Ldap Credentials)完全相同,但是在Spring会话中Redis的Hazelcast会话映射中似乎没有类似的机制来控制序列化。

我们已经提出了一个解决方法(下面),但它似乎不太理想,因为HazelcastHttpSessionConfiguration似乎并没有真正适用于扩展,所以我们似乎应该有一个更清洁的方式没有看到。

我们正在扩展HazelcastHttpSessionConfiguration以获取ExpiringSessionMap以在尝试序列化之前删除LdapCtx。这似乎并不理想,因为HazelcastHttpSessionConfiguration并没有真正地将其自我扩展,需要重复代码。

我们缺少更好的解决方案吗?

@Configuration
public class CustomHazelcastHttpSessionMapConfiguration extends HazelcastHttpSessionConfiguration{

    private String sessionMapName = "spring:session:sessions";
    private int maxInactiveIntervalInSeconds = 1800;

    @Bean
    public SessionRepository<ExpiringSession> sessionRepository(
            HazelcastInstance hazelcastInstance, SessionEntryListener sessionListener) {
        super.sessionRepository(hazelcastInstance, sessionListener);

        MapSessionRepository sessionRepository = new MapSessionRepository(
                new CustomExpiringSessionMap(hazelcastInstance.getMap(this.sessionMapName)));
        sessionRepository
                .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);

        return sessionRepository;
    }

    @Override
    public void setSessionMapName(String sessionMapName) {
        this.sessionMapName = sessionMapName;
        super.setSessionMapName(sessionMapName);
    }

    @Override
    public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
        this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
        super.setMaxInactiveIntervalInSeconds(maxInactiveIntervalInSeconds);
    }

    static class CustomExpiringSessionMap implements Map<String, ExpiringSession> {
        private IMap<String, ExpiringSession> delegate;

        CustomExpiringSessionMap(IMap<String, ExpiringSession> delegate) {
            this.delegate = delegate;
        }

        public ExpiringSession put(String key, ExpiringSession value) {
            if (value == null) {
                return this.delegate.put(key, value);
            }
            for (String attrName : value.getAttributeNames()) {
                Object attrVal = value.getAttribute(attrName);
                // Don't serialize LdapCtx in a BadCredentialsException
                if (attrVal instanceof BadCredentialsException &&
                        ((BadCredentialsException) attrVal).getCause() != null &&
                        ((BadCredentialsException) attrVal).getCause() instanceof ActiveDirectoryAuthenticationException &&
                        ((BadCredentialsException) attrVal).getCause().getCause() != null &&
                        ((BadCredentialsException) attrVal).getCause().getCause() instanceof javax.naming.AuthenticationException) {
                    ((javax.naming.AuthenticationException) ((BadCredentialsException) attrVal).getCause().getCause()).setResolvedObj(null);
                }
            }
            return this.delegate.put(key, value, value.getMaxInactiveIntervalInSeconds(),
                    TimeUnit.SECONDS);
        }

    /*... copy and paste of the rest of ExpiringSessionMap */
    }
}

2 个答案:

答案 0 :(得分:0)

更清洁的解决方案是transient-attributes

如果您有一个Web过滤器,您可以传递一个属性列表来控制行为,这个是一个逗号分隔的属性名称列表,可以从序列化中排除。

如果您需要更多信息,请告诉我。

答案 1 :(得分:0)

您应该为遇到问题的对象配置custom serialization

通过这种方式,您可以解决Hazelcast配置中的问题,而无需扩展/复制Spring Session的Hazelcast配置。