天天看點

Android HTTPS認證之Volley封裝

1、概述

Goolge IO 2013推出了Volley架構,在這之前,我們在程式中需要和網絡通信的時候,大體使用的東西莫過于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,Google I/O 2013上,Volley釋出了。Volley是Android平台上的網絡通信庫,能使網絡通信更快,更簡單,更健壯。 

這是Volley名稱的由來: a burst or emission of many things or a large amount at once 

在Google IO的演講上,其配圖是一幅發射火弓箭的圖,有點類似流星。

2、内容簡介

本文依然接着上文HTTPS通路的問題,因為Volley并不支援HTTPS通路,是以如果項目中需要用到Volley并且設計到HTTPS的通路問題就需要自己進行封裝了,本文就是在此基礎上産生的。文章主要對Volley進行了網絡請求的封裝和Volley使用HTTPS請求的封裝。

3、架構使用示例

Volley的具體的使用請自行學習,本文是對其使用的封裝。

首先我們來看看具體使用示例

使用前需要配置清單檔案

<pre name="code" class="html"><application
    android:name=".application.VolleyApplication"
    ...
</application>
           
  • 普通http的get請求 StringRequest - HTTP/GET
    private void stringRequestGetHttpExample(){
    
    DataRequester.withHttp(this)
            .setUrl(HTTP)
            .setMethod(DataRequester.Method.GET)
            .setStringResponseListener(new DataRequester.StringResponseListener() {
                @Override
                public void onResponse(String response) {
                    Toast.makeText(MainActivity.this, "HTTP/GET,StringRequest successfully.", Toast.LENGTH_SHORT).show();
                    tvResult.setText(response);
                }
            })
            .requestString();
    }
               
  • 普通http的post請求 StringRequst HTTP/POST
    private void stringRequestPostHttpExample(){
    
    HashMap<String, String> body = new HashMap <String, String>() ;
    body.put( "name", "xxx" );
    body.put( "age", "20" );
    
    DataRequester.withHttp(this)
            .setUrl(HTTP)
            .setBody(body)
            .setMethod(DataRequester.Method.POST)
            .setStringResponseListener(new DataRequester.StringResponseListener() {
                @Override
                public void onResponse(String response) {
                    Toast.makeText(MainActivity.this, "HTTP/POST,StringRequest successfully.", Toast.LENGTH_SHORT).show();
                    tvResult.setText(response);
                }
            } )
            .requestString();
    }
               
  • HTTPS預設證書通路的json資料的get請求 JsonRequst HTTP/POST
    private void jsonRequestGetHttpsExample(){
    DataRequester.withDefaultHttps(this)
            .setUrl(HTTPS)
            .setJsonResponseListener(new DataRequester.JsonResponseListener() {
                @Override
                public void onResponse(JSONObject response) {
    
                    try {
                        String s = response.getString("data");
                        tvResult.setText(s);
                        Toast.makeText( MainActivity.this, "HTTPS/GET, JsonRequest successfully.", Toast.LENGTH_SHORT).show();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            } )
            .setResponseErrorListener(new DataRequester.ResponseErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    tvResult.setText( error.getMessage() );
                }
            })
            .requestJson();
    }
               
  • HTTPS預設證書通路的json資料的post請求 JsonRequst HTTP/POST
    private void jsonRequestPostHttpsExample(){
    JSONObject json = new JSONObject();
    try{
        json.put( "name", "xxx" );
        json.put( "age", "20" );
    }catch (Exception e){
        e.printStackTrace();
    }
    
    DataRequester.withDefaultHttps(this)
            .setUrl(HTTPS)
            .setBody(json)
            .setJsonResponseListener(new DataRequester.JsonResponseListener() {
                @Override
                public void onResponse(JSONObject response) {
    
                    try {
                        String data = response.getString("data");
                        tvResult.setText( data);
    
                        Toast.makeText(MainActivity.this, "HTTPS/POST, JsonRequest successfully.", Toast.LENGTH_SHORT).show();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            } )
            .setResponseErrorListener(new DataRequester.ResponseErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    tvResult.setText(error.getMessage());
                }
            })
            .requestJson();
    }
               
  • 自定義證書使用
    DataRequester.withSelfCertifiedHttps(this)
            .setUrl(HTTPS)
            .setJsonResponseListener(new YouJsonRequestListener ())
            .requestJson();
               

