瞭解 Retrofit2 的安全性

一 什麼Retrofit

官方標語;A type-safe HTTP client for Android and Java
語意很明顯一款android安全類型的http客戶端, 那麼怎麼樣纔算安全?支持https?支持本地線程安全?
發現Rertofit其內部都是支持lambda語法(國內稱只鏈式語法),內部支持okhttp, 並且支持響應式RxJAava,當然jdk1.8 和android studio工具也支持lambda。帶着這些疑問 我開始探究一下。

在此之前準備入手資料:

國外博客
https://inthecheesefactory.com/blog/retrofit-2.0/en

官方github
http://square.github.io/retrofit/
二 Retrofit怎麼使用

下文之前先給大家看下傳統的httpclient(url) + AsyncTask實現的登錄功能,這樣我們才能發現Retrofit的優雅之處.不優雅之處請閱讀:http://blog.csdn.net/sk719887916/article/details/53613263

傳統方式:

/**
 * Represents an asynchronous login/registration task used to authenticate
 * the user.
 */
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {

    private final String mEmail;
    private final String mPassword;

    UserLoginTask(String email, String password) {
        mEmail = email;
        mPassword = password;
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        // TODO: attempt authentication against a network service.

        try {
            // Simulate network access.
            String result = "";
            BufferedReader in = null;
            String path ="http://localhost:8080/login/?" +"email =" + mEmail + "& password =" + mPassword;
            URL url =new URL(path);
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.setConnectTimeout(5 * 1000);
            conn.setRequestMethod("GET");
            InputStream inStream = conn.getInputStream();
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null)
            {
                result += "\n" + line;
            }

        }catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        for (String credential : DUMMY_CREDENTIALS) {
            String[] pieces = credential.split(":");
            if (pieces[0].equals(mEmail)) {
                // Account exists, return true if the password matches.
                return pieces[1].equals(mPassword);
            }
        }

        // TODO: register the new account here.
        return true;
    }

    @Override
    protected void onPostExecute(final Boolean success) {
        mAuthTask = null;


        if (success) {
            // do SomeThing
        } else {
            mPasswordView.setError(getString(R.string.error_incorrect_password));
            mPasswordView.requestFocus();
        }
    }

    @Override
    protected void onCancelled() {
        mAuthTask = null;
        showProgress(false);
    }
}

private void enterhome() {
    Intent intent = new Intent(LoginActivity.this, MainListActivity.class);
    startActivity(intent);
}

發現原理也很簡單,點擊loginbtn開啓一個異步線程 在AsyncTask在 doInBackground中訪問登錄API,在onPostExecute中進行UI更新;也能很簡單流暢的解決UI線程請求網絡 非UI線程更新UI的問題, 接下來介紹用Retrofit來實現以上相同功能。

2 Rxtrofit

/**
* 登錄!
*/

private  void getLogin() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://localhost:8080/")
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    ApiManager apiService = retrofit.create(ApiManager.class);

    Call<LoginResult> call = apiService.getData("lyk", "1234");
   call.enqueue(new Callback<LoginResult>() {
       @Override
       public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
           if (response.isSuccess()) {
               // do SomeThing
           } else {
              //直接操作UI
           }
       }

       @Override
       public void onFailure(Call<LoginResult> call, Throwable t) {

           // do onFailure代碼
       }
   });
}

ApiManager接口

/**
 * Created by LIUYONGKUI on 2016-05-03.
*/
public interface ApiManager {

 @GET("login/")
 Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);

好了 看了以上代碼 或許你已經看到了他的鏈式優雅高大上的地方了,也許看不懂,也許會懵逼 但沒關係我們繼續入門。

1 配置gradle

compile ‘com.squareup.retrofit2:retrofit:2.0.0-beta4’
compile ‘com.squareup.retrofit2:converter-gson:2.0.0-beta4’
com.squareup.retrofit2:converter-gson:2.0.0-beta4 此依賴非必須,只是方便我對http返回的數據進行解析。

2 定義實例化

1》初始化Retrofit

 Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://localhost:8080/")
          .addConverterFactory(GsonConverterFactory.create())
           .build();

通過 Retrofit.Builder 來創建一個retrofit客戶端,接着添加host url, 然後制定數據解析器,上面依賴的gson就是用在這裏做默認數據返回的, 之後通過build()創建出來
Retrofit也支持且內部自帶如下格式:

Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
2》編寫API

@GET("login/")
  Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);

Call是支持Cloneable序列化的 並支持泛型,且此類Retrofit統一返回對象,支持Callback回調,我們可以傳入制定的解析Modle,就會在主線程裏返回對應的model數據,這裏主要用註解@get @post s設置請求方式,後面“login/”是方法Url, @Query(“name”)來設定body的parameters.

3》 調用API
Retrofit支持異步和同步,這裏我們用call.enqueue(new Callback來採用異步請求,如果 call.execute() 則採用同步方式

Call<LoginResult> call = apiService.getData("lyk", "1234");
   call.enqueue(new Callback<LoginResult>() {
       @Override
       public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {

       }

       @Override
       public void onFailure(Call<LoginResult> call, Throwable t) {


       }
   });
}

取消請求

直接用call實例進行cancel即可

call.cancel();
如果還未理解請閱讀參考入門資料:Retrofit 2.0:有史以來最大的改進

