為什么要使用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); } }
以上代碼可以直接拷貝,省略號代碼具體代碼可能需要你自己去實現。希望對你有所幫助。