網絡通信層搭建+https

https://blog.csdn.net/dodod2012/article/details/79202085

一、網絡通信框架(Volley+https)

此次要講的網絡框架的封裝是基於volley框架進行二次封裝。底層採用httpUrlConnection進行網絡訪問,統一使用post方法請求網絡。

1訪問網絡直接調用方法:ESBHttpUtils.getInstance().httpPost(String url, Map<String, ?> params, SystemProcessListener listener);其中ESBHttpUtils是我們封裝的網絡訪問工具類,使用了單例設計模式(餓漢式)對ESBHttpUtils類進行了封裝使得線程安全。

2.ESBHttpUtils類實現了一個VolleyRequestCancelable接口,並且實現了接口裏面的方法cancelRequest,在cancelRequest方法裏面通過使用Volley框架提供的cancelAll方法對請求進行取消操作,因此,我們在使用網絡訪問時可以使用該方法對網絡請求進行取消。(在BaseActivity中在ondestroy方法中使用該方法使網絡請求與activity生命週期聯動)

3.getRequestQueue方法

ESBHttpUtils私有的構造方法中,對mContext進行了初始化,同時調用了getRequestQueue()方法對requestQueue初始化,初始化requestQueue的時候傳入的是一個HurlStack對象,這也就意味着我們的網絡框架最底層都是使用HttpURLConnection進行網絡訪問的。同時在HurlStack的構造方法中重寫了createConnection方法,在此方法中進行了添加數字證書操作、設置請求頭信息(User-agent

 

4.httpPostProcess實現方法

1)首先通過NetWorkUtil判斷手機是否聯網,沒有聯網的提示用戶。

2)通過Volley框架提供的JsonObjectRequest方法發送網絡請求,在該類的實例化中,實現了getHeaders()方法設置頭信息,在getHeaders()方法中通過createHeaderParams()方法創建一個map,把一些公共屬性比如appiddeviceIdappversion等放入了map集合中,然後把請求的參數requestParams(一個jsonObject)通過加簽處理轉換爲一個String放入到map中,最後把該map作爲一個請求頭。

5.JsonRequest請求對 Response.Listener接口中onResponse方法的處理

onResponse實現方法中會去解析json中的Result字段,然後通過接口回調,把result值以及json傳遞給httpPostProcessString url, Map<String, ?> params, SystemProcessListener listener, SystemErrorListener errorListener)方法中listeler接口中:

 listener.divisionSystemProcess(code, response);

上代碼:

public class ESBHttpUtils implements VolleyRequestCancelable {
    public static final int RESP_CODE_SUCCESS = 0;// 成功
    public static final int RESP_CODE_FAILED = -1;// 失敗
    public static final int RESP_CODE_ERROR = -2000;// 錯誤

    public static final int ERROR_TYPE_UNKNOWN = -1000;
    public static final int ERROR_TYPE_ESBKEY_INVALID = 1001;
    public static final int ERROR_TYPE_TIMESTAMP_INVALID = 1002;
    public static final int ERROR_TYPE_TOKEN_INVALID = 1003;

    private RequestQueue mRequestQueue; // 請求隊列
    private Context mContext; // 上下文
    private AppManager appManager = AppManager.getInstance();
    private int timeoutMs = 60;
    //判斷是否跳轉到登錄頁面
    private boolean toLogin = false;
    private String content_type = "application/json; charset=utf-8";
    private long timeDifference = 0;
    private int postCount = 0;

    /**
     * 獲得一個http操作類
     *
     * @return
     */
    public static ESBHttpUtils getInstance() {
        return InstanceHolder.INSTANCE;
    }

    private static class InstanceHolder {
        private static final ESBHttpUtils INSTANCE = new ESBHttpUtils();
    }

    private ESBHttpUtils() {
        mContext = PPDaiApplication.getInstance().getApplicationContext();
        mRequestQueue = getRequestQueue();
    }