OK,具體的使用基本上就是上述方式了,下面正式開始介紹代碼的實作

4、自定義Application

首先來看看這個自定義的Applicaion的做了那些事情,代碼如下

public class VolleyApplication extends Application {

    private static VolleyApplication mInstance ;

    /** 建立http請求隊列 */
    private RequestQueue mRequestQueueWithHttp ;
    /** 建立自定義證書的Https請求隊列 */
    private RequestQueue mRequestQueueWithSelfCertifiedSsl ;
    /** 建立預設證書的Https請求隊列 */
    private RequestQueue mRequestQueueWithDefaultSsl ;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this ;
    }

    /**通過單例模式擷取對象*/
    public static VolleyApplication getInstance(){
        return mInstance ;
    }

    /**
     * 擷取http請求隊列
     * @return
     */
    public RequestQueue getRequestQueueWithHttp(){
        if(mRequestQueueWithHttp == null){
            //建立普通的request
            mRequestQueueWithHttp = Volley.newRequestQueue(getApplicationContext());
        }
        return mRequestQueueWithHttp ;
    }


    /**
     * 擷取預設證書https請求隊列
     * @return
     */
    public RequestQueue getRequestQueueWithDefaultSsl(){
        if(mRequestQueueWithDefaultSsl == null){
            Network network = new BasicNetwork(new HurlStack());
            Cache cache = new DiskBasedCache(getCacheDir(),1024 * 1024) ;
            mRequestQueueWithDefaultSsl = new RequestQueue(cache,network) ;
            mRequestQueueWithDefaultSsl.start();
            SSLCertificateValidation.trustAllCertificate();
        }

        return mRequestQueueWithDefaultSsl ;
    }

    /**
     * 擷取自定義證書請求隊列
     * @return
     */
    public RequestQueue getRequestQueueWithSelfCertifiedSsl(){

        if(mRequestQueueWithSelfCertifiedSsl == null){
            SSLSocketFactory sslSocketFactory = SelfSSLSocketFactory.getSSLSocketFactory(getApplicationContext());
            Network network = new BasicNetwork(new HurlStack(null,sslSocketFactory));
            Cache cache = new DiskBasedCache(getCacheDir(),1024 * 1024) ;
            mRequestQueueWithSelfCertifiedSsl = new RequestQueue(cache,network) ;
            mRequestQueueWithSelfCertifiedSsl.start();

            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    // 當URL的主機名和伺服器的辨別主機名不比對預設傳回true
                    return true;
                }
            });
        }

        return mRequestQueueWithSelfCertifiedSsl ;
    }
}
           

從上面的代碼中我們能夠很容易的看出來具體的做了哪些事情,首先通過單例的形式提供了對象。通過getRequestQueueWithHttp()方法提供普通的http通路的請求隊列。通過getRequestQueueWithDefaultSsl()提供了預設證書的請求隊列,例如請求https://www.baidu.com的時候就可以使用該方法擷取請求隊列。當然如果我們需要自定義證書讓用戶端和服務端進行互動的話就需要通過getRequestQueueWithSelfCertifiedSsl()來擷取請求隊列了。各個方法的具體的實作參考代碼即可。

5、證書配置

public class CertificateConfig {
    public static final String KEY_STORE_TYPE_BKS = "BKS";
    public static final String keyStoreFileName = "client.bks";
    public static final String keyStorePassword = "123456" ;
    public static final String trustStoreFileName = "server.cer";
    public static final String trustStorePassword = "123456";
}
           

該類中主要是配置證書的一些資訊,證書的具體生成方式參考HTTPS認證

6、信任所有證書類的實作SSLCertificateValidation

首先直接來看代碼

public class SSLCertificateValidation {

