追記履歴
2020/02/08 参考リンク追加
2020/02/10 非同期で使うことになった理由を追記
はじめに
クライアント証明書を使ってHTTPS通信対応をすることになった時に書いたコードを会社を辞めてソースコードが見れなくなった時用にメモ代わりとして残しておく
何故Okhttpにこだわるかというと、大昔に苦しめられたもんだから大嫌いになったんですよねHttp(s)URLConnectionが
一部書き方が雑な部分や現役バリバリな人には釈迦に説法のような部分もあるかもですがご容赦をいt・・・ボク用のメモだから別にいっか!
事前準備
1.main配下(main、resと同じ階層)に「assets」フォルダを作成
2.assetsフォルダ内にpfx証明書を格納(今回は便宜上「hogehage.pfx」とするヨ)
3.「AndroidManifest.xml」にインターネット通信のパーミッションを追加(↓こんな感じ)
追加場所はmanifest間なら問題ないが、activityの間には書くな
<manifest ・・・
...
//インターネット接続用のパーミッション
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
4.「build.gradle(app配下の方)」の「dependencies」部分に以下を追加
バージョンは2020/02/05のもの
最新かどうかは https://square.github.io/okhttp/ の「Releases」部分を確認して
・・・
dependencies {
・・・
implementation("com.squareup.okhttp3:okhttp:4.3.1")
1と2はHTTPS通信用、3と4はOkhttp用の記述
もうここまでで書くの面倒くさくなってきた・・・FE風花雪〇やりたい
コード
メインスレッドではなくAsynctask・・・俗にいう非同期処理で書いてる
前まではメインスレッド(MainActivityとかとか)で書いてたが・・・あれ?なんで変えたんだっけ?
→元々はAsynctaskでHttpurlconnectionを使っていたため。試しに非同期でokhttpを使ってみたらできたし、アプリの動作が見せかけだが軽くなったのでそのまま採用した(追記)
package com.example.test;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
//非同期処理ってやつ
//HTTPS通信を行うためのとこ
//呼び出し元では画像をバイト配列に変換して、このactに渡してた
public class AsyncHttps extends AsyncTask<byte[], Void, String>
{
private Context mContext;
public AsyncHttps(Context context)
{mContext = context;}
public static String res=null;
final Handler handler = new Handler();
//URLsListの配列番号用
public static int url_id = 0;
//通信結果格納変数
public static String result = null;
// 非同期処理
@Override
protected String doInBackground(byte[]... params)
{
//url一覧を別のactに配列で書き、送信元で配列番号を送ってやればいいことに気が付いた
String urlSt = URLsList.URL[url_id];
byte[] word = params[0];
//
return result;
}
//http通信用
public void http_post(byte[] bytes)
{
//JSONを使用して送信するための設定らしい
//MediaTypeは送る物に合わせて変更が必要(画像なら"image/jpg"みたいな感じ)
MediaType mediaType= MediaType.parse("application/json; charset=utf-8");
//Request Body作成。送信する物
RequestBody requestBody = RequestBody.create(mediaType,bytes);
//リクエストを作成、郵便物の梱包みたいなもんでしょ(テキトー)
Request request = new Request.Builder()
//送り先
.url(URLsList.URL[url_id])
.post(requestBody) //送る中身
.build(); //これらをビルドアップする
KeyManagerFactory keyManagerFactory;
//証明書作成したときに設定したパスワード
final char[] PASSWORD = "***ここにパスワード***".toCharArray();
InputStream inputStream;
TrustManagerFactory trustManagerFactory;
SSLSocketFactory sslSocketFactory;
X509TrustManager trustManager;
try
{
//クライアント証明書ファイルの指定(assetsフォルダに配置しとけ)
inputStream = mContext.getResources().getAssets().open("hogehage.pfx");
//参考にしたとこの拡張子はp12だったからそっちも行けんじゃない?
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//ここから下は何も考えずコピペでOK
keyStore.load(inputStream,PASSWORD);
trustManagerFactory = TrustManagerFactory.getInstance
(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager))
{
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
trustManager = (X509TrustManager)trustManagers[0];
keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore,PASSWORD);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(),null,null);
sslSocketFactory = sslContext.getSocketFactory();
//コピペここまで
final OkHttpClient client = new OkHttpClient.Builder()
//.connectTimeout(10, TimeUnit.SECONDS) //タイムアウト設定3銃士
//.readTimeout(10, TimeUnit.SECONDS) //何を設定しているかは知らん
//.writeTimeout(10, TimeUnit.SECONDS) //あとで見といて
.sslSocketFactory(sslSocketFactory,trustManager)
.build();
client.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(@NonNull Call call, IOException e)
{
//例外発生時の処理は別処理にすると安定するらしい。
failMessage();
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException
{
result = String.valueOf(response);
//何か返ってくる場合はここで何かしたりしなかったり
client.connectionPool().evictAll();
}
});
}catch (IOException e)//こっから下は何か爆発した時Errorタブのログに出る 特に触るとこはない
{Log.e("エラー内容:", String.valueOf(e));}
catch (NoSuchAlgorithmException e)
{Log.e("エラー内容:", String.valueOf(e));}
catch (CertificateException e)
{Log.e("エラー内容:", String.valueOf(e));}
catch (UnrecoverableKeyException e)
{Log.e("エラー内容:", String.valueOf(e));}
catch (KeyStoreException e)
{Log.e("エラー内容:", String.valueOf(e));}
catch (KeyManagementException e)
{Log.e("エラー内容:", String.valueOf(e));}
}
//例外発生時(何かしらエラった時)の処理
private void failMessage()
{Log.d("通信結果:","ダメでした…");}
}
参考
Okhttp公式:
https://square.github.io/okhttp/
Android アプリ内でのクライアント証明書認証:
https://qiita.com/c_ume/items/d082ffd20b3316aab805
Now that SSLSocketFactory is deprecated on Android, what would be the best way to handle Client Certificate Authentication?
https://stackoverflow.com/questions/31002159/now-that-sslsocketfactory-is-deprecated-on-android-what-would-be-the-best-way-t