    /**
     * 得到一個 RequestQueue
     *
     * @return RequestQueue
     */
    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            final String userAgent = Config.HTTP_USER_AGENT_PREFIX + AppUtil.getVersionName(PPDaiApplication.getInstance());
//            HttpClientStack stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            HurlStack stack = new HurlStack() {
                @Override
                protected HttpURLConnection createConnection(URL url) throws IOException {
                    HttpsUtils.allowAllSSL(mContext);
                    HttpURLConnection connection = super.createConnection(url);
                    connection.setInstanceFollowRedirects(false);
                    connection.setRequestProperty("User-agent", userAgent);
                    return connection;
                }
            };
            mRequestQueue = Volley.newRequestQueue(mContext, stack);
        }
        return mRequestQueue;
    }


    /**
     * handers添加數據
     *
     * @return
     */
    private Map<String, String> createHeaderParams() {

        Map<String, String> params = new HashMap<String, String>();

        params.put("APPID", Config.ESB_APP_ID);
        params.put("TIMESTAMP", dateFormat() + "");
        params.put("DEVICEID", AppUtil.getIMEI(mContext) + "");
        params.put("KEY", Config.ESB_SECRET_KEY_ID);
        params.put("KEYVERSION", ESBKeyUtils.getInstance().getKeyVersion() + "");
        params.put("APPVERSION", AppUtil.getVersionName(mContext) + "");
        String token = PreferencesUtils.getString(mContext, "token").trim();
        if (!TextUtils.isEmpty(token)) {
            params.put("TOKEN", token);
        }
        params.put("APPOS", "2");
        params.put("PRODUCTLINE", "1");
        params.put("PRODUCTID", "2");
        params.put("AID", Config.SERVER_APP_ID);
        return params;
    }

    private long dateFormat() {
        return (System.currentTimeMillis() / 1000) - timeDifference;
    }

    /**
     * @param url      請求的地址路徑
     * @param params   請求的參數
     * @param listener 請求後狀態的監聽接口
     */
    public void httpPost(String url, Map<String, ?> params, SystemProcessListener listener) {
        httpPostProcess(url, params, listener, null);
    }

    /**
     * @param url           請求的地址路徑
     * @param params        請求的參數
     * @param listener      請求後狀態的監聽接口
     * @param errorListener 請求失敗的監聽接口
     */
    public void httpPost(String url, Map<String, ?> params, SystemProcessListener listener, SystemErrorListener errorListener) {
        httpPostProcess(url, params, listener, errorListener);
    }

    /**
     * @param url           請求的地址路徑
     * @param params        請求的參數
     * @param listener      請求後狀態的監聽接口
     * @param errorListener 請求後異常狀態的監聽接口
     */
    protected void httpPostProcess(final String url, final Map<String, ?> params, final SystemProcessListener listener, final SystemErrorListener errorListener) {
        if (!NetWorkUtil.checkNet(mContext)) {
            appManager.showTaost("當前網絡不可用,請檢查網絡!");
            if (errorListener != null) {
                errorListener.error(null);
            } else {
                LoadManager.getInstance().dismiss();
            }
            return;
        }

        JSONObject jsonParams;
        if (params != null) {
            jsonParams = new JSONObject(params);
        } else {
            jsonParams = new JSONObject();
        }

        Log.e("volley", url + "  -------params >> " + jsonParams.toString());

        // 統一用post請求
        JsonObjectRequest postRequest = new JsonObjectRequest(url, jsonParams, new Response.Listener<JSONObject>() {

            @Override
            public void onResponse(JSONObject result) {
                postCount = 0;
                String response = result.toString();
                Logger.e("volley", url + "  -------onResponse >> " + response);
//                appManager.setLog(url + "  -------onResponse", response);
                try {
                    int code = result.getInt("Result");
                    if (listener != null) {
                        listener.divisionSystemProcess(code, response);
                    }
                } catch (JSONException e) {
//                    appManager.showTaost("JSONException" + e.getMessage());
                }
            }
        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                LoadManager.getInstance().dismiss();
                if (error.networkResponse != null) {
                    if (error.networkResponse.headers == null) {
                        return;
                    }
                    String errorCode = error.networkResponse.headers.get("X-PPD-CODE");
                    int errorType = getErrorType(errorCode);
                    switch (errorType) {
                        case ERROR_TYPE_ESBKEY_INVALID: //重新請求esb key
                            ESBKeyUtils.getInstance().cleanKeys();
                            if (postCount < 5) {
                                postCount++;
                                ESBKeyUtils.getInstance().getClientCryptoKey(null);
                            }
                            break;
                        case ERROR_TYPE_TIMESTAMP_INVALID:// 時間戳無效
                            ESBKeyUtils.getInstance().cleanKeys();
                            saveTimeDifference(error.networkResponse.headers.get("X-PPD-TIMESTAMP"));
                            break;
                        case ERROR_TYPE_TOKEN_INVALID:// token無效,需要重新登錄
                            appManager.showTaost("登錄信息已過期,請重新登錄");
                            ESBKeyUtils.getInstance().cleanKeys();
                            appManager.exitLogin();
                            if (!toLogin) {
                                appManager.isLogin(mContext);
                                toLogin = true;
                            }
                            return;
                    }
                }
                // error
                if (errorListener != null) {
                    errorListener.error(error);
                } else {
                    appManager.setLog("url-error", url);
                    appManager.showTaost("網絡通信異常,請稍後再試");
                }
                appManager.setLog(url + "  -------onErrorResponse", error.getMessage() + "");
                if (error != null && error.networkResponse != null) {
                    appManager.setLog(url + "  -------onErrorResponse", JSON.toJSONString(error.networkResponse.headers));
                }
            }
        }) {
            /*
             * 設置頭信息
             */
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> headers = getRequestHeaders(params);
//                Logger.e("volley", url + "  -------header >> " + JSON.toJSONString(headers));
//                appManager.setLog(url + "  -------header", JSON.toJSONString(headers));
                return headers;
            }

            @Override
            public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
                return super.setRetryPolicy(new DefaultRetryPolicy((int) TimeUnit.SECONDS.toMillis(timeoutMs), 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
            }

            @Override
            public String getBodyContentType() {
                return content_type;
            }

            @Override
            protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
                if (response.headers != null) {
                    saveTimeDifference(response.headers.get("X-PPD-TIMESTAMP"));
                }
                return super.parseNetworkResponse(response);
            }
        };

        // 生成 tag,  Activity/Fragment 生命週期結束時調用
        String tag = VolleyHttpLifeCircleHelper.getTagFromListeners(url, listener, errorListener);
        postRequest.setTag(tag);

