android webView https双向认证

webView https双向认证里面涉及的点比较多,踩了不少坑,下面介绍下基本流程和解决问题的办法。

1. 获取SSLContext,这里使用的是ca.cer和client.bks,见如下getSSLContext方法。

public class SslOkHttpClientUtils {
    public static final String TAG = "SslOkHttpClientUtils";

    public static final String KEY_STORE_TYPE_P12 = "PKCS12";//证书类型

    private static OkHttpClient client;
    private static SSLContext sslContext;
    public static String sessionid;
    public static String cookieval;

    public static OkHttpClient getSslClient(Context context) {
        try {
            if(client == null) {
                InputStream trustKey = context.getAssets().open("ca.cer");
                InputStream clientKeyP12 = context.getAssets().open("client.p12");
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                sslContext = SSLContext.getInstance("TLS");
                Log.d(TAG, "getSslClient: 0");
                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
                Log.d(TAG, "getSslClient: 0.1");
                trustStore.load(null);
//                trustStore.load(trustKey, trustPassword.toCharArray());
                Log.d(TAG, "getSslClient: 1");
                trustStore.setCertificateEntry("0", certificateFactory.generateCertificate(trustKey));
                if (trustKey != null) {
                    trustKey.close();
                }
                KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
                keyStore.load(clientKeyP12, "123456".toCharArray());
              /*  KeyStore keyStore = KeyStore.getInstance("BKS");
                keyStore.load(clientKeyP12, clientPassword.toCharArray());*/
                    Log.d(TAG, "getSslClient: 2");
                    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                    trustManagerFactory.init(trustStore);
                    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
                    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                        throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
                    }
                    X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
                    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                    keyManagerFactory.init(keyStore, "123456".toCharArray());
                    sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
                    client =  new OkHttpClient().newBuilder()
                            .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
                            .followRedirects(false)
                            .followSslRedirects(false)
                            .hostnameVerifier(new HostnameVerifier() {
                                @Override
                                public boolean verify(String hostname, SSLSession session) {
                                    Log.d(TAG, "verify: "+hostname);
                                    if(hostname.equals("www.***.cn")) //指定域名
                                        return true;
                                    return false;
                                }
                            })
                            .build();
                return client;
            }
            return client;
        } catch (Exception e) {
            e.printStackTrace();
            android.util.Log.d(TAG, "exception222:"+e.toString());
            return null;
        }
    }

    public static SSLContext getSSLContext(Context context){
        try {
            if(sslContext == null) {
                InputStream trustKey = context.getAssets().open("ca.cer");
                InputStream clientKeyP12 = context.getAssets().open("client.bks");
                sslContext = SSLContext.getInstance("TLS");
                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
                KeyStore keyStore = KeyStore.getInstance("BKS");
                keyStore.load(clientKeyP12, "123456".toCharArray());
                clientKeyP12.close();
                trustStore.load(null);
                Log.d(TAG, "getSSLContext: 1");
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                trustStore.setCertificateEntry("0", certificateFactory.generateCertificate(trustKey));
                if (trustKey != null) {
                    trustKey.close();
                }
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
                trustManagerFactory.init(trustStore);
                keyManagerFactory.init(keyStore, "123456".toCharArray());
                sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
                Log.d(TAG, "getSSLContext: success");
            }
            return sslContext;
        } catch (Exception e) {
            e.printStackTrace();
            android.util.Log.d(TAG, "exception222:"+e.toString());
            return null;
        }

    }

}

2.webview setting设置

 mWebView.setWebChromeClient(mWebChromeClient);
        mWebView.getSettings().setUseWideViewPort(true);
        mWebView.getSettings().setLoadWithOverviewMode(true);
        mWebView.getSettings().setSupportZoom(true);// 支持缩放
        mWebView.getSettings().setBuiltInZoomControls(true);// 显示放大缩小
        mWebView.getSettings().setDisplayZoomControls(false);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        mWebView.getSettings().setDomStorageEnabled(true);
        mWebView.getSettings().setAppCacheMaxSize(1024 * 1024 * 20);
        String appCachePath = getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
        mWebView.getSettings().setAppCachePath(appCachePath);
        mWebView.getSettings().setDatabasePath(appCachePath);
        mWebView.getSettings().setAppCacheEnabled(true);
        mWebView.getSettings().setAllowFileAccess(true);
        mWebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
        mWebView.addJavascriptInterface(new WebContainer(), "WebContainer");
        mWebView.setWebViewClient(new MyWebViewClient());
        mWebView.getSettings().setSupportMultipleWindows(false);
        mWebView.getSettings().setDatabaseEnabled(true);
        mWebView.getSettings().setDefaultTextEncodingName("UTF-8");
        mWebView.getSettings().setTextZoom(100);
        mWebView.getSettings().setLayoutAlgorithm ( WebSettings.LayoutAlgorithm.SINGLE_COLUMN );
      //取消滚动条
        mWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
        //触摸焦点起作用
        mWebView.requestFocus();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mWebView.getSettings().setMixedContentMode(
                    WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        } 

3.https相关的代码主要在new MyWebViewClient()这里面。

public MyWebViewClient() {
            SslOkHttpClientUtils.cookieval = null;
            SslOkHttpClientUtils.sessionid = null;
            prepareSslPinning();
        }

