Android使用HTTPS進行IP直連握手失敗問題(okHttp)

  爲何要使用ip直連這種方式去請求咱們的服務器呢?這其實和國內運營傷有關,運營商有時爲了利益會將你的域名劫持換成他人的域名,爲了防止這種狀況的發生通用的解決辦法要麼聯繫運營商要麼就只能使用ip直連了。廣泛你們目前使用的都是okHttp,這裏就以okHttp爲例子。其實很是簡單隻須要設置一下兩個方法就行:java

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        ....
        String domain = ....;
        builder.sslSocketFactory(new TlsSniSocketFactory(domain), new SSLUtil.TrustAllManager())
                .hostnameVerifier(new TrueHostnameVerifier(domain));

經過調用sslSocketFactory()方法傳入兩個參數一個是:SSLSocket,還有一個x509TrustManager。咱們來看看第一個參數是如何實現的:後端

public class TlsSniSocketFactory extends SSLSocketFactory {
    private final String TAG = TlsSniSocketFactory.class.getSimpleName();
    HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
    String peerHost;

    public TlsSniSocketFactory(String peerHost) {
        this.peerHost = peerHost;
    }

    public TlsSniSocketFactory() {
    }

    @Override
    public Socket createSocket() {
        return null;
    }

    @Override
    public Socket createSocket(String host, int port) {
        return null;
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) {
        return null;
    }

    @Override
    public Socket createSocket(InetAddress host, int port) {
        return null;
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) {
        return null;
    }

    // TLS layer
    @Override
    public String[] getDefaultCipherSuites() {
        return new String[0];
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return new String[0];
    }

    @Override
    public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
        if (TextUtils.isEmpty(peerHost)) {
            peerHost = host;
            peerHost =  ....;
        }        Log.i(TAG, "customized createSocket. host: " + peerHost);
        InetAddress address = plainSocket.getInetAddress();
        if (autoClose) {
            // we don't need the plainSocket
            plainSocket.close();
        }
        // create and connect SSL socket, but don't do hostname/certificate verification yet
        SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
        SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);
        // enable TLSv1.1/1.2 if available
        ssl.setEnabledProtocols(ssl.getSupportedProtocols());
        // set up SNI before the handshake
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            Log.i(TAG, "Setting SNI hostname");
            sslSocketFactory.setHostname(ssl, peerHost);
        } else {
            Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
            try {
                java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
                setHostnameMethod.invoke(ssl, peerHost);
            } catch (Exception e) {
                Log.w(TAG, "SNI not useable", e);
            }
        }
        // verify hostname and certificate
        SSLSession session = ssl.getSession();
        if (!hostnameVerifier.verify(peerHost, session))
            throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);
        Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
                " using " + session.getCipherSuite());
        return ssl;
    }
}

爲了防止獲取不到domain將外圍的domain塞入,將這個domain塞入返回咱們的ssl。x509TrustManagerx信任了全部的證書,固然正常狀況下應該使用和後端約定好的證書,代碼以下:服務器

public class SSLUtil {
    /**
     * 默認信任全部的證書
     * TODO 最好加上證書認證,主流App都有本身的證書
     *
     * @return
     */
    @SuppressLint("TrulyRandom")
    public static SSLSocketFactory createSSLSocketFactory() {

        SSLSocketFactory sSLSocketFactory = null;

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllManager()},
                    new SecureRandom());
            sSLSocketFactory = sc.getSocketFactory();
        } catch (Exception e) {
        }

        return sSLSocketFactory;
    }

    public static class TrustAllManager implements X509TrustManager {

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)

        {
        }

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

        }

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




    public static class TrustAllHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }
}

最後https須要校驗個人domain是不是服務器提供的domain:session

public class TrueHostnameVerifier implements HostnameVerifier {
    public  String domain;

    public TrueHostnameVerifier(String domain) {
        this.domain = domain;
    }
    public TrueHostnameVerifier() {
    }

    @Override
    public boolean verify(String hostname, SSLSession session) {
        if(TextUtils.isEmpty(domain)) {
            domain = ...;
        }
        return HttpsURLConnection.getDefaultHostnameVerifier().verify(domain, session);
    }
}

以上代碼能夠直接拷貝,省略號代碼具體代碼可能須要你本身去實現。但願對你有所幫助。dom

相關文章
相關標籤/搜索