Android 让WebView完美支持https双向认证(SSL)

来源:互联网 发布:lazada平台知乎 编辑:程序博客网 时间:2024/05/22 02:01

 这是@happyzhang0502   关于webview https的建议:

 最近做一个安全级别比较高的项目,对方要求使用HTTPS双向认证来访问web网页。双向认证在android5.0以上很好解决,但是在Android5.0以下,webviewclient中没有客户端向服务器发送证书的回调接口(回调是个隐藏函数)。

网上搜索到大概有这么几种解决方法:

1.      利用反射调用隐藏函数(不太现实,这个方法为回调方法)

2.      自己编译完整的class.jar(试过了,没成功,成本很大)

3.      重写webview(不可能,工作量巨大)

经过上面的几种想法后来在网上高人的指点下有了第四种方法。

解决方法:拦截Webview的Request的请求,然后自己实现httpconnection捞取数据,然后返回新的WebResourceResponse给Webview。重写webviewclient中的shouldInterceptRequest方法即可。


下面我来给大家具体实现方法:

step1:生成两个证书

server.cer  服务器端证书
client.p12   客户端证书 (需要记住密码加入之后的java文件中)

将这两个证书放在 android assets 文件下。


step2:重写WebViewClient引入两个证书

SslPinningWebViewClient.java


