通常、UIコンポーネントのWebViewは自己署名証明書を「信頼できない証明書」と判断します。その結果、httpsによる接続は拒否されます。
しかし、自己署名証明書を「信頼できる証明書」として、Android StudioのProjectへ意図的に取り込むことで、httpsによる接続が可能になります。
ただし、自己署名証明書が信頼できない点は変わりません。
ですので、この方法を用いるのはDebug時に止め、Release時は正式なCA証明書を持つWebサーバーへ、運用を移行するようにして下さい。
※環境:Android Studio Otter | 2025.2.1 Patch 1
目次
想定する開発環境
この記事で想定する開発環境は図のようなものです。

ローカルWebサーバの自己署名証明書を取り込んで、httpsによる接続を行います。
※参考記事
「Android Emulatorの仮想ネットワーク」
「ローカルWebサーバーの構築(XAMPP編)」
「ローカルWebサーバーのhttps対応(XAMPP編)」
「Android Emulatorの/etc/hosts書き換え(API≦28)」
「Android Emulatorの/etc/hosts書き換え(API>28)」
WebViewの動作
自己署名証明書を取り込んでいない状態で、httpsによる接続を行った場合の動作を見てみましょう。
接続を拒否
自己署名証明書を持つmysite.example.comに対して、httpsによる接続を試みる例です。
val _uriString = "https://mysite.example.com"
AndroidView(
factory = ::WebView,
update = { webView ->
WebView.setWebContentsDebuggingEnabled(true)
webView.settings.javaScriptEnabled = true
webView.webViewClient = WebViewClient()
webView.loadUrl(_uriString)
},
modifier = Modifier
.background(Color.Gray)
.padding(10.dp).fillMaxWidth().height(300.dp)
)
※Compose UIにWebViewに相当するコンポーネントがないため、AndroidViewによりラップする形でWebViewを組み込んでいる。
WebViewは自己署名証明書を「信頼できない証明書」と判断します。
エラーとなり、接続は拒否されます。
[ERROR:ssl_client_socket_impl.cc(992)] handshake failed; returned -1, SSL error code 1, net_error -202
エラーの場合、WebViewの表示を行う前に接続がキャンセルされるので、何も表示されません。

拒否の理由
接続のキャンセルはWebViewClient内で行われています。
WebViewClient#onReceivedSslErrorは、SSLのエラー時に呼ばれるコールバックです。
public class WebViewClient {
...
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
handler.cancel();
}
...
}
引数errorに「拒否の理由」が格納されています。以下のように継承して取り出します。
update = { webView ->
WebView.setWebContentsDebuggingEnabled(true)
webView.settings.javaScriptEnabled = true
webView.webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
Log.i(TAG, "SSL Error:${error?.description()}")
Log.i(TAG, error.toString())
handler?.cancel() // サーバーとの通信を終了
// handler?.proceed() // サーバーとの通信を続行(エラーを無視)
}
}
webView.loadUrl(_uriString)
},
fun SslError.description(): String {
val _e = primaryError
return when(_e) {
SSL_NOTYETVALID -> "(${_e}) The certificate is not yet valid"
SSL_EXPIRED -> "(${_e}) The certificate has expired"
SSL_IDMISMATCH -> "(${_e}) Hostname mismatch"
SSL_UNTRUSTED -> "(${_e}) The certificate authority is not trusted"
SSL_DATE_INVALID -> "(${_e}) The date of the certificate is invalid"
SSL_INVALID -> "(${_e}) A generic error occurred"
else -> "Unknown !"
}
}
ログは「証明機関が信頼されていない」と告げています。自己署名(自身が証明機関)だからです。
SSL Error:(3) The certificate authority is not trusted
primary error: 3 certificate: \
Issued to: \
1.2.840.113549.1.9.1=#160a53616d706c6541646472,
CN=mysite.example.com,OU=SampleSect,O=SampleCorp,L=SampleShi,ST=SampleKen,C=JP; \
Issued by: \
1.2.840.113549.1.9.1=#160a53616d706c6541646472, \
CN=mysite.example.com,OU=SampleSect,O=SampleCorp,L=SampleShi,ST=SampleKen,C=JP; \
on URL: https://mysite.example.com/
証明書の取り込み
自己署名証明書を「信頼できる証明書」として、Android StudioのProjectへ意図的に取り込みます。
以下の3つの作業が必要です。
- (1)res/rawへ証明書を配置
- (2)res/xmlへnetwork_security_configファイルを配置
- (3)AndroidManifestへエントリーを追加
※Androidのドキュメントが用意されています。必要であれば参照してください。
「ネットワーク セキュリティ構成」
(1)res/rawへ証明書を配置
res/rawフォルダを作成し、証明書ファイルを配置します。