    /**
     * 信任所有證書
     */
    public static void trustAllCertificate() {

        try {
            //設定TLS方式
            SSLContext sslc = SSLContext.getInstance("TLS");

            //new一個自定義的TrustManager數組,自定義類中不做任何實作
            TrustManager[] trustManagers = {new NullX509TrustManager()};
            sslc.init(null,trustManagers,null);
            HttpsURLConnection.setDefaultSSLSocketFactory(sslc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(new NullHostnameVerifier());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //自定義類實作X509TrustManager接口,但方法不做實作
    private static class NullX509TrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

    //自定義類實作HostnameVerifier接口,但方法verify直接傳回true,預設信任所有
    private static class NullHostnameVerifier implements HostnameVerifier {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }
}
           

該類沒有什麼好解釋的,就是自定義一些類實作證書接口,但是不做任何的方法實作,預設就可以,因為需要信任所有的證書。當然如果需要自定義證書就要做實作了。

下面就來看看自定義證書所做的事情。

7、自定義SelfSSLSocketFactory工廠類

啥也不說了直接上代碼

public class SelfSSLSocketFactory {

    /**
     * 擷取SSLSocketFactory
     * @param context
     * @return
     */
    public static SSLSocketFactory getSSLSocketFactory(Context context) {
        try {
            return setCertificates(context, context.getAssets().open(CertificateConfig.trustStoreFileName)) ;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null ;
    }


    /**
     * 産生SSLSocketFactory
     * @param context
     * @param certificates
     * @return
     */
    private static SSLSocketFactory setCertificates(Context context,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){
                    e.printStackTrace() ;
                }
            }

            //取得SSL的SSLContext執行個體
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.
                    getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);

            //初始化keystore
            KeyStore clientKeyStore = KeyStore.getInstance(CertificateConfig.KEY_STORE_TYPE_BKS);
            clientKeyStore.load(context.getAssets().open(CertificateConfig.keyStoreFileName), CertificateConfig.keyStorePassword.toCharArray());

            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, CertificateConfig.trustStorePassword.toCharArray());

            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
            return sslContext.getSocketFactory() ;

        } catch (Exception e){
            e.printStackTrace();
        }
        return null ;
    }
}
           

在代碼中我們可以看到該類的左右就是提供SSLSocketFactory對象,主要方法就是setCertificates()方法,該方法需要傳人Context對象和服務端證書的流對象,在其中将服務端證書和用戶端證書庫進行讀取和設定,設定完成後傳回生成的SSLSocketFactory對象。傳回的對象在什麼地方進行使用呢?還記得我們的自定義Application類嗎,自定義的的Application類中提供了擷取自定義證書的請求隊列,在其中需要該類提供的SSLSocketFactory對象。

至此輔助類都介紹完了,下面來看看最重要的一個類

8、網絡請求類DataRequester

該類内容較多,所有的請求都是通過該類完成配置和将請求加入到請求隊列中的。下面就看看該的代碼吧,雖然代碼較多,但是邏輯很簡單

public class DataRequester {

    private static final String TAG = "DataRequester";

    /**
     * 建立請求類型枚舉
     */
    private enum Type{
        HTTP,
        HTTPS_DEFALT,
        HTTPS_SELF_CERTIFIED;
    }

    public enum Method {
        GET,
        POST
    }

    /** String資料請求成功傳回接口 */
    public interface StringResponseListener extends Response.Listener<String>{}

    /** Json資料請求成功傳回接口 */
    public interface JsonResponseListener extends Response.Listener<JSONObject>{}

    /** JsonArray資料請求成功傳回接口 */
    public interface JsonArrayResponseListener extends Response.Listener<JSONArray>{}

    /** 請求失敗傳回接口 */
    public interface ResponseErrorListener extends Response.ErrorListener{}


    private StringResponseListener mStringResponseListener ;
    private JsonResponseListener mJsonResponseListener ;
    private JsonArrayResponseListener mJsonArrayResponseListener ;
    private ResponseErrorListener mResponseErrorListener ;

    /** 請求隊列 從application中擷取*/
    private RequestQueue mRequestQueue ;
    /**請求位址*/
    private String url ;
    /**請求方式*/
    private Method method ;


    private JSONObject jsonBody;
    private Map<String,String> mapBody;
    private Map<String,String> headers;
    private String strBody;


    private DataRequester(Context context,Type type){
        if(type == Type.HTTP){
            mRequestQueue = VolleyApplication.getInstance().getRequestQueueWithHttp() ;
        }
        if(type == Type.HTTPS_DEFALT){
            mRequestQueue = VolleyApplication.getInstance().getRequestQueueWithDefaultSsl() ;
        }
        if(type == Type.HTTPS_SELF_CERTIFIED){
            mRequestQueue = VolleyApplication.getInstance().getRequestQueueWithSelfCertifiedSsl() ;
        }
    }

