使用 KEYCLOAK 确保 thorntail 服务

Posted

技术标签:

【中文标题】使用 KEYCLOAK 确保 thorntail 服务【英文标题】:Securing thorntail service with KEYCLOAK 【发布时间】:2020-08-21 05:38:52 【问题描述】:

我看到很多关于此的主题,但似乎所有人都使用相同的 URL 访问 KEYCLOAK。 解释。我尝试建立一个由 KC 架构保护的前端+微服务。

看图:

如果每个人都使用相同的 url 看到 keycloak (kc),那么一切都会正常工作,即对于 JS:

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

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

对于服务(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

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

但在现实世界中,前端处于网络的黑暗面,而 KC 和服务应该处于受保护的环境中。所以前端通过反向代理和 HTTPS 方案看到 KC,而服务通过 HTTP 方案看到它。

据我所知,该服务尝试使用 SSL 访问 KC:

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

意味着它尝试使用 https 方案,不是吗?所以我想它使用在前端发送的令牌中找到的 URL,它通过 HTTPS 方案得到它,因为我配置了我的服务(所以它应该使用 HTTP 方案):

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"

最后一点:KC 在 DOCKER 容器中。我尝试了几种环境变量 KEYCLOAK_FRONTEND_URL、KEYCLOAK_HOSTNAME 的组合。好像没什么效果。

我想我不明白 auth-server-url 的用途。如果可以在令牌中找到信息有什么用????

我确定这是非常明显的,我没有看到或理解...请帮助。

【问题讨论】:

可以通过浏览器访问keycloak吗?如果你为 HTTPS 配置了 nginx,那么你的 keycloak 地址应该是 HTTPS。 感谢您的回答。前端是一个 vuejs Web 应用程序,通过配置为 HTTPS 的 nginx 获取令牌。然后,如果我检查 TOKEN (jwt.io),我会遇到 HTTPS 问题。我假设服务使用这个地址来检查它是否配置了 HTTP 方案。你的建议是我在图中展示的架构是无效的:没有办法实现它?在我看来,这是一个非常前卫的架构...... 架构似乎还可以,但我认为您的问题与 NGINX+keycloak 配置有关。所以我问你,你能通过普通浏览器访问keycloak服务器管理面板吗? 是的,访问管理 GUI 没问题。 再清楚不过了!感谢您的帮助。 【参考方案1】:

对于同一领域中的客户端,您不能同时从 HTTP 和 HTTPS(或不同的 url)访问 Keycloak 实例。属于token issuer的auth-server-url需要相同,这个由the different adapters检查。

这似乎直到 KEYCLOAK 版本 8,但是

您的问题似乎反映在此JIRA ticket 中,Keycloak 团队确实为此提供了解决方案,该解决方案记录在this draft 和here 中。也许您可以稍微调整一下您的 keycloak docker 映像并添加此配置,因此请考虑是否值得您这样做或从您的服务外部访问 Keycloak 服务器。

另请参阅:

Invalid token issuer when running keycloak behind proxy Keycloak issuer validation and multi-tenancy approach HTTP and HTTPS with keycloack + spring

【讨论】:

【参考方案2】:

香槟! 有了这个 conf (project-defaus.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"

还有:

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

在keycloak容器的docker-compose中(keycloak.environment)

还有

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

在数据服务的容器中,效果很好。

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

得到了可以使用的正确配置:)

感谢 Xtreme Biker 的帮助和时间。

【讨论】:

非常适合您 ;-) 如果您认为它有帮助,您可以标记或支持我的回答。再见!

以上是关于使用 KEYCLOAK 确保 thorntail 服务的主要内容,如果未能解决你的问题,请参考以下文章

如何配置 Thorntail 2.5.0.Final 以使用来自 Keycloak 的 JWT 令牌授权用户?

Thorntail 没有优雅地退出

使用 Thorntail / Wildfly Swarm 配置 db2 数据源

Thorntail project-defaults.yml的技术参数

Keycloak - 将客户端登录限制为组成员

在 Thorntail WildFly 服务器中添加应用程序用户的最简单方法是啥?