証明書ファイルのフォーマットは、depおよびpemが利用できます。
opensslのデフォルトはpemを出力するので、ここではpemを用いました。
【server.crt】 -----BEGIN CERTIFICATE----- MIIEDDCCAvSgAwIBAgIUdo0DYPrXnA7wy/nfoP2/up20GTQwDQYJKoZIhvcNAQEL BQAwgZcxCzAJBgNVBAYTAkpQMRIwEAYDVQQIDAlTYW1wbGVLZW4xEjAQBgNVBAcM ... nT1hfM3OsdRmTHXWp7XFIbpCU3uCRaGdQ16uQ7Ge5PFQGv7vCtdAKJC+iM9LFplP SvL1QEaS3Z5h1fu6o7eCnU8XxR+r5qJNodEcLrNzk6g= -----END CERTIFICATE-----
※dep:証明書をITU-Tで定められたデータ構造で表し、シリアライズしたバイナリ
※pem:depのバイナリをBase64でテキスト化
(2)res/xmlへnetwork_security_configファイルを配置
res/xmlフォルダを作成し、network_security_configファイルを配置します。


「<domain>」に証明対象のドメイン(ホスト名)を指定します。
includeSubdomains=”true”にすると、証明対象をサブドメイの範囲まで拡大できます。ここでは”false”にしています。
また、「<certificates>」に証明書ファイル名(拡張子を省いた名前)を指定します。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="false">mysite.example.com</domain>
<!-- <domain includeSubdomains="true">example.com</domain>-->
<trust-anchors>
<certificates src="@raw/server"/>
</trust-anchors>
</domain-config>
</network-security-config>
(3)AndroidManifestへエントリーを追加
AndroidManifestへエントリーを追加します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application
...
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
...
<activity
android:name=".MainActivity"
...
</activity>
</application>
</manifest>
httpsアクセス
自己署名証明書の取り込み後、httpsによる接続を行いました。SSLのエラーは無くなり、接続が確立できています。

Debug/Release対応
自己署名証明書の取り込みはDebug時のみに止め、Release時は正式なCA証明書を持つWebサーバーへ、運用を移行するようにします。
mainフォルダと並列にdebugファルダを作成し、その下にリソースファイルを配置すると、「Build Variants:Debug/Release」の切り替えに追随して、使われるリソースファイルが切り替わります。


- Variants:Debug ⇒ debugフォルダ以下のリソースを使用
- Variants:Release ⇒ mainフォルダ以下のリソースを使用
- ※Variantsに対応するフォルダが無ければmain(デフォルト)
main側のconfigは空にして、自己署名証明書の取り込みが行われない(=正式なCA証明書を使う)ようにします。
<?xml version="1.0" encoding="utf-8"?> <network-security-config> </network-security-config>
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="false">mysite.example.com</domain>
<!-- <domain includeSubdomains="true">example.com</domain>-->
<trust-anchors>
<certificates src="@raw/server"/>
</trust-anchors>
</domain-config>
</network-security-config>
関連記事:
