1. 程式人生 > >android ksoap2下webservice的 https請求

android ksoap2下webservice的 https請求

參考okhttp呼叫https實現自簽名SSL證書:

stackoverflow:How to use Ksoap2 on ssl (https) connection - Android

我的專案中使用網路請求庫是webservice方式實現,依賴包版本是ksoap2-android-assembly-3.3.0-jar-with-dependencies.jar。
證書驗證類

SslRequest.java如下:

package com.tcps.xiangyangtravel.network;
import android.annotation.SuppressLint;
import
android.content.Context; import android.renderscript.ScriptGroup.Input; import android.util.Log; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import
java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import
javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.conn.ssl.AllowAllHostnameVerifier; import com.tcps.xiangyangpassenger.activity.NetMainActivity; import com.tcps.xiangyangtravel.activity.Splash; import com.tcps.xiangyangtravel.app.BusApplication; import com.tcps.xiangyangtravel.utils.LogUtils; /** * Created by Sanjay Kumar on 2/18/2015. * Copy by Liying 12/2016 */ public class SslRequest implements X509TrustManager { private static TrustManager[] trustManagers; private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{}; @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public boolean isClientTrusted(X509Certificate[] chain) { return true; } public boolean isServerTrusted(X509Certificate[] chain) { return true; } @Override public X509Certificate[] getAcceptedIssuers() { return _AcceptedIssuers; } @SuppressLint("TrulyRandom") public static void allowAllSSL(String url,Context mContext) { HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }); SSLContext context = null; InputStream inputStream = null; if (trustManagers == null) { trustManagers = new TrustManager[]{new SslRequest()}; } try { context = SSLContext.getInstance("TLS"); context.init(null, trustManagers, new SecureRandom()); try { //自簽名證書client.cer放在assets資料夾下 inputStream = mContext.getAssets().open("client.cer"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } SSLSocketFactory socketFactory = SSLUtil.getSSLSocketFactory(inputStream); if (socketFactory != null) { HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); } /* * 使用此方法並配合HttpsURLConnection connection設定(見下面)則可以越過證書驗證 */ // HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); LogUtils.e("Ssl_NoSuchAlgorithmException&&&&&&&&&&&>>>>>>>>>", e.toString()); } catch (KeyManagementException e) { e.printStackTrace(); LogUtils.e("Ssl_KeyManagementException&&&&&&&&&&&>>>>>>>>>", e.toString()); } /* * 使用此方法則可以越過證書驗證 */ /*HttpsURLConnection connection; try { connection = (HttpsURLConnection) new URL(url).openConnection(); ((HttpsURLConnection) connection).setHostnameVerifier(new AllowAllHostnameVerifier()); connection.setUseCaches(false); connection.setDoOutput(true); connection.setDoInput(true); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); LogUtils.e("Ssl_MalformedURLException&&&&&&&&&&&>>>>>>>>>", e.toString()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); LogUtils.e("Ssl_IOException&&&&&&&&&&&>>>>>>>>>", e.toString()); }*/ } }
package com.tcps.xiangyangtravel.network;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

/**
 * Https 證書工具類
 * User:lizhangqu([email protected])
 * Date:2015-09-02
 * Time: 12:52
 */
public class SSLUtil {
    //使用命令keytool -printcert -rfc -file srca.cer 匯出證書為字串,然後將字串轉換為輸入流,如果使用的是okhttp可以直接使用new Buffer().writeUtf8(s).inputStream()

    /**
     * 返回SSLSocketFactory
     *
     * @param certificates 證書的輸入流
     * @return SSLSocketFactory
     */
    public static SSLSocketFactory getSSLSocketFactory(InputStream... certificates) {
        return getSSLSocketFactory(null,certificates);
    }

    /**
     * 雙向認證
     * @param keyManagers KeyManager[]
     * @param certificates 證書的輸入流
     * @return SSLSocketFactory
     */
    public static SSLSocketFactory getSSLSocketFactory(KeyManager[] keyManagers, InputStream... certificates) {
        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) {
                }
            }
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new SecureRandom());
            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
            return socketFactory;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 獲得雙向認證所需的引數
     * @param bks bks證書的輸入流
     * @param keystorePass 祕鑰
     * @return KeyManager[]物件
     */
    public static KeyManager[] getKeyManagers(InputStream bks, String keystorePass) {
        KeyStore clientKeyStore = null;
        try {
            clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bks, keystorePass.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, keystorePass.toCharArray());
            KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
            return keyManagers;
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

在發出https請求前呼叫一次SslRequest.allowAllSSL(“”,context);證書驗證(可以寫在程式的第一個Activity中),驗證成功後,後續呼叫https請求即可。

由於我的專案使用的是ksoap框架呼叫webservice,之前用的是http請求:

public final static String WEBSERVICE_BUS_URL = "http://180.89.58.20:xxxx/bus_service_new/ServicesFacadePort?wsdl";(url型別)
public final static String BUS_SERVICE_NAMESPACE = "http://xxx.xxxx.com.cn/";(名稱空間)
String method = "getSystem";(方法名)
private static HttpTransportSE transport;
transport = new HttpTransportSE(AppUtil.WEBSERVICE_BUS_URL, AppUtil.TIMEOUT);
---------------------------------envelope----------
String url;
        String serviceNamespace;

            url = AppUtil.WEBSERVICE_BUS_URL;
            serviceNamespace = AppUtil.BUS_SERVICE_NAMESPACE;

        SoapObject request = new SoapObject(serviceNamespace, method);
        SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);

        Element el = new Element().createElement(serviceNamespace, "TCPSHeader");
        el.setAttribute(serviceNamespace, "accessType", AppUtil.ACCESSTYPE);(可以用於區分ios和android,規則自己定,比如AppUtil.ACCESSTYPE = “1”代表android)
        el.setAttribute(serviceNamespace, "productID", AppUtil.PRODUCTID);(唯一)
        el.setAttribute(serviceNamespace, "version", AppUtil.VERSION);(apk版本號,只是便於記錄,可以隨意)
        header[0] = el;
        envelope.headerOut = header;
        envelope.dotNet = false;
        //這裡可以加密傳參
        if (param != null) {
            for (int n = 0; n < param.length; n++) {
                request.addProperty("arg" + n, param[n]);
            }
        }
        envelope.bodyOut = request;
---------------------------------envelope----------

//base64.register(envelope);(加密註冊)
        Log.i("呼叫時間-----------------------", String.valueOf(System.currentTimeMillis()));
        try {
            transport.call(url, envelope);
            System.out.println(
                    "-----------------" + "請求的資料" + request.toString() + "返回: " + envelope.getResponse().toString());(返回資料也可以加密,在需要的地方再解密)
            return envelope.getResponse();
        } catch (Exception e) {

            if (e instanceof SocketTimeoutException) {
                return R.string.error_timeout;(超時)
            } else {
                return R.string.error_unknown;(未知錯誤)
            }
        }

改為https請求後,只需要將HttpTransportSE換成HttpsTransportSE,然後:

public final static String WEBSERVICE_BUS_URL = "http://180.89.58.20:xxxx/bus_service_new/ServicesFacadePort?wsdl";(url型別)
public final static String BUS_SERVICE_NAMESPACE = "http://xxx.xxxx.com.cn/";(名稱空間)
String nameSpace = "";
        String URL = "";
        String HOST = "";
        String WS_OPS = "";
        String SOAP_ACTION = "";
        String METHOD_NAME = method;
        Integer POST = 0;
if (URL.startsWith("https")){
                /*
                 * https請求
                 */
                nameSpace = AppUtil.BUS_SERVICE_NAMESPACE;
                URL = AppUtil.WEBSERVICE_BUS_URL;
                HOST = "180.89.58.20";
                WS_OPS = "/bus_service_new/ServicesFacadePort?wsdl";(指AppUtil.WEBSERVICE_BUS_URL去掉HOST和POST後的部分)
                SOAP_ACTION = nameSpace + method;
                POST = xxxx;

             }
HttpsTransportSE androidHttpTransport = new HttpsTransportSE(HOST,POST,WS_OPS,AppUtil.TIMEOUT);
        androidHttpTransport.debug = true;
        //去掉ssl證書驗證
//        SslRequest.allowAllSSL(URL,mContext);(在程式每次啟動時已呼叫,就不用每次呼叫介面時去重複驗證了)

        try {
            androidHttpTransport.call(SOAP_ACTION, envelope);(envelope不變,參照上面的http請求方式)
            if (envelope.getResponse() != null) {
                result_return = envelope.getResponse().toString();
                LogUtils.e("--------------------------------", "返回結果" + result_return);

            }
        } catch (Exception e) {
            if (e instanceof SocketTimeoutException) {
                result_return = String.valueOf(R.string.error_timeout);
            } else {
                result_return = String.valueOf(R.string.error_unknown);
            }
        }

主要是多了一步證書驗證。客戶端請求類由http換成https型別,方法都封裝在那個ksoap包中。