package com.cloudhome.webview_https;import android.annotation.TargetApi;import android.content.Context;import android.net.Uri;import android.util.Base64;import android.util.Log;import android.webkit.WebResourceRequest;import android.webkit.WebResourceResponse;import android.webkit.WebView;import android.webkit.WebViewClient;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.net.URL;import java.security.KeyManagementException;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import java.security.UnrecoverableKeyException;import java.security.cert.CertPathValidatorException;import java.security.cert.CertificateException;import java.security.cert.CertificateFactory;import java.security.cert.X509Certificate;import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.KeyManager;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLHandshakeException;import javax.net.ssl.TrustManager;import javax.net.ssl.TrustManagerFactory;import javax.net.ssl.X509TrustManager;/** * Created by mennomorsink on 06/05/15. */public class SslPinningWebViewClient extends WebViewClient {    private LoadedListener listener;    private SSLContext sslContext;    public SslPinningWebViewClient(LoadedListener listener)throws IOException  {        this.listener = listener;        prepareSslPinning(MyApplication.mContext.getResources().getAssets().open("server.cer"));    }    @Override    public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {        if(MainActivity.pinningSwitch.isChecked()) {            return processRequest(Uri.parse(url));        } else {            return null;        }    }    @Override    @TargetApi(21)    public WebResourceResponse shouldInterceptRequest (final WebView view, WebResourceRequest interceptedRequest) {        if(MainActivity.pinningSwitch.isChecked()) {            return processRequest(interceptedRequest.getUrl());        } else {            return null;        }    }    private WebResourceResponse processRequest(Uri uri) {        Log.d("SSL_PINNING_WEBVIEWS", "GET: " + uri.toString());        try {            // Setup connection            URL url = new URL(uri.toString());            HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();            // Set SSL Socket Factory for this request            urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());            // 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();                }                Log.d("SSL_PINNING_WEBVIEWS", "Mime: " + mimeType);                listener.Loaded(uri.toString());                // Return the response                return new WebResourceResponse(mimeType, encoding, is);            }        } catch (SSLHandshakeException e) {            if(isCause(CertPathValidatorException.class, e)) {                listener.PinningPreventedLoading(uri.getHost());            }            Log.d("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());        } catch (Exception e) {            Log.d("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());        }        // Return empty response for this request        return new WebResourceResponse(null, null, null);    }    private void prepareSslPinning(InputStream... certificates)throws IOException {        try {            InputStream inputStream = MyApplication.mContext.getResources().getAssets().open("client.p12");            TrustManager[] trustManagers = prepareTrustManager(certificates);            KeyManager[] keyManagers = prepareKeyManager(inputStream, "your client.p12 password");            sslContext = SSLContext.getInstance("TLS");            sslContext.init(keyManagers, new TrustManager[]{new MyTrustManager(chooseTrustManager(trustManagers))}, new SecureRandom());        } catch (NoSuchAlgorithmException e) {            e.printStackTrace();        } catch (KeyStoreException e) {            e.printStackTrace();        } catch (KeyManagementException e) {            e.printStackTrace();        }    }    private static TrustManager[] prepareTrustManager(InputStream... certificates)    {        if (certificates == null || certificates.length <= 0) return null;        try        {            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());            keyStore.load(null);            int index = 0;            for (InputStream certificate : certificates)            {                String certificateAlias = Integer.toString(index++);                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));                try                {                    if (certificate != null)                        certificate.close();                } catch (IOException e)                {                }            }            TrustManagerFactory trustManagerFactory = null;            trustManagerFactory = TrustManagerFactory.                    getInstance(TrustManagerFactory.getDefaultAlgorithm());            trustManagerFactory.init(keyStore);            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();            return trustManagers;        } catch (NoSuchAlgorithmException e)        {            e.printStackTrace();        } catch (CertificateException e)        {            e.printStackTrace();        } catch (KeyStoreException e)        {            e.printStackTrace();        } catch (Exception e)        {            e.printStackTrace();        }        return null;    }    private static KeyManager[] prepareKeyManager(InputStream bksFile, String password)    {        try        {            if (bksFile == null || password == null) return null;            KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");            clientKeyStore.load(bksFile, password.toCharArray());            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());            keyManagerFactory.init(clientKeyStore, password.toCharArray());            return keyManagerFactory.getKeyManagers();        } catch (KeyStoreException e)        {            e.printStackTrace();        } catch (NoSuchAlgorithmException e)        {            e.printStackTrace();        } catch (UnrecoverableKeyException e)        {            e.printStackTrace();        } catch (CertificateException e)        {            e.printStackTrace();        } catch (IOException e)        {            e.printStackTrace();        } catch (Exception e)        {            e.printStackTrace();        }        return null;    }    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers)    {        for (TrustManager trustManager : trustManagers)        {            if (trustManager instanceof X509TrustManager)            {                return (X509TrustManager) trustManager;            }        }        return null;    }    private static class MyTrustManager implements X509TrustManager    {        private X509TrustManager defaultTrustManager;        private X509TrustManager localTrustManager;        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException        {            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());            var4.init((KeyStore) null);            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());            this.localTrustManager = localTrustManager;        }        @Override        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException        {        }        @Override        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException        {            try            {                defaultTrustManager.checkServerTrusted(chain, authType);            } catch (CertificateException ce)            {                localTrustManager.checkServerTrusted(chain, authType);            }        }        @Override        public X509Certificate[] getAcceptedIssuers()        {            return new X509Certificate[0];        }    }    public static boolean isCause(            Class<? extends Throwable> expected,            Throwable exc    ) {        return expected.isInstance(exc) || (                exc != null && isCause(expected, exc.getCause())        );    }}


step3:重写的WebViewClient :SslPinningWebViewClient加入到webview中


package com.cloudhome.webview_https;import android.content.Context;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.webkit.WebView;import android.widget.Button;import android.widget.Switch;import android.widget.TextView;import java.io.IOException;public class MainActivity extends AppCompatActivity {    private WebView webView;    public static Switch pinningSwitch;    private Button btnA;    private Button btnB;    public static TextView textView;    private String url1 = "your https url";    private String url2 = "your https url";    public MainActivity() {    }    public static Context mContext;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        webView = (WebView)findViewById(R.id.webView);        webView.getSettings().setJavaScriptEnabled(true);        pinningSwitch = (Switch)findViewById(R.id.pinningSwitch);        btnA = (Button)findViewById(R.id.btn1);        btnB = (Button)findViewById(R.id.btn2);        textView = (TextView)findViewById(R.id.textView);        mContext=this;        SslPinningWebViewClient webViewClient = null;        try {            webViewClient = new SslPinningWebViewClient(new LoadedListener() {                @Override                public void Loaded(final String url) {                    runOnUiThread(new Runnable() {                        @Override                        public void run() {                            textView.setText("Loaded " + url);                        }                    });                }                @Override                public void PinningPreventedLoading(final String host) {                    runOnUiThread(new Runnable() {                        @Override                        public void run() {                            textView.setText("SSL Pinning prevented loading from " + host);                        }                    });                }            });        } catch (IOException e) {            e.printStackTrace();        }        webView.setWebViewClient(webViewClient);        btnA.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 webView.clearView();                 textView.setText("");                 webView.loadUrl(url1);             }         });        btnB.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                webView.clearView();                textView.setText("");                webView.loadUrl(url2);            }        });    }}


step4:下载回调监听借口LoadedListener借口(根据实际情况使用)


package com.cloudhome.webview_https;/** * Created by mennomorsink on 25/07/15. */public interface LoadedListener {    void Loaded(String url);    void PinningPreventedLoading(String host);}


运行效果:

开始前



运行中加载图片(链接中有其它https请求)


最后加载完成




参考资料:

http://blog.csdn.net/lmj623565791/article/details/48129405

http://blog.csdn.net/zhangyong125/article/details/50562865







2 0
原创粉丝点击