//        appManager.setLog(url + "  -------params", JSON.toJSONString(params));

        System.setProperty("http.keepAlive", "false");
        mRequestQueue.add(postRequest);
    }

    @NonNull
    public Map<String, String> getRequestHeaders(Map<String, ?> params) {
        Map<String, String> headers = createHeaderParams();
        String signString = ESBKeyUtils.getInstance().getSignString(params);
        headers.put("X-PPD-SIGN", signString);
        headers.put("X-PPD-SIGNVERSION", "1");
        return headers;
    }

    private int getErrorType(String errorCode) {
        if ("SRV-BRQ-INVALIDSIGN".equals(errorCode) ||
                "SRV-BRQ-NOSIGN".equals(errorCode) ||
                "SRV-BRQ-NOKEY".equals(errorCode) ||
                "SRV-BRQ-INVALIDKEY".equals(errorCode) ||
                "SRV-BRQ-NOKEYVERSION".equals(errorCode) ||
                "SRV-BRQ-INVALIDKEYVERSION".equals(errorCode) ||
                "SRV-BRQ-OLDKEYVERSION".equals(errorCode) ||
                "GTW-BRQ-NOHEADER".equals(errorCode)) {
            return ERROR_TYPE_ESBKEY_INVALID;
        } else if ("GTW-BRQ-INVALIDTIMESTAMP".equals(errorCode)) {
            return ERROR_TYPE_TIMESTAMP_INVALID;
        } else if ("GTW-BRQ-INVALIDTOKEN".equals(errorCode)) {
            return ERROR_TYPE_TOKEN_INVALID;
        }
        return ERROR_TYPE_UNKNOWN;
    }

    /**
     * 保存本地時間和服務器的時間差
     *
     * @param timestamp
     */
    private void saveTimeDifference(String timestamp) {
        long serviceTimestamp = 0;
        if (!TextUtils.isEmpty(timestamp)) {
            serviceTimestamp = Long.parseLong(timestamp);
        }
        //保存本地和服務器時間差
        timeDifference = System.currentTimeMillis() / 1000 - serviceTimestamp;
    }

    @Override
    public void cancelRequest(final String tag) {
        if (mRequestQueue != null && tag != null) {
            mRequestQueue.cancelAll(new VolleyRequestFilter() {
                @NonNull
                @Override
                public Object getTag() {
                    return tag;
                }
            });
        }
    }

    public void httpGet(String url, final Response.Listener<String> listener, final Response.ErrorListener errorListener) {
        if (!NetWorkUtil.checkNet(mContext)) {
            if (errorListener != null) {
                errorListener.onErrorResponse(null);
            }
            return;
        }

        StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                if (listener != null) {
                    listener.onResponse(s);
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                if (errorListener != null) {
                    errorListener.onErrorResponse(volleyError);
                }
            }
        }) {
            @Override
            public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
                return super.setRetryPolicy(new DefaultRetryPolicy((int) TimeUnit.SECONDS.toMillis(timeoutMs), 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
            }
        };

        String tag = VolleyHttpLifeCircleHelper.getTagFromListeners(url, listener, errorListener);
        request.setTag(tag);

        System.setProperty("http.keepAlive", "false");
        mRequestQueue.add(request);
    }

}

