【问题标题】:How to authenticate a backend-to-backend with Spring Boot / Keycloak如何使用 Spring Boot / Keycloak 对后端到后端进行身份验证
【发布时间】:2019-07-09 18:18:01
【问题描述】:

我正在尝试使用 Keycloak 和 Spring Boot 以及 Spring Security 和 JWT-tokens(Keycloak 中的仅承载设置)在面向微服务的应用程序的后端服务中实现身份验证。

我有一个需要身份验证才能访问 REST 端点的后端服务。该服务为 Web UI 提供数据,并将数据存储在数据库中,以便以后进行处理。 UI 中的用户身份验证以及针对该后端服务的 UI 均已生效。

然后,有另一个后端服务在后台运行,计算也应该存在于第一个提到的后端服务中的值。由于该服务需要身份验证,因此进行计算的服务首先需要从 Keycloak 检索访问令牌,以针对其他后端服务进行身份验证,以使 HTTP 发布工作。

我正在尝试使用 KeycloakRestTemplate 进行 HTTP 发布,但是当我调用 .postForObject 方法时,我得到一个异常:

Caused by: java.lang.IllegalStateException: Cannot set authorization header because there is no authenticated principal
    at org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory.getKeycloakSecurityContext(KeycloakClientRequestFactory.java:70)
    at org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory.postProcessHttpRequest(KeycloakClientRequestFactory.java:55)
    at org.springframework.http.client.HttpComponentsClientHttpRequestFactory.createRequest(HttpComponentsClientHttpRequestFactory.java:160)

在调用其他 REST 服务之前,计算服务似乎没有自动检索身份验证令牌。我在 Google 上对所有这些 Keycloak 特定类进行了大量研究,但我没有找到我需要做什么。

谁能给我一个提示? 我也不知道 Spring 配置的哪些部分与此处相关,但如果您需要,我会提供它们。

编辑

我的计算服务的 application.properties 如下所示:

keycloak.auth-server-url=https://localhost/auth
keycloak.realm=myrealm
keycloak.bearer-only=true
keycloak.resource=backend-service2
keycloak.principal-attribute=preferred_username
keycloak.cors=true
keycloak.realm-key=<PUBKEY>
keycloak.credentials.secret=<SECRET_UUID_STYLE>
keycloak.use-resource-role-mappings=true

更新

感谢@Sai prateek 和@Xtreme Biker。 这似乎将我引向了正确的方向。

我应用了这个解决方案,但仍然出现异常,我认为 keycloak 配置错误。我现在在 keycloak 中有三个客户端:webui、backend-service1、backend-service2。

webui配置为: 访问类型:公开

backend-service1 配置为: 访问类型:仅承载

backend-service2 配置为: 访问类型:仅承载

例外是:

2019-02-18 11:15:32.914 DEBUG 22620 --- [  restartedMain] o.s.web.client.RestTemplate              : POST request for "http://localhost:<PORT>/auth/realms/<REALM_NAME>/protocol/openid-connect/token" resulted in 400 (Bad Request); invoking error handler

Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
    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 org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: error="access_denied", error_description="Access token denied."
    at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:142)
    at org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider.obtainAccessToken(ClientCredentialsAccessTokenProvider.java:44)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:148)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:121)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:683)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:644)
    at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:399)
[STRIPPED]


    ... 5 more
Caused by: error="invalid_client", error_description="Bearer-only not allowed"
    at org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:80)
    at org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:33)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3072)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readInternal(AbstractJackson2HttpMessageConverter.java:217)
    at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:198)
    at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport$AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:237)
    at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:730)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:688)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:654)
    at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:137)
    ... 18 more

另请注意,我将 keycloak.auth-server-url 更改为 http://localhost:&lt;PORT&gt;/auth(无 HTTPS),因此证书验证不会因开发中的自签名证书而失败。