每次打开h5,让cookie和sessionid置位null.

  @Override
        public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {
            Log.d(TAG,"shouldInterceptRequest1:"+url);
//            String url2 = url.replace("http://","https://");
            if(!url.startsWith("https")) {
                return null;
            }
            if(url.endsWith("favicon.ico"))
                return null;
            return processRequest(Uri.parse(url));

        }
        @Override
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public WebResourceResponse shouldInterceptRequest (final WebView view, WebResourceRequest interceptedRequest) {
            Log.d(TAG,"shouldInterceptRequest2:");
//            String url2 = interceptedRequest.getUrl().toString().replace("http://","https://");
            if(!interceptedRequest.getUrl().toString().startsWith("https")) {
                return null;
            }
            if(interceptedRequest.getUrl().toString().endsWith("favicon.ico"))
                return null;
            return processRequest(interceptedRequest.getUrl());


        }

shouldInterceptRequest方法截获https请求,针对http请求则返回null.

  private WebResourceResponse processRequest(Uri uri) {
            android.util.Log.d(TAG, "processRequest url: "+uri.toString());
            HttpsURLConnection urlConnection = httpsUrlRequest(uri,"GET");
            try {

                //获取请求的内容、contentType、encoding
                android.util.Log.d(TAG, "processRequest: "+urlConnection.getResponseCode()+":"+uri.toString());
                //若返回405 尝试post请求
                if(urlConnection.getResponseCode() == 405){
                    urlConnection = httpsUrlRequest(uri,"POST");
                    android.util.Log.d(TAG, "processRequest: "+urlConnection.getResponseCode()+":"+uri.toString());
                }
                if(urlConnection.getResponseCode() == 200){
                    String cookie = urlConnection.getHeaderField("Set-Cookie");
                    if(cookie != null) {
                        SslOkHttpClientUtils.cookieval = cookie;
                        SslOkHttpClientUtils.sessionid = cookie.substring(0, cookie.indexOf(";"));
                    }
                }
                String contentType = urlConnection.getContentType();
                String encoding = urlConnection.getContentEncoding();
                InputStream inputStream = urlConnection.getInputStream();
                if (null != contentType){
                    String mimeType = contentType;
                    if (contentType.contains(";")){
                        mimeType = contentType.split(";")[0].trim();
                    }
                    //返回新的response
                    return new WebResourceResponse(mimeType, encoding, inputStream);
                }

            } catch (MalformedURLException e) {
                e.printStackTrace();
                android.util.Log.d(TAG, "MalformedURLException: "+e.getMessage());
            } catch (IOException e) {
                e.printStackTrace();
                android.util.Log.d(TAG, "IOException: "+e.getMessage());
            }/*finally {
                if(urlConnection!=null){
                    urlConnection.disconnect();
                }
            }*/
            return null;
        }

        private HttpsURLConnection httpsUrlRequest(Uri uri ,String requstType){
            HttpsURLConnection urlConnection = null;
            try {
                //设置连接
                URL url = new URL(uri.toString());
                urlConnection = (HttpsURLConnection) url.openConnection();
                android.util.Log.d(TAG, "procesessionid: "+SslOkHttpClientUtils.sessionid);
                if(SslOkHttpClientUtils.sessionid != null) {
                    urlConnection.setRequestProperty("Cookie", SslOkHttpClientUtils.sessionid);
                }

                //为request设置SSL Socket Factory
                urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
                urlConnection.setConnectTimeout(3000);
                urlConnection.setRequestMethod(requstType);
                urlConnection.setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                return urlConnection;

            } catch (MalformedURLException e) {
                e.printStackTrace();
                android.util.Log.d(TAG, "MalformedURLException: "+e.getMessage());
            } catch (IOException e) {
                e.printStackTrace();
                android.util.Log.d(TAG, "IOException: "+e.getMessage());
            }
            return null;
        }

        private void prepareSslPinning() {
            sslContext = SslOkHttpClientUtils.getSSLContext(mContext);

        } 

HttpsURLConnection建立连接,进行认证,webView涉及页面的跳转和返回,必须要在同一个session里面进行,不然涉及到登录或者token验证时就会异常,一个页面有多个https请求,涉及到的js,css都要认证,因此需要把session强制到url请求里去。后面又发现有的页面返回405,经过抓包是由于setRequestMethod方法不对,默认是get,如果页面是post请求则会报405,需要转为post.

4.资源图片加载不出来是因为对证书请求时Android默认的处理方式是取消,需要改下代码:

  @Override
        public void onReceivedSslError(WebView view,
                                       SslErrorHandler handler, SslError error) {
            android.util.Log.d(TAG, "onReceivedSslError: "+view.getUrl());
            // TODO Auto-generated method stub
            // handler.cancel();// Android默认的处理方式
            handler.proceed();// 接受所有网站的证书
            // handleMessage(Message msg);// 进行其他处理
            
        } 

之前http完全没有任何问题,改为https后,起初发现h5页面有的加载不出来,有的从一个页面跳到另外页面报错,或者从第二个页面返回第一个页面,第一个页面报错,经过和后台联调,发现是seesionid不一致,为何https为这样呢,可能是域名不一致,导致返回的时候需要重新请求。

经过 网上查询安卓h5session不一致问题,大概是因为android手机端在访问web服务器时,没有给http请求头部设置sessionID,而使用web浏览器作为客户端访问服务器时,在客户端每次发起请求的时候,都会将交互中的sessionID:JSESSIONID设置在Cookie头中携带过去,服务器根据这个sessionID获取对应的Session,而不是重新创建一个新Session(除了这个Session失效)。

因此需要手动传个sessionid过去。

你可能感兴趣的:(anroid)