三 進階拓展

通過以上的介紹和案列,我們瞭解了怎樣運用Retrofit請求網絡數據,展現數據更新UI,但實際開發中會存在很多問題,很多同學會遇到:Retrofit的內部Log都無法輸出 , header怎麼加入,請求怎麼支持https,包括怎麼結合RxJava.? 不用擔心,這些Retrofit 2.0 都給我提供了自定義的Interceptor(攔截器),通過不同的Interceptor可以實現不同的自定義請求形式,比如統一加head,參數,加入證書(ssl)等,前提必須結合okhttp來實現 , 通過給OkHttpClient添加Interceptor,然後給Retrofit設置http客戶端即可.Retrofit提供了
.client()方法供我們傳入自定義的網絡客戶端,當然默然就是okhttps. 如果無法自動導包 需要我們自己添加對okhttp的依賴

compile ‘com.squareup.okhttp3:okhttp:3.3.1’
OkHttp入門請移步:

~https://github.com/square/okhttp
~ OKHttp源碼解析

1 開啓Log

Retrofit retrofit = new Retrofit.Builder().client(new OkHttpClient.Builder()

                 .addNetworkInterceptor(
                            new   HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))        .build())

2 增加頭部信息

統一通用header

 new Retrofit.Builder()
           .addConverterFactory(GsonConverterFactory.create())
           .client(new OkHttpClient.Builder()
           .addInterceptor(new Interceptor() {
                       @Override
                       public Response intercept(Chain chain) throws IOException {
                           Request request = chain.request()
                                   .newBuilder()
                                   .addHeader("mac", "f8:00:ea:10:45")
                                   .addHeader("uuid","gdeflatfgfg5454545e")
                                   .addHeader("userId", "Fea2405144")
                                   .addHeader("netWork", "wifi")
                                   .build();
                           return chain.proceed(request);
                       }
                   }) .build()

當然可以對單一的某個API加入header

@Headers({ 
"Accept: application/vnd.github.v3.full+json", 
"User-Agent: Retrofit-your-App"})‘
@get("users/{username}")
Call<User> getUser(@Path("username") String username);

添加參數見:http://blog.csdn.net/sk719887916/article/details/52189602

3設置代理

   Proxy proxy = new Proxy(Proxy.Type.HTTP,  new InetSocketAddress(proxyHost, proxyPort));
OkHttpClient client = new OkHttpClient.Builder().proxy(proxy).build();

Retrofit.Builder builder = new Retrofit.Builder().client(client);
Retrofit retrofit = builder.build();

4 添加證書Pinning

證書可以在自定義的OkHttpClient加入certificatePinner 實現

OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(new CertificatePinner.Builder()
            .add("YOU API.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
            .add("YOU API..com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
            .add("YOU API..com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
            .add("YOU API..com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
            .build())

5 支持https

加密和普通http客戶端請求支持https一樣,步驟如下:

1 CertificateFactory 得到Context.getSocketFactory
2 添加證書源文件
3 綁定到okhttpClient
4設置okhttpClient到retrofit中
證書同樣可以設置到okhttpclient中,我們可以把證書放到raw路徑下

   SLSocketFactory sslSocketFactory =getSSLSocketFactory_Certificate(context,"BKS", R.raw.XXX);

準備證書源文件:

加入證書源文件,我的證書是放在Raw下面的:
這裏寫圖片描述

綁定證書

protected static SSLSocketFactory getSSLSocketFactory(Context context, int[] certificates) {

    if (context == null) {
        throw new NullPointerException("context == null");
    }

    CertificateFactory certificateFactory;
    try {
        certificateFactory = CertificateFactory.getInstance("X.509");
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);

        for (int i = 0; i < certificates.length; i++) {
            InputStream certificate = context.getResources().openRawResource(certificates[i]);
            keyStore.setCertificateEntry(String.valueOf(i), certificateFactory.generateCertificate(certificate));

            if (certificate != null) {
                certificate.close();
            }
        }
        SSLContext sslContext = SSLContext.getInstance("TLS");
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);
        sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
       return sslContext.getSocketFactory();  

指定支持的host

/** 
* set HostnameVerifier 
* {@link HostnameVerifier} 
*/ 
protected static HostnameVerifier getHostnameVerifier(final String[] hostUrls) {

        HostnameVerifier TRUSTED_VERIFIER = new HostnameVerifier() {

            public boolean verify(String hostname, SSLSession session) {
                boolean ret = false;
                for (String host : hostUrls) {
                    if (host.equalsIgnoreCase(hostname)) {
                        ret = true;
                    }
                }
                return ret;
            }
        };

    return TRUSTED_VERIFIER;
}

設置setSocketFactory

okhttpBuilder.socketFactory(HttpsFactroy.getSSLSocketFactory(context,   certificates));

certificates 是你raw下證書源ID, int[] certificates = {R.raw.myssl}

設置setNameVerifie

 okhttpBuilder.hostnameVerifier(HttpsFactroy.getHostnameVerifier(hosts));

hosts是你的host數據 列如 String hosts[]`= {“https//:aaaa,com”, “https//:bbb.com”}

實現自定義 添加到Retrofit

    okHttpClient = okhttpBuilder.build();

        retrofit = new Retrofit.Builder()
            .client(okHttpClient)
            .build();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章