Reputation: 12292
Please read the "Update" at the bottom. It seems to be a problem with the name of the controllers. Depending on the name the SSL-configuration is not applied.
The UserService
of my spring application connects to an external server that uses a self-signed cert in development. I added the self-signed and valid cert to a java key store ./dev-truststore.jks
and use this @Configuration
:
@Profile("development")
@Configuration
class SSLConfigDev {
@Suppress("unused")
@PostConstruct
private fun configureSSL() {
System.setProperty("javax.net.ssl.trustStore", "./dev-truststore.jks")
System.setProperty("javax.net.ssl.trustStorePassword", "secret")
}
}
This is the reduced UserServiceImpl
that still shows the error:
import org.keycloak.admin.client.Keycloak
// ...
@Service
class UserServiceFeatureImpl(
private val userRepository: UserRepository,
private val kc: Keycloak,
) : UserService {
override suspend fun createUser(email: String, pw: String) {
try {
val realmResources = kc.realm("myRealm")
val usersResource = realmResources.users()
// The next line works fine until I define the new TaskService as
// described below. Then it fails with the SSL exception shown below.
val existingUsers = usersResource.search(email, 0, 1)
// ...
} catch (e: Exception) {
throw CreateAccountException("Could not create account", e)
}
}
}
And this is the route for creating users:
@RestController
class UserController(
private val userService: UserService,
) {
@PostMapping("/users/new")
@ResponseStatus(HttpStatus.CREATED)
suspend fun create(@RequestBody @Valid dto: CredentialsDTO) {
userService.createUser(dto.email, dto.password)
}
}
This is the bean creation for Keycloak
:
@Configuration
@ConfigurationProperties("keycloak")
class KeycloakConfig{
lateinit var serverUrl: String
lateinit var realm: String
lateinit var clientId: String
lateinit var clientSecret: String
lateinit var frontEndClientId: String
@Bean
fun init(): Keycloak {
return KeycloakBuilder.builder()
.serverUrl(serverUrl)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.realm(realm)
.clientId(clientId)
.clientSecret(clientSecret)
.resteasyClient(
ResteasyClientBuilder().connectionPoolSize(10).build()
)
.build()
}
}
This works fine.
Now, I was developing a new unrelated feature (controller/service/repository) and suddenly the create-user route stops working, because it cannot connect to the keycloak service anymore because it fails to perform the SSL handshake. Note that the create-user action is not related to anything in the new service/feature and has not been modified.
javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
The full stack trace is:
2021-06-25 18:04:19.050 ERROR 7821 --- [or-http-epoll-3] a.w.r.e.AbstractErrorWebExceptionHandler : [755b0875-1] 500 Server Error for HTTP POST "/users/new"
com.example.backend.user.CreateAccountException: Could not create account
at com.example.backend.user.UserServiceFeatureImpl.createUser$suspendImpl(UserServiceFeatureImpl.kt:278) ~[main/:na]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Handler com.example.backend.user.UserController#create(CredentialsDTO, Continuation) [DispatcherHandler]
|_ checkpoint ⇢ com.example.backend.core.ReactorContextLocaleWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP POST "/users/new" [ExceptionHandlingWebHandler]
Stack trace:
at com.example.backend.user.UserServiceFeatureImpl.createUser$suspendImpl(UserServiceFeatureImpl.kt:278) ~[main/:na]
at com.example.backend.user.UserServiceFeatureImpl.createUser(UserServiceFeatureImpl.kt) ~[main/:na]
at com.example.backend.user.UserServiceFeatureImpl$$FastClassBySpringCGLIB$$ed9e17ac.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.3.7.jar:5.3.7]
at com.example.backend.user.UserServiceFeatureImpl$$EnhancerBySpringCGLIB$$2ccf6d25.createUser(<generated>) ~[main/:na]
at com.example.backend.user.UserServicePermissionsImpl.createUser$suspendImpl(UserServicePermissionsImpl.kt:47) ~[main/:na]
at com.example.backend.user.UserServicePermissionsImpl.createUser(UserServicePermissionsImpl.kt) ~[main/:na]
at com.example.backend.user.UserServicePermissionsImpl$$FastClassBySpringCGLIB$$d7f96f3a.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.3.7.jar:5.3.7]
at com.example.backend.user.UserServicePermissionsImpl$$EnhancerBySpringCGLIB$$fb5d3241.createUser(<generated>) ~[main/:na]
at com.example.backend.user.UserController.create$suspendImpl(UserController.kt:116) ~[main/:na]
at com.example.backend.user.UserController.create(UserController.kt) ~[main/:na]
at com.example.backend.user.UserController$$FastClassBySpringCGLIB$$d1552207.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.3.7.jar:5.3.7]
at com.example.backend.user.UserController$$EnhancerBySpringCGLIB$$f0dbf2c8.create(<generated>) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97) ~[kotlin-reflect-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Instance.call(CallerImpl.kt:113) ~[kotlin-reflect-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108) ~[kotlin-reflect-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at kotlin.reflect.full.KCallables.callSuspend(KCallables.kt:55) ~[kotlin-reflect-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at org.springframework.core.CoroutinesUtils$invokeSuspendingFunction$mono$1.invokeSuspend(CoroutinesUtils.kt:64) ~[spring-core-5.3.7.jar:5.3.7]
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[kotlin-stdlib-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:377) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.reactor.MonoKt.monoInternal$lambda-2(Mono.kt:90) ~[kotlinx-coroutines-reactor-1.5.0.jar:na]
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:57) ~[reactor-core-3.4.6.jar:3.4.6]
// ...
at reactor.netty.channel.FluxReceive.terminateReceiver(FluxReceive.java:469) ~[reactor-netty-core-1.0.7.jar:1.0.7]
at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:261) ~[reactor-netty-core-1.0.7.jar:1.0.7]
at reactor.netty.channel.FluxReceive.request(FluxReceive.java:130) ~[reactor-netty-core-1.0.7.jar:1.0.7]
at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:162) ~[reactor-core-3.4.6.jar:3.4.6]
at reactor.core.publisher.FluxPeek$PeekSubscriber.request(FluxPeek.java:137) ~[reactor-core-3.4.6.jar:3.4.6]
at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:162) ~[reactor-core-3.4.6.jar:3.4.6]
at reactor.core.publisher.MonoCollect$CollectSubscriber.onSubscribe(MonoCollect.java:103) ~[reactor-core-3.4.6.jar:3.4.6]
at reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:92) ~[reactor-core-3.4.6.jar:3.4.6]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onSubscribe(FluxPeek.java:170) ~[reactor-core-3.4.6.jar:3.4.6]
at reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:92) ~[reactor-core-3.4.6.jar:3.4.6]
at reactor.netty.channel.FluxReceive.startReceiver(FluxReceive.java:168) ~[reactor-netty-core-1.0.7.jar:1.0.7]
at reactor.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:147) ~[reactor-netty-core-1.0.7.jar:1.0.7]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:384) ~[netty-transport-native-epoll-4.1.65.Final-linux-x86_64.jar:4.1.65.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
Caused by: javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine.invoke(ApacheHttpClient4Engine.java:328) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:443) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:149) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:112) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:76) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at com.sun.proxy.$Proxy131.grantToken(Unknown Source) ~[na:na]
at org.keycloak.admin.client.token.TokenManager.grantToken(TokenManager.java:90) ~[keycloak-admin-client-13.0.1.jar:13.0.1]
at org.keycloak.admin.client.token.TokenManager.getAccessToken(TokenManager.java:70) ~[keycloak-admin-client-13.0.1.jar:13.0.1]
at org.keycloak.admin.client.token.TokenManager.getAccessTokenString(TokenManager.java:65) ~[keycloak-admin-client-13.0.1.jar:13.0.1]
at org.keycloak.admin.client.resource.BearerAuthFilter.filter(BearerAuthFilter.java:52) ~[keycloak-admin-client-13.0.1.jar:13.0.1]
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.filterRequest(ClientInvocation.java:579) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:440) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:149) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:112) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:76) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
at com.sun.proxy.$Proxy173.search(Unknown Source) ~[na:na]
at com.example.backend.user.UserServiceFeatureImpl.createUser$suspendImpl(UserServiceFeatureImpl.kt:247) ~[main/:na]
at com.example.backend.user.UserServiceFeatureImpl.createUser(UserServiceFeatureImpl.kt) ~[main/:na]
at com.example.backend.user.UserServiceFeatureImpl$$FastClassBySpringCGLIB$$ed9e17ac.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.3.7.jar:5.3.7]
at com.example.backend.user.UserServiceFeatureImpl$$EnhancerBySpringCGLIB$$2ccf6d25.createUser(<generated>) ~[main/:na]
at com.example.backend.user.UserServicePermissionsImpl.createUser$suspendImpl(UserServicePermissionsImpl.kt:47) ~[main/:na]
at com.example.backend.user.UserServicePermissionsImpl.createUser(UserServicePermissionsImpl.kt) ~[main/:na]
at com.example.backend.user.UserServicePermissionsImpl$$FastClassBySpringCGLIB$$d7f96f3a.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.3.7.jar:5.3.7]
at com.example.backend.user.UserServicePermissionsImpl$$EnhancerBySpringCGLIB$$fb5d3241.createUser(<generated>) ~[main/:na]
at com.example.backend.user.UserController.create$suspendImpl(UserController.kt:116) ~[main/:na]
at com.example.backend.user.UserController.create(UserController.kt) ~[main/:na]
at com.example.backend.user.UserController$$FastClassBySpringCGLIB$$d1552207.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.3.7.jar:5.3.7]
at com.example.backend.user.UserController$$EnhancerBySpringCGLIB$$f0dbf2c8.create(<generated>) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97) ~[kotlin-reflect-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Instance.call(CallerImpl.kt:113) ~[kotlin-reflect-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108) ~[kotlin-reflect-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at kotlin.reflect.full.KCallables.callSuspend(KCallables.kt:55) ~[kotlin-reflect-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at org.springframework.core.CoroutinesUtils$invokeSuspendingFunction$mono$1.invokeSuspend(CoroutinesUtils.kt:64) ~[spring-core-5.3.7.jar:5.3.7]
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[kotlin-stdlib-1.5.0.jar:1.5.0-release-749 (1.5.0)]
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:377) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126) ~[kotlinx-coroutines-core-jvm-1.5.0.jar:na]
at kotlinx.coroutines.reactor.MonoKt.monoInternal$lambda-2(Mono.kt:90) ~[kotlinx-coroutines-reactor-1.5.0.jar:na]
// ...
at reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:92) ~[reactor-core-3.4.6.jar:3.4.6]
at reactor.netty.channel.FluxReceive.startReceiver(FluxReceive.java:168) ~[reactor-netty-core-1.0.7.jar:1.0.7]
at reactor.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:147) ~[reactor-netty-core-1.0.7.jar:1.0.7]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:384) ~[netty-transport-native-epoll-4.1.65.Final-linux-x86_64.jar:4.1.65.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:325) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:268) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:263) ~[na:na]
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1340) ~[na:na]
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1215) ~[na:na]
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1158) ~[na:na]
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396) ~[na:na]
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:445) ~[na:na]
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:423) ~[na:na]
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:182) ~[na:na]
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:171) ~[na:na]
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1475) ~[na:na]
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1381) ~[na:na]
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:441) ~[na:na]
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:412) ~[na:na]
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) ~[httpclient-4.5.13.jar:4.5.13]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) ~[httpclient-4.5.13.jar:4.5.13]
at org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine.invoke(ApacheHttpClient4Engine.java:323) ~[resteasy-client-3.13.2.Final.jar:3.13.2.Final]
... 98 common frames omitted
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:439) ~[na:na]
at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:306) ~[na:na]
at java.base/sun.security.validator.Validator.validate(Validator.java:264) ~[na:na]
at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231) ~[na:na]
at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132) ~[na:na]
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1324) ~[na:na]
... 122 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) ~[na:na]
at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) ~[na:na]
at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297) ~[na:na]
at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:434) ~[na:na]
... 127 common frames omitted
2021-06-25 18:04:19.072 INFO 7821 --- [or-http-epoll-3] reactor.netty.http.server.AccessLog : 127.0.0.1:59138 - - [25/Jun/2021:18:04:18 +0200] "POST /users/new HTTP/1.1" 500 23140 306 ms
I tracked the issue down by removing all methods from the new service (so it is an empty interface + the implementation in a @Service
annotated class):
interface TaskService {}
and
@Service
class TaskServiceImpl(
// next line implies the error.
private val userService: UserService,
) : TaskService {}
When I remove the private val userService: UserService
dependency from the TaskServiceImpl
the userService
is able to connect to keycloak and the create-user route does not fail, but if I add it again, the said SSL handshake error occurs on the create-user route.
Other services, that also depend on the UserService
as well, do not imply the same error. The new service is completely empty, so I am very confused why the other services do not imply the same error?
For example, this service also uses the UserService
and it does not imply the user creation route to fail:
@Service
class FeedServiceImpl(
private val userService: UserService,
// other dependencies
) : FeedService {
/// ...
}
The TaskService
is used in two controllers, I have removed all routes and all other dependencies:
@RestController
class TaskReviewController(
// adding or removing this dependency does not change anything
private val taskService: TaskService,
) {
}
@RestController
class ActionsController(
// adding or removing this dependency implies the error
private val taskService: TaskService,
) {
}
When I remove the taskService
dependency from both, the error does not occur. When I only add it to the ActionsController
the error does occur - but if I only add it to the TaskReviewController
the error does not occur.
I am lost how I can track it further down. I thought that this might indicate a dependency cycle but there isn't a cycle, as UserService
does not depend on anything of the new TaskService
and has not been modified when adding the new TaskService
. And the UserService
also has no dependency on any of the controllers.
Any ideas?
--
PS: I also verified that configureSSL
is still executed and I am running the right development
profile.
I found out that the error depends on the package and name of the controller. If all controllers that use the TaskService
are alphabetically ordered after the SSLConfigDev
configuration than the error does not occur:
So this controller in the same package as the 'SSLConfigDev` implies the error:
@RestController
class SSLConfigDeuController(
private val taskService: TaskReviewService,
) {
}
but this is fine:
@RestController
class SSLConfigDewController(
private val taskService: TaskReviewService,
) {
}
Upvotes: 1
Views: 666
Reputation: 12292
After further investigation I think I understand the problem.
Depending on the name of the controller the user service is instantiated before or after the SSLConfigDev
configuration.
The UserService
depends on Keycloak
. The Keycloak
class creates a resteasy client in the constructor:
The relevant code of the library is:
Keycloak(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, String grantType, Client resteasyClient, String authtoken) {
config = new Config(serverUrl, realm, username, password, clientId, clientSecret, grantType);
client = resteasyClient != null ? resteasyClient : newRestEasyClient(null, null, false);
authToken = authtoken;
tokenManager = authtoken == null ? new TokenManager(config, client) : null;
target = (ResteasyWebTarget) client.target(config.getServerUrl());
target.register(newAuthFilter());
}
This code is run when the UserService
is instantiated because it triggers creating the Keycloak
bean.
Depending on whether this happens before or after the SSLConfigDev
is run the system properties are already set or not.
As often with the hard-to-spot bugs, solving the problem is easy:
We just need to ensure that the Keycloak
bean is created after the system properties have been set. And because the correct configuration should not depend on the order of bean creation, the implementation should be bound to the specific bean.
For example, we can remove the SSLConfigDev
and instead create a dev-profile version of the Keycloak.init()
method that sets the system properties just before creating the bean instance. Or we can just check the environment when creating the bean as follows:
@Configuration
@ConfigurationProperties("keycloak")
class KeycloakConfig(
private val env: Environment,
){
lateinit var serverUrl: String
lateinit var realm: String
lateinit var clientId: String
lateinit var clientSecret: String
lateinit var frontEndClientId: String
@Bean
fun init(): Keycloak {
if (env.activeProfiles.contains("development")) {
System.setProperty("javax.net.ssl.trustStore", "./dev-truststore.jks")
System.setProperty("javax.net.ssl.trustStorePassword", "keycloak")
}
return KeycloakBuilder.builder()
.serverUrl(serverUrl)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.realm(realm)
.clientId(clientId)
.clientSecret(clientSecret)
.resteasyClient(
ResteasyClientBuilder().connectionPoolSize(10).build()
)
.build()
}
}
Upvotes: 1