最近发布了一个关于HttpClient over Https的问题(在这里找到)。我取得了一些进展,但我遇到了新的问题。就像我的最后一个问题一样,我似乎找不到一个适合我的例子。基本上,我希望我的客户端接受任何证书(因为我只指向一个服务器),但我总是得到一个javax.net.ssl.SSLException:不受信任的服务器证书异常。

这就是我得到的:


    public void connect() throws A_WHOLE_BUNCH_OF_EXCEPTIONS {

        HttpPost post = new HttpPost(new URI(PROD_URL));
        post.setEntity(new StringEntity(BODY));

        KeyStore trusted = KeyStore.getInstance("BKS");
        trusted.load(null, "".toCharArray());
        SSLSocketFactory sslf = new SSLSocketFactory(trusted);
        sslf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme ("https", sslf, 443));
        SingleClientConnManager cm = new SingleClientConnManager(post.getParams(),
                schemeRegistry);

        HttpClient client = new DefaultHttpClient(cm, post.getParams());
        HttpResponse result = client.execute(post);
    }

这是我得到的错误:

    W/System.err(  901): javax.net.ssl.SSLException: Not trusted server certificate 
    W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:360) 
    W/System.err(  901):    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:92) 
    W/System.err(  901):    at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:321) 
    W/System.err(  901):    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:129) 
    W/System.err(  901):    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) 
    W/System.err(  901):    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) 
    W/System.err(  901):    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348) 
    W/System.err(  901):    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555) 
    W/System.err(  901):    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487) 
    W/System.err(  901):    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465) 
    W/System.err(  901):    at me.harrisonlee.test.ssl.MainActivity.connect(MainActivity.java:129) 
    W/System.err(  901):    at me.harrisonlee.test.ssl.MainActivity.access$0(MainActivity.java:77) 
    W/System.err(  901):    at me.harrisonlee.test.ssl.MainActivity$2.run(MainActivity.java:49) 
    W/System.err(  901): Caused by: java.security.cert.CertificateException: java.security.InvalidAlgorithmParameterException: the trust anchors set is empty 
    W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:157) 
    W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:355) 
    W/System.err(  901):    ... 12 more 
    W/System.err(  901): Caused by: java.security.InvalidAlgorithmParameterException: the trust anchors set is empty 
    W/System.err(  901):    at java.security.cert.PKIXParameters.checkTrustAnchors(PKIXParameters.java:645) 
    W/System.err(  901):    at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:89) 
    W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.<init>(TrustManagerImpl.java:89) 
    W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.TrustManagerFactoryImpl.engineGetTrustManagers(TrustManagerFactoryImpl.java:134) 
    W/System.err(  901):    at javax.net.ssl.TrustManagerFactory.getTrustManagers(TrustManagerFactory.java:226)W/System.err(  901):     at org.apache.http.conn.ssl.SSLSocketFactory.createTrustManagers(SSLSocketFactory.java:263) 
    W/System.err(  901):    at org.apache.http.conn.ssl.SSLSocketFactory.<init>(SSLSocketFactory.java:190) 
    W/System.err(  901):    at org.apache.http.conn.ssl.SSLSocketFactory.<init>(SSLSocketFactory.java:216) 
    W/System.err(  901):    at me.harrisonlee.test.ssl.MainActivity.connect(MainActivity.java:107) 
    W/System.err(  901):    ... 2 more

当前回答

我正在为那些使用httpclient-4.5的用户添加一个响应,它可能也适用于4.4。

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.fluent.ContentResponseHandler;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;



public class HttpClientUtils{

public static HttpClient getHttpClientWithoutSslValidation_UsingHttpClient_4_5_2() {
    try {
        SSLContextBuilder builder = new SSLContextBuilder();
        builder.loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        });
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(), new NoopHostnameVerifier());
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); 
        return httpclient;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
}

其他回答

这是个坏主意。信任任何证书仅仅(非常)比不使用SSL好一点点。当你说“我想让我的客户端接受任何证书(因为我只指向一个服务器)”时,你假设这意味着以某种方式指向“一个服务器”是安全的,它不是在公共网络上。

通过信任任何证书,您完全有可能受到中间人攻击。任何人都可以通过与您和终端服务器建立单独的SSL连接来代理您的连接。然后,MITM可以访问您的整个请求和响应。除非您一开始就不需要SSL(您的消息没有任何敏感内容,并且不进行身份验证),否则您不应该盲目地信任所有证书。

