SpringBoot のフレームワークを使った環境下で、別のサーバと http や https で通信する場合、RestTemplate のライブラリが便利です。
どの言語、フレームワークでも似たような機能は用意されていますが、やはり PHP に比べると利用するまでが比較的面倒です。
そんな中、クライアント証明書の認証を必要とするサーバにアクセスが必要になったので試してみました。
SSLのサーバ証明書がオレオレの場合
クライアント証明書の前に、サーバ証明書についても触れておきます。
ローカル環境や開発環境で https の通信をする場合、サーバ側には自己証明書を用意するケースが多いと思います。
そもそも、開発環境で正規のドメインを使うことは珍しいですし、ローカルなら localhost な場合が大半ですよね。
その場合、SSL のサーバ証明書はオレオレ証明書となるわけですが、RestTemplate はデフォルトでオレオレ証明書の認証は通しません。
https なプロトコルにアクセスすると、以下の Exception が発生します。
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
まずは、ここから突破が必要です。
オレオレ証明書を許可するか無視する
方法は 2 つあります。
1 つはキーストア(KeyStore)に自己証明書の情報を登録する方法。
もう 1 つはホスト名の検証を無効にする方法です。
プロジェクトの開発状況にもよりますが、どちらを選択するかは自分の環境に合わせて考えてみましょう。
SpringBoot では Java のセキュリティによって引っかかりますが、他の言語ではオレオレでも通してくれるものも多いですからね。
ホスト名の検証を無効にする
今回は他の開発メンバーの環境でも KeyStore を触ってもらうのは面倒だったので、プログラム側でホスト名の検証をスルーさせました。
この対応をするには、自己証明書に対応した HttpClient を RestTemplate で使う必要があるので、gradle で以下のライブラリを利用します。
implementation(‘org.apache.httpcomponents:httpclient:4.5.6’)
gradle 以外の場合は下記のページを参考にしてください。
クライアント証明書の設定と合わせて簡易的なサンプルコードを書いてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // Fileでも良かったのですが Docker コンテナでファイル参照できなかったので Url を使ってます val url = DefaultResourceLoader().getResource("classpath:xxxxxxxx.p12").url val password = "hogehoge" val timeOut = 10000 // クライアント証明書の設定と自己証明書の許可 val context = SSLContexts .custom() .loadKeyMaterial( url, password.toCharArray(), password.toCharArray() ) .loadTrustMaterial( TrustSelfSignedStrategy() ) .build() // ホスト名の検証をスルー val httpClient = HttpClients.custom() .setSSLContext(context) .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) .build() val requestFactory = HttpComponentsClientHttpRequestFactory() return RestTemplate(requestFactory).apply { (this.requestFactory as HttpComponentsClientHttpRequestFactory).httpClient = httpClient (this.requestFactory as HttpComponentsClientHttpRequestFactory).setConnectTimeout(timeOut) (this.requestFactory as HttpComponentsClientHttpRequestFactory).setReadTimeout(timeOut) } |
これで以下に対応した RestTemplate のインスタンスが作成できます。
オレオレ証明書
ホストの検証をスルー
クライアント証明書
参考にさせてもらったサイトは以下になりますが、前者のサイトでは最終的に KeyStore に登録する方がオススメされています。
SpringのRestTemplateでSSL Certificate Validationを無効にする
What is the right way to send a client certificate with every request made by the resttemplate in spring?
クライアント証明書について
クライアント証明書は jks ファイルを読み込んで使うという説明があったのですが、keytool で p12 から jks の変換を試みたところ、p12 ファイルの形式の方が新しいのでこちらが推奨されているようです。
Warning: JKSキーストアは独自の形式を使用しています。
“keytool -importkeystore -srckeystore hoge.jks -destkeystore hoge.jks -deststoretype pkcs12″を使用する業界標準の形式であるPKCS12に移行することをお薦めします。
よって以下のサイトの情報は少し古いかもしれません。
keytool の使い方は以下のサイトが参考になります。まあ、今回は不要でしたが。
オレオレ証明書の回避に失敗
ところが、とあるサーバへ接続した際に SSL の認証に失敗してしまいました。
こちらはクライアント証明書の認証ではなく、サーバ証明書の方です。
せっかく自己証明書はスルーするようにしたハズなのにどうしてでしょうか。
そんな中、以下の情報に行き当たりました。
なるほど、証明書チェーンが一つかどうかだけを判定しています。これ、オレオレ証明書の作り方によってはうまく動かないかもしれませんので注意が必要ですね。
よって、TrustSelfSignedStrategy の isTrusted() メソッドが chain.length == 1 以外の場合でも、強制的に true を返すようにしたメソッドをオーバーライドして用意します。
TrustStrategy クラスを継承したクラスを用意して、中で以下のように isTrusted() メソッドをオーバーライドして対応するのが良さそうでしょうか。
1 2 3 4 | @Throws(CertificateException::class) override fun isTrusted(chain: Array<X509Certificate>, authType: String): Boolean { return true } |
Macでクライアント証明書を使う場合の注意
Mac 初心者だからかもしれませんが、以下にハマりました。
クライアント証明書はブラウザではなくキーチェーンアクセスで管理する
クライアント証明書のパスワードが空だと登録できない
慣れない OS は時間が解決するとわかっていても、Windows と比べて不便さを感じるとイラっとしますね・・・。