    /**
     * 普通http請求
     * @param context
     * @return
     */
    public static DataRequester withHttp(Context context){
        return new DataRequester(context,Type.HTTP) ;
    }

    /**
     * 預設證書HTTPS請求
     * @param context
     * @return
     */
    public static DataRequester withDefaultHttps(Context context){
        return new DataRequester(context,Type.HTTPS_DEFALT) ;
    }

    /**
     * 自定義證書HTTPS請求
     * @param context
     * @return
     */
    public static DataRequester withSelfCertifiedHttps(Context context){
        return new DataRequester(context,Type.HTTPS_SELF_CERTIFIED) ;
    }

    /**
     * 設定請求url
     * @param url
     * @return
     */
    public DataRequester setUrl(String url){
        this.url = url ;
        return this ;
    }

    /**
     * 設定請求方式
     * @param method
     * @return
     */
    public DataRequester setMethod(Method method){
        this.method = method ;
        return this ;
    }

    /**
     * 設定JsonObject請求體
     * @param body
     * @return
     */
    public DataRequester setBody(JSONObject body) {
        this.jsonBody = body;
        return this;
    }

    /**
     * 設定String請求體
     * @param body
     * @return
     */
    public DataRequester setBody(String body) {
        this.strBody = body;
        return this;
    }

    /**
     * 設定Map請求體
     * @param stringRequestParam
     * @return
     */
    public DataRequester setBody (Map<String,String> stringRequestParam){
        this.mapBody = stringRequestParam;
        return this;
    }

    /**
     * 設定String請求成功監聽
     * @param mStringResponseListener
     * @return
     */
    public DataRequester setStringResponseListener(StringResponseListener mStringResponseListener) {
        this.mStringResponseListener = mStringResponseListener;
        return this;
    }

    /**
     * 設定Json請求成功傳回監聽
     * @param mJsonResponseListener
     * @return
     */
    public DataRequester setJsonResponseListener(JsonResponseListener mJsonResponseListener) {
        this.mJsonResponseListener = mJsonResponseListener;
        return this;
    }

    /**
     * 設定JsonArray請求成功監聽
     * @param mJsonArrayResponseListener
     * @return
     */
    public DataRequester setJsonArrayResponseListener (JsonArrayResponseListener mJsonArrayResponseListener){
        this.mJsonArrayResponseListener = mJsonArrayResponseListener;
        return this;
    }

    /**
     * 設定請求出錯監聽
     * @param mResponseErrorListener
     * @return
     */
    public DataRequester setResponseErrorListener(ResponseErrorListener mResponseErrorListener) {
        this.mResponseErrorListener = mResponseErrorListener;
        return this;
    }

    /**
     * String請求
     */
    public void requestString(){
        StringRequest request = null ;

        if(Method.GET == method){
            request = new StringRequest(
                    Request.Method.GET,
                    url,
                    mStringResponseListener,
                    mResponseErrorListener);
        }
        if(Method.POST == method){
            request = new StringRequest(
                    Request.Method.POST,
                    url,
                    mStringResponseListener,
                    mResponseErrorListener){
                @Override
                protected Map<String, String> getParams() throws AuthFailureError {
                    return mapBody ;
                }

                @Override
                public Map<String, String> getHeaders() throws AuthFailureError {
                    if (headers != null){
                        return headers;
                    }else {
                        return super.getHeaders();
                    }
                }
            };
        }
        mRequestQueue.add(request) ;
    }

    /**
     * Json請求
     */
    public void requestJson() {
        JsonObjectRequest request = new JsonObjectRequest( url,
                jsonBody,
                mJsonResponseListener,
                mResponseErrorListener );

        if (jsonBody != null) {
            LogUtils.d(TAG, request.getBody().toString());
        }

        mRequestQueue.add( request );
    }

    /**
     * JsonArray請求
     */
    public void requestJsonArray() {
        JsonArrayRequest request = new JsonArrayRequest (url,
                mJsonArrayResponseListener,
                mResponseErrorListener );

        mRequestQueue.add( request );
    }

