Java:重写函数以禁用SSL证书检查

30

该Web服务在SSL上以REST方式运行,具有自签名证书,托管在远程系统中。我已经创建了一个客户端访问该Web服务。这是通过将证书添加到密钥库程序中完成的。

现在我听说,访问自签名Web服务时不需要将证书添加到密钥库中。相反,我们可以通过重写一些方法来禁用证书检查。这是真的吗?哪些方法?请帮忙。


3
一个 Web 服务不应该使用自签名证书。一定要支付费用并获取由 CA 签署的证书。你听到的信息是正确的,但这种做法也极不可取。它需要在客户端进行特殊编程,这会引入安全问题、开发风险和部署问题。 - user207421
4
该网络服务尚未发布,正在进行中。这就是为什么它现在没有通过可信CA的认证。已知禁用证书检查后使用该服务不理想,尽管我需要检查它。请帮忙,谢谢@EJP。 - Sumithlal
3
任何为临时解决方案所做的努力,都是浪费时间和金钱,并且会引入安全风险,即您可能会“意外”在生产中部署它。现在就花钱吧,只需要几百美元。 - user207421
@EJP,所有的问题都在那里。但是出于学习目的,现在我需要禁用此检查来访问该服务。 - Sumithlal
2
@Sumithlal,如果您真的无法购买证书,为了学习目的,最好使用自己的CA(例如xca或tinyca等工具),并在客户端的信任存储中部署自己的CA证书(不一定要以编程方式实现)。这肯定比更改代码中证书验证方式更现实且更少容易出现安全漏洞。 - Bruno
3个回答

41

这应该足够了。当我在测试和暂存服务器上测试代码时,我使用它,因为我们没有经过适当签署的证书。但是,您应该非常强烈地考虑在生产服务器上获得有效的SSL证书。没有人想被窃听并侵犯隐私。

SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new TrustAllX509TrustManager() }, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){
    public boolean verify(String string,SSLSession ssls) {
        return true;
    }
});

并且这个。

import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

/**
 * DO NOT USE IN PRODUCTION!!!!
 * 
 * This class will simply trust everything that comes along.
 * 
 * @author frank
 *
 */
public class TrustAllX509TrustManager implements X509TrustManager {
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }

    public void checkClientTrusted(java.security.cert.X509Certificate[] certs,
            String authType) {
    }

    public void checkServerTrusted(java.security.cert.X509Certificate[] certs,
            String authType) {
    }

}

祝你好运!

===更新===

我想提醒大家,有一个称为Let's Encrypt的服务可以自动化生成和设置被几乎所有人认可的SSL/TLS证书,而且完全免费!


感谢您的回复。只有在jssecert或cacert中添加了证书时,此方法才有效。如果证书被删除并运行,则会出现SSLHandshakeException错误。同时还会警告“无法发送消息”。 - Sumithlal
不应该出现这种情况。当使用带有自签名证书的密钥库时,您可以使用javax.ssl.something=keystore.jks来设置密钥库。但是在使用此方法时不应设置此项。我会尽快重新测试它。 - Paaske
2
getAcceptedIssuers() 的返回值不应为 null。请参阅 Javadoc。 - user207421
1
还有其他人想知道这段代码在生产环境中被复制粘贴的频率吗? - David J. Liszewski
2
我降低了互联网的安全和隐私标准吗? - Paaske
显示剩余2条评论

13
忽略每个连接的证书更加安全,因为任何其他代码仍将使用安全默认值。
以下代码:
- 每个连接基础上覆盖信任管理器和主机名验证器。 - 重用SSLSocketFactory以支持持久连接,绕过重复请求同一服务器的昂贵SSL握手。
正如其他人所述,这只应用于测试和/或内部系统与其他内部系统通信。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class TestPersistentConnection
{
    private static SSLSocketFactory sslSocketFactory = null;

    /**
     * Use the VM argument <code>-Djavax.net.debug=ssl</code> for SSL specific debugging;
     * the SSL handshake will appear a single time when connections are re-used, and multiple
     * times when they are not.
     * 
     * Use the VM <code>-Djavax.net.debug=all</code> for all network related debugging, but 
     * note that it is verbose.
     * 
     * @throws Exception
     */
    public static void main(String[] args) throws Exception
    {

        //URL url = new URL("https://google.com/");
        URL url = new URL("https://localhost:8443/");

        // Disable first
        request(url, false);

        // Enable; verifies our previous disable isn't still in effect.
        request(url, true);
    }

    public static void request(URL url, boolean enableCertCheck) throws Exception {
        BufferedReader reader = null;
        // Repeat several times to check persistence.
        System.out.println("Cert checking=["+(enableCertCheck?"enabled":"disabled")+"]");
        for (int i = 0; i < 5; ++i) {
            try {

                HttpURLConnection httpConnection = (HttpsURLConnection) url.openConnection();

                // Normally, instanceof would also be used to check the type.
                if( ! enableCertCheck ) {
                    setAcceptAllVerifier((HttpsURLConnection)httpConnection);
                }

                reader = new BufferedReader(new InputStreamReader(httpConnection.getInputStream()), 1);

                char[] buf = new char[1024];
                StringBuilder sb = new StringBuilder();
                int count = 0;
                while( -1 < (count = reader.read(buf)) ) {
                    sb.append(buf, 0, count);
                }
                System.out.println(sb.toString());

                reader.close();

            } catch (IOException ex) {
                System.out.println(ex);

                if( null != reader ) {
                    reader.close();
                }
            }
        }
    }

    /**
     * Overrides the SSL TrustManager and HostnameVerifier to allow
     * all certs and hostnames.
     * WARNING: This should only be used for testing, or in a "safe" (i.e. firewalled)
     * environment.
     * 
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    protected static void setAcceptAllVerifier(HttpsURLConnection connection) throws NoSuchAlgorithmException, KeyManagementException {

        // Create the socket factory.
        // Reusing the same socket factory allows sockets to be
        // reused, supporting persistent connections.
        if( null == sslSocketFactory) {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, ALL_TRUSTING_TRUST_MANAGER, new java.security.SecureRandom());
            sslSocketFactory = sc.getSocketFactory();
        }

        connection.setSSLSocketFactory(sslSocketFactory);

        // Since we may be using a cert with a different name, we need to ignore
        // the hostname as well.
        connection.setHostnameVerifier(ALL_TRUSTING_HOSTNAME_VERIFIER);
    }

    private static final TrustManager[] ALL_TRUSTING_TRUST_MANAGER = new TrustManager[] {
        new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {}
            public void checkServerTrusted(X509Certificate[] certs, String authType) {}
        }
    };

    private static final HostnameVerifier ALL_TRUSTING_HOSTNAME_VERIFIER  = new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };

}

感谢以下网站提供的帮助: http://runtime32.blogspot.com/2008/11/let-java-ssl-trust-all-certificates.html


2
你确定它是基于每个连接的工作方式吗?使用 SSLContext.getInstance 我从不知道它是获取器还是什么... - maaartinus
相当确定。示例程序中唯一替换的情况是当enableCertCheck为false时(通过setAcceptAllVerifier(...))。如果系统范围的套接字工厂和主机名验证器被替换,那么在main中对request(url, true)的第二次调用将在标准输出上打印异常。否则,它被更改的唯一方式是连接本身临时更改,完成后会恢复它,这似乎不太可能。 - Josh Hansen
这个方法可行!我之前使用了 setDefaultSSLSocketFactory 方法来禁用证书检查,但是这导致我连接到第二个需要进行证书检查的网站时失败了。现在我只需稍作修改就可以同时连接到两个 https 网站了。 - jpp1jpp1
是的,这正是我需要的。我只需要禁用一个 HTTPS 验证来进行 keep-alive https 检查。但是我仍然需要它来处理 JVM 的其余部分。每篇其他文章或帖子似乎都已经静态地关闭了 所有 SSL 验证。 - Nicholas DiPiazza

0
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    }
                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    }
                }
            };
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            HostnameVerifier allHostsValid = new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            };
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
            GetCustomerPhone http = new GetCustomerPhone(); 

    System.out.println("Processing..");     
     try{
            http.sendPost();    
        }
    catch(Exception e){
            e.printStackTrace();
        }               
}

我认为它会正常工作。因为对我来说很好...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接