zoukankan      html  css  js  c++  java
  • LdapTemplate忽略ssl证书

    一、背景

    最近做JAVA的LDAP操作,使用的是Spring的LdapTemplate,基本上一个bean注入就完成了LdapTemplate的初始化,正常连接389端口,现在要要试一下HTTPS的连接方式

    spring.ldap:
      urls: ldap://ip:389
      base: dc=xxx,dc=com
      username: xxx
      password: xxx
      
    @Bean
        public LdapTemplate firstLdapTemplate() {
            LdapContextSource contextSource = new LdapContextSource();
            contextSource.setUrl(url);
            contextSource.setBase(base);
            contextSource.setUserDn(username);
            contextSource.setPassword(password);
            contextSource.setPooled(false);
            contextSource.afterPropertiesSet(); // important
    
            LdapTemplate template = new LdapTemplate(contextSource);
            return template;
        }
    

    二、采坑

    把urls改成了:ldaps://xxx:636,启动报错,收到了如下错误:

    Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target  
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)  
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)  
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)  
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)  
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1341)  
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)  
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)  
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)  
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)  
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)  
        at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:882)  
        at sun.security.ssl.AppInputStream.read(AppInputStream.java:102)  
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)  
        at java.io.BufferedInputStream.read1(BufferedInputStream.java:275)  
        at java.io.BufferedInputStream.read(BufferedInputStream.java:334)  
        at com.sun.jndi.ldap.Connection.run(Connection.java:853)  
        at java.lang.Thread.run(Thread.java:744)  
    Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target  
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)  
        at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)  
        at sun.security.validator.Validator.validate(Validator.java:260)  
        at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)  
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)  
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)  
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)  
        ... 12 more  
    Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target  
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)  
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)  
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)  
        ... 18 more  
    

    说的是校验证书错误,运维搭建的ldap服务器用的是自签的证书,也没有给我证书,所以会失败,那我就忽略证书验证吧,这样总可以吧...,google了一下,有答案,心里很开心:

      LdapContextSource lcs = new LdapContextSource();
        lcs.setBase("[base]");
        lcs.setUserDn("[userDn]");
        lcs.setPassword("[password]");
        lcs.setPooled(false);
        lcs.setUrl("ldaps://[server-address]:636");
    
        DefaultTlsDirContextAuthenticationStrategy strategy = new DefaultTlsDirContextAuthenticationStrategy();
        strategy.setShutdownTlsGracefully(true);
        strategy.setSslSocketFactory(new CustomSSLSocketFactory());  // <-- not considered at all
        strategy.setHostnameVerifier(new HostnameVerifier(){
    
            @Override
            public boolean verify(String hostname, SSLSession session){
    
                return true;
            }
        });
    
        lcs.setAuthenticationStrategy(strategy);
        lcs.afterPropertiesSet();
    

    CustomSSLSocketFactory是自定义的SSL工厂里面加载自己实现X509TrustManager,信任自签证书,然后运行,还是报错,瞬间怀疑啊,又是google一顿猛如虎的操作,有人遇到了跟我一样的问题(https://stackoverflow.com/questions/30546193/spring-ldapcontextsource-ignores-sslsocketfactory/30573130), 还好有人给出解决方法:

    To fix the error use SimpleDirContextAuthenticationStrategy instead of DefaultTlsDirContextAuthenticationStrategy

    但是没给出怎么用SimpleDirContextAuthenticationStrategy解决上面的问题,好吧,继续搜,找了一些答案,但是还是不行,还是继续报上面的错误,泪崩了,一直怀疑是不是自定义的CustomSSLSocketFactory与X509TrustManager有问题,而且网上的基本已经被我搜的差不多了,期间看到一个解决方案(http://java2db.com/jndi-ldap-programming/solution-to-sslhandshakeexception),我一直没试,因为不是LdapContextSource与ldaptemplate,用的是ldapContext,因为我无法通过ldapContext来构建ldaptemplate,所有就一直没试

    由于怀疑CustomSSLSocketFactory与X509TrustManager有问题,然后网上又说这个方案是可以的,那就索性用来验证下我的CustomSSLSocketFactory与X509TrustManager到底有没有问题,写了个main函数,组织如下代码:

        String url = "ldaps://ip:636";
        String conntype = "simple";
        String AdminDn  = "xxx";
        String password = "xxx";
        Hashtable<String, String> environment = new Hashtable<String, String>();
        
        environment.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
        environment.put(Context.PROVIDER_URL,url); 
        environment.put("java.naming.ldap.factory.socket", CustomSSLSocketFactory.class.getName());        
        environment.put(Context.SECURITY_AUTHENTICATION,conntype);         
        environment.put(Context.SECURITY_PRINCIPAL,AdminDn);
        environment.put(Context.SECURITY_CREDENTIALS, password);
        
        ldapContext = new InitialDirContext(environment);
        
        System.out.println("Bind successful");
    

    果然可以!!!!

    既然这个可以,那么LdapContextSource应该也可以啊,只要按照上面,把socket注入到上下文中啊,可是LdapContextSource它压根就没给我设定的接口,没办法了,只能看源码了,看看LdapContextSource的url,name,password是怎么初始化进去的

    AbstractContextSource类的getAuthenticatedEnv:

    protected Hashtable<String, Object> getAuthenticatedEnv(String principal, String credentials) {
    		// The authenticated environment should always be rebuilt.
    		Hashtable<String, Object> env = new Hashtable<String, Object>(getAnonymousEnv());
    		setupAuthenticatedEnvironment(env, principal, credentials);
    		return env;
    	}
    

    然后看了下getAnonymousEnv是protected,完美,是不是可以继承LdapContextSource,然后重写getAnonymousEnv方法,立马测试下:

    public class SSLLdapContextSource extends LdapContextSource {
        public Hashtable<String, Object> getAnonymousEnv(){
            Hashtable<String, Object> anonymousEnv = super.getAnonymousEnv();
            anonymousEnv.put("java.naming.security.protocol", "ssl");
            anonymousEnv.put("java.naming.ldap.factory.socket", CustomSSLSocketFactory.class.getName());
            anonymousEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            return anonymousEnv;
        }
    }
    
    @Bean
    public LdapTemplate secondLdapTemplate() {
        //此处用     SSLLdapContextSource
        SSLLdapContextSource contextSource = new SSLLdapContextSource();
        contextSource.setUrl(url);
        contextSource.setBase(base);
        contextSource.setUserDn(username);
        contextSource.setPassword(password);
        contextSource.setPooled(false);
        contextSource.afterPropertiesSet(); // important
        LdapTemplate template = new LdapTemplate(contextSource);
        return template;
    }
    

    欧了,,只需简单的继承下LdapContextSource,前后加起来折腾了一天时间,o(╥﹏╥)o

    附上CustomSSLSocketFactory:

    public class CustomSSLSocketFactory extends SSLSocketFactory
    {
        private SSLSocketFactory socketFactory;
        public CustomSSLSocketFactory()
        {
            try {
                SSLContext ctx = SSLContext.getInstance("TLS");
                ctx.init(null, new TrustManager[]{ new DummyTrustmanager()}, new SecureRandom());
                socketFactory = ctx.getSocketFactory();
            } catch ( Exception ex ){ ex.printStackTrace(System.err);  }
        }
        public static SocketFactory getDefault(){
            return new CustomSSLSocketFactory();
        }
        @Override
        public String[] getDefaultCipherSuites()
        {
            return socketFactory.getDefaultCipherSuites();
        }
        @Override
        public String[] getSupportedCipherSuites()
        {
            return socketFactory.getSupportedCipherSuites();
        }
        @Override
        public Socket createSocket(Socket socket, String string, int num, boolean bool) throws IOException
        {
            return socketFactory.createSocket(socket, string, num, bool);
        }
        @Override
        public Socket createSocket(String string, int num) throws IOException, UnknownHostException
        {
            return socketFactory.createSocket(string, num);
        }
        @Override
        public Socket createSocket(String string, int num, InetAddress netAdd, int i) throws IOException, UnknownHostException
        {
            return socketFactory.createSocket(string, num, netAdd, i);
        }
        @Override
        public Socket createSocket(InetAddress netAdd, int num) throws IOException
        {
            return socketFactory.createSocket(netAdd, num);
        }
        @Override
        public Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException
        {
            return socketFactory.createSocket(netAdd1, num, netAdd2, i);
        }
    
    
        /**
         * 证书
         */
        public static class DummyTrustmanager implements X509TrustManager {
            public void checkClientTrusted(X509Certificate[] cert, String string) throws CertificateException
            {
            }
            public void checkServerTrusted(X509Certificate[] cert, String string) throws CertificateException
            {
            }
            public X509Certificate[] getAcceptedIssuers()
            {
                return new java.security.cert.X509Certificate[0];
            }
    
        }
    
    
  • 相关阅读:
    null和undefined的区别
    减少页面加载时间的方法
    html5有哪些新特性、移除了那些元素?
    cookies,sessionStorage 和 localStorage 的区别
    小程序页面
    快速保存网页图片的工具
    Flex 布局教程
    第一阶段:Python开发基础 day08 Python基础语法入门--列表元组字典集合类型的内置方法
    第一阶段:Python开发基础 day08 数据类型的内置方法 课后作业
    python学习第一周知识内容回顾与小结
  • 原文地址:https://www.cnblogs.com/muzhao/p/11114705.html
Copyright © 2011-2022 走看看