0

I see a lot of topics about this but it seems that all of them access KEYCLOAK with the same URL. Explanation. I try to set up a frontend+microservice secured by KC architecture.

See the drawing:

Simple architecture

Everything work well if keycloak (kc) is seen by everybody with the same url, that is for JS:

const keycloakURL = "http://test-kc-keycloak:8080/auth";

const keycloakParams = {
  url: keycloakURL,
  realm: "Test",
  clientId: "IHM"
};
const keycloak = Keycloak(keycloakParams);
...

For the service (project-default.yml):

thorntail:
  keycloak:
    secure-deployments:
      kc.war:
        auth-server-url: "http://test-kc-keycloak:8080/auth"
        realm: Test
        resource: service
        bearer-only: true
        ssl-required: external
  microprofile:
    jwtauth:
      realm: Test
      token:
        issuedBy: "http://test-kc-keycloak:8080/auth/realms/Test"


  logging:
    loggers:
      kc:
        level: DEBUG

See https://github.com/lbroque/test-kc

But in the real world, the frontend is in the dark side of the net while KC and the service is supposed to be in a protected environment. So the frontend see KC through a reverse proxy and a HTTPS scheme, while the service see it with an HTTP scheme.

As far as I can see, the service try to access KC with SSL:

10:37:51,102 ERROR [adapters.rotation.JWKPublicKeyLocator] (default task-1) :
>>> Error when sending request to retrieve realm keys: org.keycloak.adapters.HttpClientAdapterException: IO error
    at org.keycloak.adapters.HttpAdapterUtils.sendJsonHttpRequest(HttpAdapterUtils.java:57)
    at org.keycloak.adapters.rotation.JWKPublicKeyLocator.sendRequest(JWKPublicKeyLocator.java:99)
    at org.keycloak.adapters.rotation.JWKPublicKeyLocator.getPublicKey(JWKPublicKeyLocator.java:63)
    at org.keycloak.adapters.rotation.AdapterTokenVerifier.getPublicKey(AdapterTokenVerifier.java:121)
    at org.keycloak.adapters.rotation.AdapterTokenVerifier.createVerifier(AdapterTokenVerifier.java:111)
    at org.keycloak.adapters.rotation.AdapterTokenVerifier.verifyToken(AdapterTokenVerifier.java:47)
    at org.wildfly.swarm.keycloak.mpjwt.deployment.KeycloakJWTCallerPrincipalFactory.parse(KeycloakJWTCallerPrincipalFactory.java:26)
    at org.wildfly.swarm.microprofile.jwtauth.deployment.auth.jaas.JWTLoginModule.validate(JWTLoginModule.java:100)
    at org.wildfly.swarm.microprofile.jwtauth.deployment.auth.jaas.JWTLoginModule.login(JWTLoginModule.java:65)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755)
    at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195)
    at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682)
    at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680)
    at javax.security.auth.login.LoginContext.login(LoginContext.java:587)
    at org.jboss.security.authentication.JBossCachedAuthenticationManager.defaultLogin(JBossCachedAuthenticationManager.java:406)
    at org.jboss.security.authentication.JBossCachedAuthenticationManager.proceedWithJaasLogin(JBossCachedAuthenticationManager.java:345)
    at org.jboss.security.authentication.JBossCachedAuthenticationManager.authenticate(JBossCachedAuthenticationManager.java:323)
    at org.jboss.security.authentication.JBossCachedAuthenticationManager.isValid(JBossCachedAuthenticationManager.java:146)
    at org.wildfly.extension.undertow.security.JAASIdentityManagerImpl.verifyCredential(JAASIdentityManagerImpl.java:123)
    at org.wildfly.extension.undertow.security.JAASIdentityManagerImpl.verify(JAASIdentityManagerImpl.java:96)
    at org.wildfly.swarm.microprofile.jwtauth.deployment.auth.JWTAuthMechanism.authenticate(JWTAuthMechanism.java:77)
    at org.wildfly.extension.undertow.security.jaspi.modules.HTTPSchemeServerAuthModule.validateRequest(HTTPSchemeServerAuthModule.java:88)
    at org.jboss.security.auth.message.config.JBossServerAuthContext.invokeModules(JBossServerAuthContext.java:157)
    at org.jboss.security.auth.message.config.JBossServerAuthContext.validateRequest(JBossServerAuthContext.java:135)
    at org.jboss.security.plugins.auth.JASPIServerAuthenticationManager.isValid(JASPIServerAuthenticationManager.java:115)
    at org.wildfly.extension.undertow.security.jaspi.JASPICAuthenticationMechanism.authenticate(JASPICAuthenticationMechanism.java:125)
    at io.undertow.security.impl.SecurityContextImpl$AuthAttempter.transition(SecurityContextImpl.java:245)
    at io.undertow.security.impl.SecurityContextImpl$AuthAttempter.access$100(SecurityContextImpl.java:231)
    at io.undertow.security.impl.SecurityContextImpl.attemptAuthentication(SecurityContextImpl.java:125)
    at io.undertow.security.impl.SecurityContextImpl.authTransition(SecurityContextImpl.java:99)
    at io.undertow.security.impl.SecurityContextImpl.authenticate(SecurityContextImpl.java:92)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:55)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow.security.jaspi.JASPICSecureResponseHandler.handleRequest(JASPICSecureResponseHandler.java:48)
    at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
    at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1504)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
    at java.lang.Thread.run(Thread.java:748)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
    at org.apache.http.conn.ssl.SSLSocketFactory.createLayeredSocket(SSLSocketFactory.java:573)
    at org.keycloak.adapters.SniSSLSocketFactory.createLayeredSocket(SniSSLSocketFactory.java:114)
    at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:557)
    at org.keycloak.adapters.SniSSLSocketFactory.connectSocket(SniSSLSocketFactory.java:109)
    at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:414)
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)
    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:144)
    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:134)
    at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:610)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:445)
    at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
    at org.keycloak.adapters.HttpAdapterUtils.sendJsonHttpRequest(HttpAdapterUtils.java:36)
    ... 72 more
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 sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:397)
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:302)
    at sun.security.validator.Validator.validate(Validator.java:262)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
    ... 94 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:392)
    ... 100 more

meaning it tries with the https scheme, No? so I suppose it uses the URL found in the TOKEN sent by frontend which got it with the HTTPS scheme, because I configured my service (so it should use HTTP scheme):

thorntail:
  keycloak:
    secure-deployments:
      model4xxx.war:
        auth-server-url: "http://keycloak.hnr:9090/auth"
        realm: xxx
        resource: model4xxx
        bearer-only: true
        ssl-required: external
  microprofile:
    jwtauth:
      realm: xxx
      token:
        issuedBy: "http://keycloak.hnr:9090/auth/realms/xxxx"

Last point : KC is in a DOCKER container. I tried several combinations of env variables KEYCLOAK_FRONTEND_URL, KEYCLOAK_HOSTNAME. It doesn't seem to have any effect.

I think I didn't understand the purpose of auth-server-url. What the use if the information could be found in the token ????

I'm sure it is something very very obvious that I don't see or understood ... please help.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Lbro
  • 309
  • 2
  • 16
  • Can you access keycloak through a browser? If you configured nginx for HTTPS, then your keycloak address should be HTTPS. – Aritz May 06 '20 at 10:01
  • Thx for your answer. Frontend is a vuejs web application and gets the token through nginx which is configured for HTTPS. Then if I check the TOKEN (jwt.io) I get an iss with HTTPS. I Suppose the service uses this address to check even if it is configured with the HTTP scheme. What you suggest is that the architecture I presented in the drawing is not valid : no way to implement it ? It seemed to me that it's a quite staritforward archi... – Lbro May 06 '20 at 10:09
  • The architecture seems to be Okay, but what I think is that your issue has more to do with NGINX+keycloak configuration. That's why I ask you, can you access the keycloak server admin panel through a normal browser? – Aritz May 06 '20 at 10:27
  • Yep, no problem to access admin GUI. – Lbro May 06 '20 at 10:36
  • So you access it using HTTPS? – Aritz May 06 '20 at 10:42
  • Yes. There is no way to access protected area but through nginx. – Lbro May 06 '20 at 10:51
  • Okay, I see the point. Your service should access the keycloak server externally, not through the internal network. The issuer url needs to be the same either for the front client and the database service client. – Aritz May 06 '20 at 10:57
  • :( Then I have to "play" with the nginx conf in order to make my service access KC with the same URL as the front. But then I don't understand the purpose of auth-server-url parameter (keycloak.json) – Lbro May 06 '20 at 12:49
  • You shouldn't need to change the nginx conf.. As long as your service has access to the "outside world" it should be just a matter of changing the `auth-server-url` to point out to the public url of the KC server. – Aritz May 06 '20 at 13:04
  • Then I get `[org.keycloak.adapters.KeycloakDeployment] (default task-1) Failed to load URLs from https://keycloak.hnr/auth/realms/xxxx/.well-known/openid-configuration: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target` I read something about it here : https://stackoverflow.com/questions/52186020/keycloak-secured-wildfly-rest-service-returns-401-unauthorized I'll try that ... – Lbro May 06 '20 at 13:13
  • What kind of certificate do you use for your nginx? This is probably related of your service JDK not trusting that. – Aritz May 06 '20 at 13:17
  • You may be right : it's an autosigned certif .... I'm trying the keystore trick – Lbro May 06 '20 at 13:27
  • Can I publish my comments as an answer? Your current issue seems to be totally unrelated to the original question – Aritz May 06 '20 at 13:29
  • Sure you can. But I don't think it's unrelated : the question is how to address a keycloak server from 2 places ; one place covered with HTTPS and the other with HTTP. – Lbro May 06 '20 at 13:33
  • The answer to this is: You can't. The token issuer needs to be the same for both places. – Aritz May 06 '20 at 13:35
  • 1
    It can't be clearer ! Thank you for your help. – Lbro May 06 '20 at 13:38

2 Answers2

1

You can't access the Keycloak instance both from HTTP and HTTPS (or different urls) for clients in the same realm. The auth-server-url, which belongs to the token issuer needs to be the same, this is checked by the different adapters.

THIS SEEMS TO BE UNTIL KEYCLOAK VERSION 8, HOWEVER

Your question seems to be mirrored in this JIRA ticket and the Keycloak team does have a solution for this, which is documented in this draft and here. Probably you can tune your keycloak docker image a bit and add this configuration, so think if it deserves for you doing that or accessing the Keycloak server externally from your service.

See also:

Aritz
  • 30,971
  • 16
  • 136
  • 217
1

Champagne ! With this conf (project-defauls.yml) :

thorntail:
  keycloak:
    secure-deployments:
      model4geo3d.war:
        auth-server-url: "http://keycloak.hnr:9090/auth"
        realm: xxxx
        resource: model4xxx
        bearer-only: true
        ssl-required: external
  microprofile:
    jwtauth:
      realm: xxx
      token:
        issuedBy: "https://keycloak.hnr/auth/realms/xxxx"

And :

       - "KEYCLOAK_FRONTEND_URL=https://keycloak.hnr/auth/"
        - "PROXY_ADDRESS_FORWARDING=true"

in docker-compose of the keycloak container (keycloak.environment)

And

keytool -import -trustcacerts -keystore /etc/ssl/certs/java/cacerts -storepass 'changeit' -file /home/core/dota/keycloak.hnr.crt -alias keycloak

in the container of the data service, it works great.

2020-05-06 17:41:56,582 INFO  [org.keycloak.adapters.KeycloakDeployment] (default task-1) Loaded URLs from http://keycloak.hnr:9090/auth/realms/xxxx/.well-known/openid-configuration

got a correct config that it could use :)

Thanks to Xtreme Biker for his help and time.

Lbro
  • 309
  • 2
  • 16
  • Great it worked for you ;-) If you consider it helpful you can mark or upvote my answer. See you! – Aritz May 06 '20 at 16:35