     /** 圖檔顯示的控件 */
    private ImageView mImageView ;
    /** 下載下傳圖檔的緩存 */
    private ImageLoader.ImageCache mCache = null ;
    /** 下載下傳過程過程中顯示的圖檔 */
    private int mDefaultImage ;
    /** 下載下傳失敗後顯示的圖檔 */
    private int mFailImage ;
    /** 圖檔的寬度 */
    private int maxWidth = 0 ;
    /** 圖檔的高度 */
    private int maxHeight = 0;

    /**
     * 設定圖檔顯示的控件
     * @param iv
     * @return
     */
    public DataRequester setImageView(ImageView iv){
        this.mImageView = iv ;
        return this ;
    }

    /**
     * 設定下載下傳圖檔過程中顯示的圖檔
     * @param defaultImage
     * @return
     */
    public DataRequester setDafaultImage(int defaultImage){
        this.mDefaultImage = defaultImage ;
        return this ;
    }

    /**
     * 設定下載下傳失敗時顯示的圖檔
     * @param failImage
     * @return
     */
    public DataRequester setFailImage(int failImage){
        this.mFailImage = failImage ;
        return this ;
    }

    /**
     * 設定圖檔的緩存
     * @param cache
     * @return
     */
    public DataRequester setImageCache(ImageLoader.ImageCache cache){
        this.mCache = cache ;
        return this ;
    }

    /**
     * 設定下載下傳圖檔的最大寬度,預設為0,可以不設定
     * @param width
     * @return
     */
    public DataRequester setImageWidth(int width){
        this.maxWidth = width ;
        return this ;
    }

    /**
     * 設定下載下傳圖檔的最大高度,預設為0,可以不設定
     * @param height
     * @return
     */
    public DataRequester setImageHeight(int height){
        this.maxHeight = height;
        return this ;
    }


    /**
     * 請求圖檔
     */
    public void requestImage(){
        if(mCache == null){
            mCache = new ImageLoader.ImageCache() {
                @Override
                public Bitmap getBitmap(String url) {
                    return null;
                }

                @Override
                public void putBitmap(String url, Bitmap bitmap) {

                }
            } ;
        }
        ImageLoader imageLoader = new ImageLoader(mRequestQueue, mCache);
        ImageLoader.ImageListener listener = ImageLoader.getImageListener(mImageView,mDefaultImage, mFailImage);

        imageLoader.get(url,listener, 200, 200);
    }
}
           

首先看看該類,在類的開始定義了兩個枚舉Type和Method,Type定義的是連接配接伺服器的方式,如HTTP,預設HTTPS,自定義HTTPS。Method定義的是請求方式POST和GET方式兩種。因為這兩種是最常用的兩種,是以暫時隻定義了這兩種請求方式,如果需要其他的請求方式可以再添加,這裡就不做額外的添加了,有需要的可以自己嘗試添加即可。 

在類中還定義了一些請求回調的接口

/** String資料請求成功傳回接口 */
public interface StringResponseListener extends Response.Listener<String>{}

/** Json資料請求成功傳回接口 */
public interface JsonResponseListener extends Response.Listener<JSONObject>{}

/** JsonArray資料請求成功傳回接口 */
public interface JsonArrayResponseListener extends Response.Listener<JSONArray>{}

/** 請求失敗傳回接口 */
public interface ResponseErrorListener extends Response.ErrorListener{}
           

如果伺服器傳回的是String類型的内容則可以通過StringResponseListener擷取到伺服器傳回的内容

如果伺服器傳回的是Json字元串類型的内容則可以通過JsonResponseListener擷取到伺服器傳回的内容

如果伺服器傳回的是JsonArray類型的内容則可以通過JsonArrayResponseListener擷取到伺服器傳回的内容

如果請求出錯或失敗則可以通過ResponseErrorListener擷取請求失敗的原因。

類中還有對應的設定請求方式和URL還有設定參數等方法,設定完成後需要對用的request方法,在request方法中調用Vollley的方法設定成功回調和将請求加入到對應的請求隊列方式中。該請求隊列通過自定義的Application擷取。

9、總結

該封裝還是很多不足的,比如在設定證書的時候我采用的是之前的部落格中生成證書和設定證書的方式做的,如果有需要特殊的則需要自己完成SelfSSLSocketFactory類中的setCertificates()。

項目位址https://github.com/studyzhxu/VolleyHttps

繼續閱讀