之前webview都是显示http网址的,或者是显示出名的https网址,比如百度
因为安全性问题,需要用上https防止别人抓取到请求链接,省钱就自签名生成cer和p12后缀的证书,这两个证书都是服务器端生成的,必须的(单向认证可以只有cer证书)。
解决思路:webview中加入证书校验,如果是双向认证的,类似百度出来的忽略证书是行不通的。
下面开始:重写WebViewClient
public class SslPinningWebViewClient extends WebViewClient {
private SSLContext sslContext;
public SslPinningWebViewClient()throws IOException {
// 添加证书cer
List certificates = new ArrayList<>();
List certs_data = NetConfig.getCertificatesData();
// 将字节数组转为数组输入流
if (certs_data != null && !certs_data.isEmpty()) {
for (byte[] bytes : certs_data) {
certificates.add(new ByteArrayInputStream(bytes));
}
}
prepareSslPinning(certificates);
}
@Override
public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {
LogUtils.i("shouldInterceptRequest","shouldInterceptRequest1");
return processRequest(url);
}
private WebResourceResponse processRequest(String webUrl) {
LogUtils.i("SSL_PINNING_WEBVIEWS", "GET: " + webUrl.toString());
try {
// Setup connection
URL url = new URL(webUrl);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
// Set SSL Socket Factory for this request
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
urlConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});//很重要,校验证书
// Get content, contentType and encoding
InputStream is = urlConnection.getInputStream();
String contentType = urlConnection.getContentType();
String encoding = urlConnection.getContentEncoding();
// If got a contentType header
if(contentType != null) {
String mimeType = contentType;
// Parse mime type from contenttype string
if (contentType.contains(";")) {
mimeType = contentType.split(";")[0].trim();
}
LogUtils.i("SSL_PINNING_WEBVIEWS", "Mime: " + mimeType);
// Return the response
return new WebResourceResponse(mimeType, encoding, is);
}
} catch (Exception e) {
e.printStackTrace();
LogUtils.i("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());
}
// Return empty response for this request
return new WebResourceResponse(null, null, null);
}
private void prepareSslPinning(List certificates)throws IOException {
try {
// 服务器端需要验证的客户端证书,其实就是客户端的keystore
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance("X509");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
KeyStore keyStore2 = KeyStore.getInstance(KeyStore.getDefaultType());
//读取证书
InputStream ksIn = getResources().getAssets().open("***.p12");//***:你的p12证书
//加载证书
keyStore2.load(null);
keyStore.load(ksIn, "****".toCharArray());//***:p12证书的密码,必须
ksIn.close();
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
try {
for (int i = 0, size = certificates.size(); i < size; ) {
InputStream certificate = certificates.get(i);
String certificateAlias = Integer.toString(i++);
keyStore2.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
if (certificate != null)
certificate.close();
}
} catch (IOException e) {
e.printStackTrace();
}
sslContext = SSLContext.getInstance("TLS");
// TrustManagerFactory trustManagerFactory =
//
// TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "yibanyiban".toCharArray());
trustManagerFactory.init(keyStore2);
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
}catch (Exception e) {
e.printStackTrace();
}
}
}
上面NetConfig读取cer证书
public class NetConfig {
// 证书数据
private static List CERTIFICATES_DATA = new ArrayList<>();
/**
* 添加https证书
* @param inputStream
*/
public synchronized static void addCertificate(InputStream inputStream) {
if (inputStream != null) {
try {
int ava = 0;// 数据当次可读长度
int len = 0;// 数据总长度
ArrayList data = new ArrayList<>();
while ((ava = inputStream.available()) > 0) {
byte[] buffer = new byte[ava];
inputStream.read(buffer);
data.add(buffer);
len += ava;
}
byte[] buff = new byte[len];
int dstPos = 0;
for (byte[] bytes:data) {
int length = bytes.length;
System.arraycopy(bytes, 0, buff, dstPos, length);
dstPos += length;
}
CERTIFICATES_DATA.add(buff);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* https证书
* @return
*/
public static List getCertificatesData() {
return CERTIFICATES_DATA;
}
}
读取cer证书,读取的位置看你项目的需求
/**
* 读取cer证书
*/
private void readCer(){
// 添加https证书
try {
InputStream is = getAssets().open("tomcat.cer");
NetConfig.addCertificate(is); // 这里将证书读取出来,,放在配置中byte[]里
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
以上就完成webview加载https网址了
花絮,花絮
如果你项目只需支持5.0系统版本以上的,webview有方法直接导入证书的,已测试过
wvMain.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
//super.onReceivedSslError(view, handler, error);
LogUtils.i("mPrivateKey","SslError");
handler.proceed();//接受证书
}
//加载https网页
@Override
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
//super.onReceivedClientCertRequest(view, request);
LogUtils.i("mPrivateKey","mPrivateKey");
if ((null != HttpUtils.mPrivateKey)
&& ((null != HttpUtils.mX509Certificates) && (HttpUtils.mX509Certificates.length != 0))) {
request.proceed(HttpUtils.mPrivateKey, HttpUtils.mX509Certificates);//这里的key,cer在你双向认证处理中都可以找到
} else {
request.cancel();
}
}
});
有问题可加QQ群:142739277