【问题讨论】:

  • 如果没有用户在场,您可能会发现这是不可能的。服务到服务调用最好使用 SSL 客户端证书进行身份验证,但如果您的基础设施无法做到这一点,那么使用 API 密钥(即任何其他名称的密码)的简单基本身份验证将起作用。不过这里讨论的太多了。
  • 似乎有道理。你知道一个好的教程/例子吗? “基础设施”究竟能做什么?我认为一个非常幼稚的实现是给计算一个私钥,以便在启动时加载另一个服务必须公钥,以便它可以验证来自计算服务的签名?但是,如果私钥被泄露怎么办......你的意思是基础设施需要照顾......更新密钥等?
  • 您需要一个 PKI 基础设施来颁发/撤销/更新受信任的证书,以及一个将所述证书分发到服务器的机制。自签名证书可以在开发/测试中使用,但您不希望它们在产品中使用。好消息是证书交换是 SSL 协议的(可选)部分,而 X.509 身份验证是 spring-security 的内置功能(网上有很多教程可以帮助您入门)
  • backend-service2 应该是机密的 Access-Type
  • 不幸的是,这也不起作用。我收到错误Caused by: error="access_denied", error_description="Error requesting access token."

标签: java spring-boot spring-security jwt keycloak


【解决方案1】:

您似乎缺少身份验证服务器的一些配置。 KeycloakRestTemplate 使用客户端 ID、客户端密码、用户名和密码来验证 Keycloak 服务器。您需要为KeycloakClientCredentialsRestTemplate 设置clientidclientsecretrealm 和身份验证服务器 url,例如 -

@Service
public class MyKeycloakClientCredentialsConfig {

    @Value("${keycloak.realm}")
    private String realm;

    @Value("${keycloak.auth-server-url}")
    private String authServerUrl;

    @Value("${keycloak.resource}")
    private String clientId;

    @Value("${keycloak.credentials.secret}")
    private String clientSecret;

    @Bean
    public KeycloakClientCredentialsRestTemplate createRestTemplate() {
        return new KeycloakClientCredentialsRestTemplate(getClientCredentialsResourceDetails(),
                new DefaultOAuth2ClientContext());
    }

    private ClientCredentialsResourceDetails getClientCredentialsResourceDetails() {
        String accessTokenUri = String.format("%s/realms/%s/protocol/openid-connect/token",
            authServerUrl, realm);
        List<String> scopes = new ArrayList<String>(0); // TODO introduce scopes

        ClientCredentialsResourceDetails clientCredentialsResourceDetails = 
                new ClientCredentialsResourceDetails();

        clientCredentialsResourceDetails.setAccessTokenUri(accessTokenUri);
        clientCredentialsResourceDetails.setAuthenticationScheme(AuthenticationScheme.header);
        clientCredentialsResourceDetails.setClientId(clientId);
        clientCredentialsResourceDetails.setClientSecret(clientSecret);
        clientCredentialsResourceDetails.setScope(scopes);

        return clientCredentialsResourceDetails;
    }

}

我的resttemplate是这样的-

public class SampleRestTemplate extends OAuth2RestTemplate {

    public KeycloakClientCredentialsRestTemplate(OAuth2ProtectedResourceDetails resource,
            OAuth2ClientContext context) {
        super(resource, context);
    }

}

它非常适合我。

【讨论】:

  • 我用 application.properties 更新了我的问题。我不完全理解如何将您的示例用于请求。你有一个最小的工作示例吗?
  • 这个答案是要走的路。@pudelwudel您需要以客户端身份登录(在这里您是作为客户端,而不是代表任何用户)。您可能会发现这个问题很有用:stackoverflow.com/questions/46073485/…
  • 现在,我更进一步,请查看我的更新答案;)
【解决方案2】:

好的,我自己找到了解决方案:我需要在 keycloak 内的“backend-service2”的客户端配置中将“启用服务帐户”按钮设置为 ON。

【讨论】:

  • 你拯救了我的一天!
猜你喜欢
  • 1970-01-01
  • 2021-01-19
  • 2018-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-11
  • 2021-07-13
  • 2021-01-21
相关资源
最近更新 更多