1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
| class WebviewTlsSniSocketFactory extends SSLSocketFactory { private final String TAG = WebviewTlsSniSocketFactory.class.getSimpleName(); HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); private HttpsURLConnection conn;
public WebviewTlsSniSocketFactory(HttpsURLConnection conn) { this.conn = conn; }
@Override public Socket createSocket() throws IOException { return null; }
@Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return null; }
@Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return null; }
@Override public Socket createSocket(InetAddress host, int port) throws IOException { return null; }
@Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { 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 { String peerHost = this.conn.getRequestProperty("Host"); if (peerHost == null) peerHost = host; 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; } } webView.setWebViewClient(new WebViewClient() { @SuppressLint("NewApi") @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { String scheme = request.getUrl().getScheme().trim(); String method = request.getMethod(); Map<String, String> headerFields = request.getRequestHeaders(); String url = request.getUrl().toString(); Log.e(TAG, "url:" + url); // 无法拦截body,拦截方案只能正常处理不带body的请求; if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) && method.equalsIgnoreCase("get")) { try { URLConnection connection = recursiveRequest(url, headerFields, null);
if (connection == null) { Log.e(TAG, "connection null"); return super.shouldInterceptRequest(view, request); }
// 注*:对于POST请求的Body数据,WebResourceRequest接口中并没有提供,这里无法处理 String contentType = connection.getContentType(); String mime = getMime(contentType); String charset = getCharset(contentType); HttpURLConnection httpURLConnection = (HttpURLConnection)connection; int statusCode = httpURLConnection.getResponseCode(); String response = httpURLConnection.getResponseMessage(); Map<String, List<String>> headers = httpURLConnection.getHeaderFields(); Set<String> headerKeySet = headers.keySet(); Log.e(TAG, "code:" + httpURLConnection.getResponseCode()); Log.e(TAG, "mime:" + mime + "; charset:" + charset);
// 无mime类型的请求不拦截 if (TextUtils.isEmpty(mime)) { Log.e(TAG, "no MIME"); return super.shouldInterceptRequest(view, request); } else { // 二进制资源无需编码信息 if (!TextUtils.isEmpty(charset) || (isBinaryRes(mime))) { WebResourceResponse resourceResponse = new WebResourceResponse(mime, charset, httpURLConnection.getInputStream()); resourceResponse.setStatusCodeAndReasonPhrase(statusCode, response); Map<String, String> responseHeader = new HashMap<String, String>(); for (String key: headerKeySet) { // HttpUrlConnection可能包含key为null的报头,指向该http请求状态码 responseHeader.put(key, httpURLConnection.getHeaderField(key)); } resourceResponse.setResponseHeaders(responseHeader); return resourceResponse; } else { Log.e(TAG, "non binary resource for " + mime); return super.shouldInterceptRequest(view, request); } } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return super.shouldInterceptRequest(view, request); }
@Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { // API < 21 只能拦截URL参数 return super.shouldInterceptRequest(view, url); } }); webView.loadUrl(targetUrl); }
public URLConnection recursiveRequest(String path, Map<String, String> headers, String reffer) { HttpURLConnection conn; URL url = null; try { url = new URL(path); conn = (HttpURLConnection) url.openConnection(); // 异步接口获取IP String ip = httpdns.getIpByHostAsync(url.getHost()); if (ip != null) { // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置 Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!"); String newUrl = path.replaceFirst(url.getHost(), ip); conn = (HttpURLConnection) new URL(newUrl).openConnection();
if (headers != null) { for (Map.Entry<String, String> field : headers.entrySet()) { conn.setRequestProperty(field.getKey(), field.getValue()); } } // 设置HTTP请求头Host域 conn.setRequestProperty("Host", url.getHost()); } else { return null; } conn.setConnectTimeout(30000); conn.setReadTimeout(30000); conn.setInstanceFollowRedirects(false); if (conn instanceof HttpsURLConnection) { final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)conn; WebviewTlsSniSocketFactory sslSocketFactory = new WebviewTlsSniSocketFactory((HttpsURLConnection) conn);
// sni场景,创建SSLScocket httpsURLConnection.setSSLSocketFactory(sslSocketFactory); // https场景,证书校验 httpsURLConnection.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { String host = httpsURLConnection.getRequestProperty("Host"); if (null == host) { host = httpsURLConnection.getURL().getHost(); } return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session); } }); } int code = conn.getResponseCode();// Network block if (needRedirect(code)) { // 原有报头中含有cookie,放弃拦截 if (containCookie(headers)) { return null; }
String location = conn.getHeaderField("Location"); if (location == null) { location = conn.getHeaderField("location"); }
if (location != null) { if (!(location.startsWith("http://") || location .startsWith("https://"))) { //某些时候会省略host,只返回后面的path,所以需要补全url URL originalUrl = new URL(path); location = originalUrl.getProtocol() + "://" + originalUrl.getHost() + location; } Log.e(TAG, "code:" + code + "; location:" + location + "; path" + path); return recursiveRequest(location, headers, path); } else { // 无法获取location信息,让浏览器获取 return null; } } else { // redirect finish. Log.e(TAG, "redirect finish"); return conn; } } catch (MalformedURLException e) { Log.w(TAG, "recursiveRequest MalformedURLException"); } catch (IOException e) { Log.w(TAG, "recursiveRequest IOException"); } catch (Exception e) { Log.w(TAG, "unknow exception"); } return null; }
|