Обеспечение обслуживания с помощью KEYCLOAK

Я вижу много тем по этому поводу, но кажется, что все они обращаются к KEYCLOAK с одним и тем же URL-адресом. Объяснение. Я пытаюсь настроить внешний интерфейс + микросервис, защищенный архитектурой KC.

Смотрите рисунок:

Простая архитектура

Все работает хорошо, если keycloak (kc) видят все с одним и тем же URL-адресом, то есть для 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 и сервис должны находиться в защищенной среде. Таким образом, интерфейс видит KC через обратный прокси и схему HTTPS, а служба видит его через схему HTTP.

Насколько я понимаю, служба пытается получить доступ к KC с помощью 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

Имеется в виду, что он пытается использовать схему 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. Что толку, если информацию можно найти в токене ????

Я уверен, что это что-то очень очевидное, чего я не вижу или не понимаю ... пожалуйста, помогите.


person Lbro    schedule 06.05.2020    source источник
comment
Можно ли получить доступ к Keycloak через браузер? Если вы настроили nginx для HTTPS, тогда ваш адрес keycloak должен быть HTTPS.   -  person Xtreme Biker    schedule 06.05.2020
comment
Спасибо за ответ. Frontend - это веб-приложение vuejs, которое получает токен через nginx, настроенный для HTTPS. Затем, если я проверю ТОКЕН (jwt.io), я получаю запрос с HTTPS. Я предполагаю, что служба использует этот адрес для проверки, даже если он настроен по схеме HTTP. Вы предполагаете, что архитектура, которую я представил на чертеже, недействительна: нет способа ее реализовать? Мне показалось, что это довольно устойчивая архи ...   -  person Lbro    schedule 06.05.2020
comment
Кажется, что с архитектурой все в порядке, но я думаю, что ваша проблема больше связана с конфигурацией NGINX + keycloak. Вот почему я спрашиваю вас, можете ли вы получить доступ к административной панели сервера keycloak через обычный браузер?   -  person Xtreme Biker    schedule 06.05.2020
comment
Да, нет проблем с доступом к графическому интерфейсу администратора.   -  person Lbro    schedule 06.05.2020
comment
Значит, вы получаете к нему доступ по HTTPS?   -  person Xtreme Biker    schedule 06.05.2020
comment
да. Нет другого способа получить доступ к защищенной области, кроме как через nginx.   -  person Lbro    schedule 06.05.2020
comment
Ладно, я вижу суть. Ваша служба должна получать доступ к серверу keycloak извне, а не через внутреннюю сеть. URL-адрес издателя должен быть одинаковым как для внешнего клиента, так и для клиента службы базы данных.   -  person Xtreme Biker    schedule 06.05.2020
comment
:( Затем мне нужно поиграть с nginx conf, чтобы моя служба получила доступ к KC с тем же URL, что и передняя. Но тогда я не понимаю цель параметра auth-server-url (keycloak.json)   -  person Lbro    schedule 06.05.2020
comment
Вам не нужно менять конфигурацию nginx .. Пока ваша служба имеет доступ к внешнему миру, нужно просто изменить auth-server-url, чтобы он указывал на общедоступный URL-адрес сервера KC.   -  person Xtreme Biker    schedule 06.05.2020
comment
Затем я получаю [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 Я кое-что читал об этом здесь: stackoverflow.com/questions/52186020/ Я попробую ...   -  person Lbro    schedule 06.05.2020
comment
Какой сертификат вы используете для своего nginx? Вероятно, это связано с тем, что ваш сервис JDK не доверяет этому.   -  person Xtreme Biker    schedule 06.05.2020
comment
Возможно, вы правы: это сертификат с автоподписью .... Я пробую трюк с хранилищем ключей   -  person Lbro    schedule 06.05.2020
comment
Могу ли я опубликовать свои комментарии в качестве ответа? Ваша текущая проблема, похоже, совершенно не связана с исходным вопросом   -  person Xtreme Biker    schedule 06.05.2020
comment
Что вы можете. Но я не думаю, что это не связано: вопрос в том, как обратиться к серверу keycloak из двух мест; одно место покрыто HTTPS, а другое - HTTP.   -  person Lbro    schedule 06.05.2020
comment
Ответ на это: вы не можете. Эмитент токена должен быть одинаковым для обоих мест.   -  person Xtreme Biker    schedule 06.05.2020
comment
Яснее быть не может! Спасибо за помощь.   -  person Lbro    schedule 06.05.2020


Ответы (2)


Вы не можете получить доступ к экземпляру Keycloak как через HTTP, так и через HTTPS (или разные URL-адреса) для клиентов в одной области. auth-server-url, принадлежащий эмитенту токена, должен быть таким же, это проверяется разные адаптеры.

ЭТО, ОДНАКО, ПОКАЗЫВАЕТСЯ ДО KEYCLOAK VERSION 8

Ваш вопрос, кажется, отражен в этом заявке JIRA, и у команды Keycloak действительно есть решение для этого, что задокументировано в этого черновика и здесь. Вероятно, вы можете немного настроить свой образ докера keycloak и добавить эту конфигурацию, поэтому подумайте, заслуживает ли он того, чтобы вы это делали или обращались к серверу Keycloak извне из вашей службы.

См. также:

person Xtreme Biker    schedule 06.05.2020

Шампанское ! С этим 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"

А также :

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

в docker-compose контейнера keycloak (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 за его помощь и время.

person Lbro    schedule 06.05.2020
comment
Отлично, это сработало для вас ;-) Если вы считаете это полезным, вы можете отметить мой ответ или проголосовать за него. Увидимся! - person Xtreme Biker; 06.05.2020