二、框架中https實現

public class HttpsUtils {

    private static TrustManager[] trustManagers;

//將認證機構的ca證書添加到系統默認的信任證書列表裏面,供https通信時識別認證機構頒發的證書是否篡改使用
private static TrustManager[] getTrustManagers(Context context) throws Exception {
        // Load CAs from an InputStream
        // (could be from a resource or ByteArrayInputStream or ...)
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        // From https://www.washington.edu/itconnect/security/ca/load-der.crt
        InputStream caInput = new BufferedInputStream(context.getResources().openRawResource(R.raw.certification));
        Certificate ca;
        try {
            ca = cf.generateCertificate(caInput);
            System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
        } finally {
            caInput.close();
        }

        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        return tmf.getTrustManagers();
    }

    public static void allowAllSSL(Context context) {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                // TODO Auto-generated method stub
                return true;
            }

        });

        try {
            if (trustManagers == null) {
                trustManagers = getTrustManagers(context);
            }
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagers, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1)什麼是https

HTTPSHTTP通信接口部分用SLL(Secure Socket Layer)TLS (Transport Layer Security) 協議替代而已.通常, HTTP直接和TCP通信,當使用SSL,演變成了先和SSL通信,再由SSLTCP通信了,簡而言之,所謂HTTPS,其實就是身披SSL協議的這層外殼的HTTP.在採用SSL, HTTP就擁有了HTTPS的加密,證書和完整性的保護這些功能.SSL是獨立於HTTP的協議,所有不光是HTTP協議,其他運行在應用層的SMTP(郵件協議)Telnet等協議均可配合SSL協議使用.可以說SSL是當今世界上應用最廣泛的網絡安全技術.

2)爲什麼要使用https通信

所有信息都是加密傳輸,第三方無法竊聽。具有校驗機制,一旦被篡改,通信雙方都會立刻發現。配備身份證書,防止身份被冒充。http是超文本傳輸協議,信息是明文傳輸,https則是具有安全性的ssl加密傳輸協議。

3)https通信過程

通信步驟:

 

1)客戶端請求服務器,發送握手消息

2)服務器返回客戶端自己的加密算法、數字證書和公鑰;

3)客戶端驗證服務器端發送來的數字證書是否與本地受信任的證書相關信息一致;如果不一致則客戶端瀏覽器提示證書不安全如下圖所示

如果驗證通過,則瀏覽器會採用自身的隨機數算法產生一個隨機數,並用服務器發送來的公鑰加密;發送給服務器;這裏如果有人通過攻擊獲取了這個消息,那也沒用,因爲他沒有解密此段消息所需要私鑰;驗證通過的網站在瀏覽器地址欄的右邊會有一安全鎖的標識;

3)服務器解密得到此隨機數,並用此隨機數作爲密鑰採用對稱加密算法加密一段握手消息發送給瀏覽器;

4)瀏覽器收到消息後解密成功,則握手結束,後續的信息都通過此隨機密鑰加密傳輸。

以上是服務端認證的情況,如果服務端對訪問的客戶端也有認證需求,則客戶端也需要將自己的證書發送給服務器,服務器認證不通過,通訊結束;原理同上;

什麼是數字證書:

數字證書爲發佈公鑰提供了一種簡便的途徑,其數字證書則成爲加密算法以及公鑰的載體,依靠數字證書,我們可以構建一個簡單的加密網絡應用平臺,數字證書就好比我們生活中的身份證,現實中,身份證由公安機關簽發,而網絡用戶的身份憑證由數字證書頒發認證機構CA簽發,只有經過CA簽發的證書在網絡中才具備可認證性,CA並不是一個單純的防禦手段,它集合了多種密碼學算法。.CER證書籤發流程:


證書的簽發過程實際上是對申請數字證書的公鑰做數字簽名,證書的驗證過程實際上是對數字證書的公鑰做驗證簽名,其中還包含證書有效期驗證,通過CA數字證書,我們對網絡上傳輸的數據進行加密/解密和簽名/驗證操作,確保數據機密性、完整性、抗否認性、認證性,保證交易實體身份的真實性,保證網絡安全性。

 

 

圖解https:

 

如有不懂歡迎諮詢,本人qq:1342923917

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章