この記事は Java Advent Calendar 2025 の18日目の記事です。
はじめに
本記事は、Spring Framework を採用した Web システムにおいて遭遇した、SSL/TLS 設定に起因する特定クライアントでのログインエラー に関するトラブルシューティングの記録です。調査の結果、Spring Boot に組み込まれている Netty 内部でNPEが発生していることを特定し、それを解消することでログインエラーを解決しました。
今回の事象で最も厄介だったのは、技術的な原因そのものよりも 「本番環境の一部のPCでのみ発生し、検証環境では当該PCでも再現しない」 という再現性の欠如にありました。
現在、設定変更によりエラー自体は解消していますが、「なぜ検証環境で再現させられなかったのか」という点については課題を残しています。必要があれば適切なコミュニティへバグ報告を行いたいところですが、根本原因がアプリケーション(Spring/Java/Netty)、クライアントOS、あるいはネットワーク機器のどこにあるのか断定しきれていません。そのため、「Issue を起票したいが、真因が不明確なため、そもそもどのリポジトリに投げればよいのかすら判断がつかない」 というのが正直なところです。
本記事では、解決策の共有だけでなく、そこに至るまでの調査プロセス(パケット解析やログ調査など)を詳細に残すことで、複雑なネットワークトラブルに挑む際の一助となることを目的としています。
この記事で共有すること
- 事象と解決策: SSL/TLS 周りの設定不整合によるログインエラーと、その具体的な対処法
- 調査アプローチ: エラーログが出ない状況から、どのように原因箇所(SSL層)を特定したかの調査ログ
ヒント募集
可能でしたら、検証環境で状況を再現させるためのトライする価値のあることが思いついた方はコメントなどいただけると幸いです。
本番環境での設定変更、試行錯誤が難しいので検証環境で状況を再現させたいのです。
最小限の問題の説明と解決策
Spring cloud Gateway 及び Spring Security を利用した Gateway サーバーを経由して本番アプリケーションを利用するWebシステム環境を表しています。認証は、認証基盤である Keycloak を利用している環境です。
この環境では、ログイン時にエラーが出る端末と出ない端末が、それぞれ複数台ありました。
Gateway サーバー 及び Keycloak は SSL/TLS 対応しており、証明書はSSLサーバー証明書発行サービス(有料)を利用(ワイルドカード証明書を発行)し、サーバー間通信の際にエラーが出たので、SSLサーバー証明書発行サービスが発行しているルート証明書と中間証明書をサーバープログラムが使用している Java に import しています。
社内イントラネット内で動いています。
問題の環境(最小構成)
Keycloak(本番)
- URL:
https://prod.[mydomain]:7063- 証明書はSSLサーバー証明書発行サービスを利用(ワイルドカード証明書を発行)
- Version 25.0.1
Keycloak(本番) 詳細
- Java:
- openjdk version "21.0.3" 2024-04-16 LTS
- OpenJDK Runtime Environment Temurin-21.0.3+9 (build 21.0.3+9-LTS)
- OpenJDK 64-Bit Server VM Temurin-21.0.3+9 (build 21.0.3+9-LTS, mixed mode, sharing)
- この環境で使用している SSL/TLS証明書の ルート証明書がなかったので keytool で 中間証明書とルート証明書を追加している
Gateway(本番)
- URL:
https://prod.[mydomain]:7070 - 主な利用ライブラリとバージョン
- Spring Boot Version 3.5.6
- Spring dependency management Version 1.1.7
- ※ 以下のライブラリのバージョンは Spring dependency management 依存
- Spring Security Version 3.5.6
- Spring cloud Reactive Gateway Version 6.5.5
- netty Version 4.1.127
Gateway(本番) 詳細
- Java: Keycloak(本番) と同じ Java を使用
MyApplication(本番)
- URL:
http://localhost:8080- Gateway による Reverse Proxy でアクセス
- 主な利用ライブラリ
- Spring Boot (本件、問題に県警内と判断して詳細省略)
本番サーバー
- OS Windows
- Microsoft Azure 上の 仮想マシン
クライアントPC-A 及び クライアントPC-B
- OS Windows 10 or Windows 11
- Browser Google Chrome / Microsoft Edge / Mozilla Firefox など
- エラーが起きる端末は ブラウザを変更してもエラーが出る(含む Google Chrome の シークレットモード)
問題の事象
- ユーザーがクライアントPC-A のブラウザで
https://prod.[mydomain]:7070にアクセス。トップページが表示される - ユーザーがトップページで「ログインする」のボタンを押す。Keycloak で設定したログイン画面がが表示される。
- ユーザーがユーザーIDとパスワードを入力してログインボタンを押すとエラーページが表示される ※問題※
- ユーザーが再度
https://prod.[mydomain]:7070にアクセスすると MyApplication(本番) で作成した画面が表示される(認証が成功してログインができる)
問題の原因かもしれないと思われる事象:Gatewayサーバーで NullPointerException
問題が発生するケースでは Gateway(本番) のサーバーログに以下のエラーログが残されていました。
2025-09-25T20:29:26.483+09:00 DEBUG 31936 --- [gateway-sso-proxy] [ctor-http-nio-5] i.n.handler.ssl.SslClientHelloHandler : Unexpected client hello packet:(略)
java.lang.NullPointerException: Cannot invoke "reactor.netty.tcp.SslProvider.getSslContext()" because "sslProvider" is null
at reactor.netty.tcp.SniProvider$SniHandler.onLookupComplete(SniProvider.java:126) ~[reactor-netty-core-1.1.19.jar!/:1.1.19]
at io.netty.handler.ssl.AbstractSniHandler.onLookupComplete(AbstractSniHandler.java:191) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslClientHelloHandler.select(SslClientHelloHandler.java:222) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslClientHelloHandler.decode(SslClientHelloHandler.java:178) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
該当箇所のソースは以下のようになっていました。 future.getNow() が null なのが想定外のようです。
package reactor.netty.tcp;
//(略)
final class SniProvider {
//(略)
static final class SniHandler extends AbstractSniHandler<SslProvider> {
//(略)
@Override
protected void onLookupComplete(ChannelHandlerContext ctx, String hostname, Future<SslProvider> future) {
//(略)
SslProvider sslProvider = future.getNow();
SslHandler sslHandler = null;
try {
// ↓で NullPointerException "sslProvider" is null
sslHandler = sslProvider.getSslContext().newHandler(ctx.alloc());
sslProvider.configure(sslHandler);
// (略)
Gateway(本番) で、NullPointerException が起きているから Keycloak(本番) へ トークン をリクエストするときの通信に問題があると予想しています。
但し、この通信がクライアント毎に挙動が変わる理由は思いついていません(課題)。
また、ログイン時にエラーが出る端末でエラーページが表示された後にアクセスした場合にはログインが成功する理由も不明です。
ご参考:ここをクリックすると、ログイン時のシーケンス図を確認できます
ログイン情報がない場合のログイン時のシーケンス図
問題が発生するクライアントからのアクセスの場合 トークンリクエスト (POST /token) でエラーが起きる。
ログイン情報がある場合のログイン時のシーケンス図
問題が発生するクライアントからのアクセスの場合 トークンリクエスト (POST /token) でエラーが起きそうだが正常に処理が続行される。
成功した回避策
Gateway(本番) のプログラムにて Spring Boot の 組込WebServer のカスタマイズ機構である WebServerFactoryCustomizer を利用して、
自分でビルドした SSLContext を利用するようにしました。
この設定を追加することでログイン時にエラーページが表示されることはなくなりました。
// (packeage 宣言 及び import 省略)
@Configuration
@Slf4j
public class ForceSslContextConfiguration implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
@Value("${server.ssl.key-store:}")
private String keyStore;
@Value("${server.ssl.key-store-password:}")
private String keyStorePassword;
@Value("${server.ssl.key-store-type:}")
private String keyStoreType;
@Override
public void customize(NettyReactiveWebServerFactory factory) {
// 使用している変数の null チェック や フィーチャーフラグ用の条件分岐などは省略しています
factory.addServerCustomizers(new NettyServerCustomizer() {
@Override
public HttpServer apply(HttpServer httpServer) {
try {
SslContext context = buildContext(
keyStore, keyStorePassword, keyStoreType
);
return httpServer.secure(ssl -> ssl.sslContext(context));
} catch (Exception e) {
throw new RuntimeException("Failed to forceSslContextConfiguration.", e);
}
}
});
}
public SslContext buildContext(String keyStorePath, String password, String keyStoreType) throws Exception {
File keyStoreFile = new File(keyStorePath);
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
char[] passwordChars = (password == null ) ? null : password.toCharArray();
try (FileInputStream fis = new FileInputStream(keyStoreFile)) {
keyStore.load(fis, passwordChars);
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, passwordChars);
return SslContextBuilder.forServer(kmf).build();
}
}
Netty の SniProvider がホスト名を解決しようとした際に、何らかの理由で SSL コンテキストの取得に失敗していたが、WebServerFactoryCustomizer で固定の SSL コンテキストを割り当てることで、SNI 判定をスキップ(あるいはデフォルト化)させたため解消したと考えています。
問題詳細
問題の環境(詳細)
検証サーバーを構築しましたが、本番サーバー利用時に問題が発生するクライアントPCで検証サーバー上の同環境にアクセスしても問題は発生しません。
問題が起きるクライアントからのログインは必ず問題が発生します。
| サーバー環境 | クライアント端末 | 問題発生 | 補足 |
|---|---|---|---|
| 本番環境 | クライアントPC-A | ❌ 問題発生 | クライアント端末複数で発生。Google Chrome の シークレットウィンドウを使っても問題発生 |
| 本番環境 | クライアントPC-B | 問題なし | |
| 本番環境 | 本番サーバ | 問題なし | |
| 検証環境 | クライアントPC-A | 問題なし | |
| 検証環境 | クライアントPC-B | 問題なし |
上記の内容にから 問題はクライアント、サーバーの両方(組み合わせ)にある と予想できます。
SSL/TLS証明書の設定
問題となっている通信が Gateway(本番) から Keycloak(本番) を呼び出すときの通信なので、以下の2点の情報を示します。
- Keycloak(本番) の keystore の設定(私が誰かを証明するための設定)
- Gateway(本番) の truststore の設定(相手を信頼するための設定)
Keycloak(本番) の keystore の設定
Keycloak には pkcs12ファイルの keystore を設定しました。
https-key-store-file=C:/xxx/xxxx/xxxx/xxxx.pfx
https-key-store-password=xxxx
https-port=7063
※ 説明の都合上 conf ファイルに https-key-store-password を設定(記載)していますが、認証情報の保護された形式で設定しています。詳細は 公式ドキュメント をご覧ください。
Gateway(本番) の truststore の設定
Keycloak に SSL 証明書を設定するだけだと Gateway から Keycloak に通信する時に sun.security.validator.ValidatorException: PKIX path building failed のエラーが出てしまいました。
これは、Java が保持している truststore に Keycloak が使用する証明書に対応する情報が不足している時に出るエラーです。
これを解消するためには Keycloak が使用している証明書を発行している機関が発行している ルート証明書 及び 中間証明書 をインポートする必要があります。
以下のスクリプトで 作成しました。
setlocal
pushd "%~dp0"
@rem 設定項目1: 使用する Java のディレクトリ
set JAVA_DIR=..\..\java\
@rem 設定項目2: import する 中間証明書 ルート証明書が格納されているフォルダ
set CHAIN_OF_TRUST_DIR=..\_root_intermediate_cert\
@rem 設定項目3: 出力する truststore のファイル名
set TRUST_STORE_PATH=..\store\truststore.jks
@rem Java ディレクトリ内にある keytool を探します
echo Searching for keytool.exe in: %JAVA_DIR%
call :FindFile "%JAVA_DIR%" "keytool.exe" "KEYTOOL_PATH"
if defined KEYTOOL_PATH (
echo Found keytool.exe at: %KEYTOOL_PATH%
) else (
echo Error: keytool.exe not found
goto :normal_end
)
@rem Java のデフォルト truststore を探します
echo Searching for Java Default Truststore in: %JAVA_DIR%
call :FindFile "%JAVA_DIR%" "cacerts" "JAVA_DEFAULT_TRUST_STORE_PATH"
if defined JAVA_DEFAULT_TRUST_STORE_PATH (
echo Found cacerts at: %JAVA_DEFAULT_TRUST_STORE_PATH%
) else (
echo Error: cacerts not found
goto :normal_end
)
@rem 使用したい ルート証明書 中間証明書 を含んだ truststore を作ります
@echo Create truststore
@rem 既に 出力するファイルがあったら削除します
if exist %TRUST_STORE_PATH% (
del %TRUST_STORE_PATH%
)
@rem デフォルトの truststore の内容をコピーします
%KEYTOOL_PATH% ^
-importkeystore ^
-srckeystore %JAVA_DEFAULT_TRUST_STORE_PATH% ^
-destkeystore %TRUST_STORE_PATH% ^
-deststoretype JKS ^
-srcstorepass changeit ^
-deststorepass changeit
@rem ↑でコピーしたファイルに ルート証明書 中間証明書を追加します(拡張子 .crt のファイルをimport)
for /r "%CHAIN_OF_TRUST_DIR%" %%f in ( "*.crt" ) do (
%KEYTOOL_PATH% ^
-importcert ^
-alias __%%~nf ^
-file "%%~f" ^
-keystore %TRUST_STORE_PATH% ^
-storepass changeit ^
-noprompt
)
goto :normal_end
:FindFile
echo off
set "SEARCH_DIR=%~1"
set "FILE_NAME=%~2"
set "RESULT_VAR=%~3"
set "%RESULT_VAR%="
if not exist "%SEARCH_DIR%" (
echo Error: Search directory does not exist: %SEARCH_DIR%
goto :FindFileEnd
)
for /r "%SEARCH_DIR%" %%f in ( "*.*" ) do (
if "%%~nxf"=="%FILE_NAME%" (
set "%RESULT_VAR%=%%~f"
goto :FindFileEnd
)
)
rem File not found: %FILE_NAME% in %SEARCH_DIR%
echo on
:FindFileEnd
@goto :eof
:normal_end
@popd
@endlocal
上記で作成した truststore を Gateway を起動するときに Java の起動引数で指定しました。
-Djavax.net.ssl.trustStore=%APPLICATION_HOME%ssl\store\truststore.jks -Djavax.net.ssl.trustStorePassword=changeit"
出ているログ詳細
前述の java.lang.NullPointerException: Cannot invoke "reactor.netty.tcp.SslProvider.getSslContext()" because "sslProvider" is null 以外に javax.net.ssl.SSLHandshakeException: no cipher suites in common というログも出ている
20xx-xx-xxT19:12:28.970+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] r.netty.transport.ServerTransport : [2f1682f7, L:/10.0.0.5:7070 ! R:/128.14.239.62:41430] onUncaughtException(SimpleConnection{channel=[id: 0x2f1682f7, L:/10.0.0.5:7070 ! R:/128.14.239.62:41430]})
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: no cipher suites in common
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:500) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
(略)
Caused by: javax.net.ssl.SSLHandshakeException: no cipher suites in common
at java.base/sun.security.ssl.Alert.createSSLException(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.Alert.createSSLException(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(Unknown Source) ~[na:na]
(略)
また、どちらの例外のログも ログレベルが DEBUG であることも気になっているポイントではあります(=発生しても問題ない例外なのではないか?)。
ご参考:ここをクリックすると、全文が確認できます
20xx-xx-xxT19:12:22.318+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] r.n.http.server.HttpServerOperations : [7c8bda58, L:/10.0.0.5:7070 - R:/128.14.239.62:41398] New http connection, requesting read
20xx-xx-xxT19:12:22.318+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] r.netty.transport.TransportConfig : [7c8bda58, L:/10.0.0.5:7070 - R:/128.14.239.62:41398] Initialized pipeline DefaultChannelPipeline{(reactor.left.sslHandler = reactor.netty.tcp.SniProvider$SniHandler), (reactor.left.sslReader = reactor.netty.tcp.SslProvider$SslReadHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpServerCodec), (reactor.left.httpTrafficHandler = reactor.netty.http.server.HttpTrafficHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
20xx-xx-xxT19:12:22.521+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] jdk.event.security : TLSHandshake: null:-1, TLSv1.3, TLS_AES_128_GCM_SHA256, 0
20xx-xx-xxT19:12:22.521+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] io.netty.handler.ssl.SslHandler : [id: 0x7c8bda58, L:/10.0.0.5:7070 - R:/128.14.239.62:41398] HANDSHAKEN: protocol:TLSv1.3 cipher suite:TLS_AES_128_GCM_SHA256
20xx-xx-xxT19:12:27.531+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] r.n.channel.ChannelOperationsHandler : [7c8bda58, L:/10.0.0.5:7070 - R:/128.14.239.62:41398] Received a TLS close_notify, closing the channel now.
20xx-xx-xxT19:12:28.970+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] r.n.http.server.HttpServerOperations : [2f1682f7, L:/10.0.0.5:7070 - R:/128.14.239.62:41430] New http connection, requesting read
20xx-xx-xxT19:12:28.970+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] r.netty.transport.TransportConfig : [2f1682f7, L:/10.0.0.5:7070 - R:/128.14.239.62:41430] Initialized pipeline DefaultChannelPipeline{(reactor.left.sslHandler = reactor.netty.tcp.SniProvider$SniHandler), (reactor.left.sslReader = reactor.netty.tcp.SslProvider$SslReadHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpServerCodec), (reactor.left.httpTrafficHandler = reactor.netty.http.server.HttpTrafficHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
20xx-xx-xxT19:12:28.970+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] i.n.handler.ssl.SslClientHelloHandler : Unexpected client hello packet: 160303015701000153030391ac714edc882158812eeaf58a9d9287096dc76bcc1e851cc5117c6cf09e5879203cbb996db09542dad792870f133bf3e3ede5690134619f4b249364fde763b1c50046c012c007cc1413011302cca9c073c072c02cc0afc0adc024c00ac02bc0aec0acc023c009c008009a00c4008800be0045009fc0a3c09f006b0039009ec0a2c09e006700330016010000c400000012001000000d31332e38302e3134382e313530001700000001000101ff01000100000a000a0008001d001700180019000b00020100002300000010003c003a08687474702f302e3908687474702f312e3008687474702f312e3106737064792f3106737064792f3206737064792f3302683203683263026871000d00140012040308040401050308050501080606010201003300260024001d002071ba03cb9ef4a4274539836737c5b62884c1610d7e3e90ca6d83eac73aefcf94002d00020101
java.lang.NullPointerException: Cannot invoke "reactor.netty.tcp.SslProvider.getSslContext()" because "sslProvider" is null
at reactor.netty.tcp.SniProvider$SniHandler.onLookupComplete(SniProvider.java:126) ~[reactor-netty-core-1.1.19.jar!/:1.1.19]
at io.netty.handler.ssl.AbstractSniHandler.onLookupComplete(AbstractSniHandler.java:191) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslClientHelloHandler.select(SslClientHelloHandler.java:222) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslClientHelloHandler.decode(SslClientHelloHandler.java:178) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
20xx-xx-xxT19:12:28.970+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] r.netty.transport.ServerTransport : [2f1682f7, L:/10.0.0.5:7070 - R:/128.14.239.62:41430] onUncaughtException(SimpleConnection{channel=[id: 0x2f1682f7, L:/10.0.0.5:7070 - R:/128.14.239.62:41430]})
javax.net.ssl.SSLHandshakeException: no cipher suites in common
at java.base/sun.security.ssl.Alert.createSSLException(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.Alert.createSSLException(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ServerHello$T12ServerHelloProducer.chooseCipherSuite(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ServerHello$T12ServerHelloProducer.produce(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLHandshake.produce(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ClientHello$T12ClientHelloConsumer.consume(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.onClientHello(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.consume(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLHandshake.consume(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.HandshakeContext.dispatch(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(Unknown Source) ~[na:na]
at java.base/java.security.AccessController.doPrivileged(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(Unknown Source) ~[na:na]
at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1651) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1497) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1338) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1387) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.handlerRemoved(ByteToMessageDecoder.java:266) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:537) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
20xx-xx-xxT19:12:28.970+02:00 DEBUG 14232 --- [gateway-sso-proxy] [ctor-http-nio-4] r.netty.transport.ServerTransport : [2f1682f7, L:/10.0.0.5:7070 ! R:/128.14.239.62:41430] onUncaughtException(SimpleConnection{channel=[id: 0x2f1682f7, L:/10.0.0.5:7070 ! R:/128.14.239.62:41430]})
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: no cipher suites in common
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:500) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.handlerRemoved(ByteToMessageDecoder.java:266) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:537) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.110.Final.jar!/:4.1.110.Final]
at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
Caused by: javax.net.ssl.SSLHandshakeException: no cipher suites in common
at java.base/sun.security.ssl.Alert.createSSLException(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.Alert.createSSLException(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ServerHello$T12ServerHelloProducer.chooseCipherSuite(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ServerHello$T12ServerHelloProducer.produce(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLHandshake.produce(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ClientHello$T12ClientHelloConsumer.consume(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.onClientHello(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.consume(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLHandshake.consume(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.HandshakeContext.dispatch(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(Unknown Source) ~[na:na]
at java.base/java.security.AccessController.doPrivileged(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(Unknown Source) ~[na:na]
at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1651) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1497) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1338) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1387) ~[netty-handler-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.110.Final.jar!/:4.1.110.Final]
... 24 common frames omitted
まとめ
今回のトラブルシューティングの要点は以下の通りです。
-
事象: Spring Cloud Gateway (Netty) 環境下で、一部のクライアントからのアクセス時に
SslProvider is null(NPE) が発生し、ログインできない。 -
原因: Netty の SNI ハンドラ (
SniHandler) が SSL コンテキストを動的に解決する際、特定の条件下で参照が取得できずに落ちていたと推測される。 -
対策:
WebServerFactoryCustomizerを実装し、アプリケーション起動時にビルドしたSslContextを Netty サーバーに明示的に割り当てる(固定する)ことで解消。
同様のエラーログ (reactor.netty.tcp.SslProvider.getSslContext() because "sslProvider" is null) に遭遇された場合は、証明書設定の見直しと合わせて、今回紹介した「SSLコンテキストの明示的な指定」を試してみる価値があるかと思います。
もし、検証環境での再現方法や、より深い根本原因について知見をお持ちの方がいらっしゃれば、コメント等で教えていただけると大変助かります。
おまけ
調査中に見つけた、本件に関係ありそうな情報を共有しておきます。
類似のエラーの報告
以下の類似事例を確認しました:
-
How to solve intermediate authorization request not found error with Spring Cloud Gateway and Keycloak
- 同じ点: 使用しているコンポーネントが同じ(Spring Cloud Gateway + Keycloak)。リトライすると正常動作する
-
異なる点: 発生しているエラーは異なる(
intermediate authorization request not foundvsInvalid credentials)
-Djavax.net.debug=ssl → SSLに関するデバッグログ有効化
Java の起動引数に -Djavax.net.debug=ssl を追加すると SSL 関連のログが出力されるようになります。
残念ながら今回の件に有益な情報は見つけられませんでした。
javax.net.ssl|DEBUG|10|main|2025-10-01 20:33:19.823 JST|SSLCipher.java:432|jdk.tls.keyLimits: entry = AES/GCM/NoPadding KeyUpdate 2^37. AES/GCM/NOPADDING:KEYUPDATE = 137438953472
javax.net.ssl|DEBUG|10|main|2025-10-01 20:33:19.827 JST|SSLCipher.java:432|jdk.tls.keyLimits: entry = ChaCha20-Poly1305 KeyUpdate 2^37. CHACHA20-POLY1305:KEYUPDATE = 137438953472
javax.net.ssl|DEBUG|10|main|2025-10-01 20:33:21.421 JST|Utilities.java:74|the previous server name in SNI (type=host_name (0), value=xxx.xxxx.com) was replaced with (type=host_name (0), value=xxx.xxxx.com)
javax.net.ssl|DEBUG|10|main|2025-10-01 20:33:21.598 JST|SSLCipher.java:1836|KeyLimit read side: algorithm = AES/GCM/NoPadding:KEYUPDATE
countdown value = 137438953472
javax.net.ssl|DEBUG|10|main|2025-10-01 20:33:21.601 JST|SSLCipher.java:1987|KeyLimit write side: algorithm = AES/GCM/NoPadding:KEYUPDATE
countdown value = 137438953472
javax.net.ssl|DEBUG|10|main|2025-10-01 20:33:21.742 JST|SSLCipher.java:1836|KeyLimit read side: algorithm = AES/GCM/NoPadding:KEYUPDATE
countdown value = 137438953472
javax.net.ssl|DEBUG|10|main|2025-10-01 20:33:21.745 JST|SSLCipher.java:1987|KeyLimit write side: algorithm = AES/GCM/NoPadding:KEYUPDATE
countdown value = 137438953472
javax.net.ssl|DEBUG|82|Keep-Alive-Timer|2025-10-01 20:33:26.871 JST|SSLSocketImpl.java:577|duplex close of SSLSocket
javax.net.ssl|DEBUG|82|Keep-Alive-Timer|2025-10-01 20:33:26.876 JST|SSLSocketImpl.java:1775|close the SSL connection (passive)