你应该考虑使用keytool将公共证书添加到jks中,并使用它来构建你的套接字工厂,例如:

    KeyStore ks = KeyStore.getInstance("JKS");

    // get user password and file input stream
    char[] password = ("mykspassword")).toCharArray();
    ClassLoader cl = this.getClass().getClassLoader();
    InputStream stream = cl.getResourceAsStream("myjks.jks");
    ks.load(stream, password);
    stream.close();

    SSLContext sc = SSLContext.getInstance("TLS");
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

    kmf.init(ks, password);
    tmf.init(ks);

    sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(),null);

    return sc.getSocketFactory();

这需要注意一个警告。证书最终会过期,代码届时将停止工作。通过查看证书,您可以很容易地确定何时会发生这种情况。

丹尼尔的回答很好,除了我不得不改变这个代码…

    SchemeRegistry registry = new SchemeRegistry();
    registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    registry.register(new Scheme("https", sf, 443));

    ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

对于这个代码…

    ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
    SchemeRegistry registry = ccm.getShemeRegistry()
    registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    registry.register(new Scheme("https", sf, 443));

让它工作。

对于那些希望允许所有证书在OAuth上工作(用于测试目的)的人,请遵循以下步骤:

1)下载Android OAuth API源代码:https://github.com/kaeppler/signpost

2)找到CommonsHttpOAuthProvider类

3)修改如下:

public class CommonsHttpOAuthProvider extends AbstractOAuthProvider {

private static final long serialVersionUID = 1L;

private transient HttpClient httpClient;

public CommonsHttpOAuthProvider(String requestTokenEndpointUrl, String accessTokenEndpointUrl,
        String authorizationWebsiteUrl) {
    super(requestTokenEndpointUrl, accessTokenEndpointUrl, authorizationWebsiteUrl);


    //this.httpClient = new DefaultHttpClient();//Version implemented and that throws the famous "javax.net.ssl.SSLException: Not trusted server certificate" if the certificate is not signed with a CA
    this.httpClient = MySSLSocketFactory.getNewHttpClient();//This will work with all certificates (for testing purposes only)
}

上面的“MySSLSocketFactory”是基于已接受的答案。为了让它更简单,下面是完整的类:

package com.netcomps.oauth_example;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;

//http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https
public class MySSLSocketFactory extends SSLSocketFactory {

    SSLContext sslContext = SSLContext.getInstance("TLS");

public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {

    super(truststore);
    TrustManager tm = new X509TrustManager() {

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    };

    sslContext.init(null, new TrustManager[] { tm }, null);
}

@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
    return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}

@Override
public Socket createSocket() throws IOException {
    return sslContext.getSocketFactory().createSocket();
}



public static HttpClient getNewHttpClient() {

    try {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);

        SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
        sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        registry.register(new Scheme("https", sf, 443));

        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

        return new DefaultHttpClient(ccm, params);

    } catch (Exception e) {
        return new DefaultHttpClient();
    }
}

}

希望这能帮助到一些人。

自API 8以来,您可以通过这种方式禁用HttpURLConnection SSL检查:

    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    if (conn instanceof HttpsURLConnection) {
        HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
        httpsConn.setSSLSocketFactory(SSLCertificateSocketFactory.getInsecure(0, null));
        httpsConn.setHostnameVerifier(new AllowAllHostnameVerifier());
    }

信任所有证书对我来说并不是真正的替代方法,所以我做了以下工作来让HttpsURLConnection信任一个新的证书(也请参阅http://nelenkov.blogspot.jp/2011/12/using-custom-certificate-trust-store-on.html)。

Get the certificate; I got this done by exporting the certificate in Firefox (click on the little lock icon, get certificate details, click export), then used portecle to export a truststore (BKS). Load the Truststore from /res/raw/geotrust_cert.bks with the following code: final KeyStore trustStore = KeyStore.getInstance("BKS"); final InputStream in = context.getResources().openRawResource( R.raw.geotrust_cert); trustStore.load(in, null); final TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); final SSLContext sslCtx = SSLContext.getInstance("TLS"); sslCtx.init(null, tmf.getTrustManagers(), new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslCtx .getSocketFactory());