zoukankan      html  css  js  c++  java
  • Android使用HTTPS进行IP直连握手失败问题(okHttp)

      为什么要使用ip直连这种方式去请求我们的服务器呢?这其实和国内运营伤有关,运营商有时为了利益会将你的域名劫持换成他人的域名,为了防止这种情况的发生通用的解决办法要么联系运营商要么就只能使用ip直连了。普遍大家目前使用的都是okHttp,这里就以okHttp为例子。其实非常简单只需要设置一下两个方法就行:

            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:

    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);
        }
    }

    以上代码可以直接拷贝,省略号代码具体代码可能需要你自己去实现。希望对你有所帮助。

  • 相关阅读:
    random(1)
    django(1)
    python复习
    bootstrap(1)
    jquery(2)
    Jquery(3)
    day17 正则表达式 re模块
    文字笔记
    MATLAB之数学建模:深圳市生活垃圾处理社会总成本分析
    MATLAB之折线图、柱状图、饼图以及常用绘图技巧
  • 原文地址:https://www.cnblogs.com/lovelyYakir/p/10701618.html
Copyright © 2011-2022 走看看