【问题标题】:Keycloak spring security client credential grantKeycloak Spring Security 客户端凭证授予
【发布时间】:2018-02-14 20:13:01
【问题描述】:

我可以使用KeycloakRestTemplate,其中一个 keycloak 客户端正在与另一个 keycloak 客户端通信。然而,它只有在我登录到第一个 keycloak 客户端时才有效,即它将客户端 ID、客户端密码、用户名、密码发送到 keycloak 服务器。如果我没有在第一个客户端上使用用户和密码进行身份验证,我会得到“无法设置授权标头,因为没有经过身份验证的原则”。但是我已将 keycloak 配置为为第一个客户端(客户端凭据授予)使用服务帐户,因此我不应该使用用户/密码,而应该只依赖客户端 ID/秘密。这是 OAuth 2 规范的错误/偏差吗?

【问题讨论】:

    标签: spring security spring-security keycloak


    【解决方案1】:

    KeycloakRestTemplate 将客户端 ID、客户端密码、用户名和密码发送到 Keycloak 服务器。我只想发送客户端 ID 和密码。我创建了OAuth2RestTemplateKeycloakClientCredentialsRestTemplate 子类来执行此操作。它使用 Spring Boot 中的 OAuth2 支持来授予客户端凭据。它还从 application.properties 获取 Keycloak 属性。

    import org.springframework.security.oauth2.client.OAuth2ClientContext;
    import org.springframework.security.oauth2.client.OAuth2RestTemplate;
    import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
    
    public class KeycloakClientCredentialsRestTemplate extends OAuth2RestTemplate {
    
        public KeycloakClientCredentialsRestTemplate(OAuth2ProtectedResourceDetails resource,
                OAuth2ClientContext context) {
            super(resource, context);
        }
    
    }
    

    还有:

    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
    import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
    import org.springframework.security.oauth2.common.AuthenticationScheme;
    import org.springframework.stereotype.Service;
    
    @Service
    public class KeycloakClientCredentialsConfig {
    
        @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;
        }
    
    }
    

    【讨论】:

    • 对我来说就像一个魅力。我有完全相同的问题。谢谢!
    • 准确答案。谢谢!
    • 注意!自 Spring Security 5.2.x 起不再支持 OAuth2RestTemplate 等。正确的方法是使用 WebClient(反应式和非阻塞式)。 stackoverflow.com/questions/58982286/…
    【解决方案2】:

    对于我的基于微服务架构的应用程序,我同时使用了 user 和 service accounts。我猜弹簧安全适配器只处理与用户相关的东西(至少我正在使用的版本是 2.2.1)。我要做的是拥有另一个RestTemplate,我自己处理它以便作为客户端访问资源。

    举个例子:

    @Service
    public class RemoteAccessService{
    
        //Manages user access
        private KeycloakRestTemplate userAccessRestTemplate;
    
        //Manages client access
        private RestTemplate clientAccessRestTemplate;
    
        public RemoteAccessService(KeycloakRestTemplate userAccessRestTemplate, 
            @Qualifier("clientAccessRestTemplate") RestTemplate clientAccessRestTemplate;){
    
        }
    
    }
    

    然后,您在 @Configuration 类中构建一个 RestTemplate bean 以管理客户端授权:

    @Bean
    public RestTemplate clientAccessRestTemplate() {
        RestTemplate template = new RestTemplate();
        template.getMessageConverters().add(new FormHttpMessageConverter());
        template.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
        template.getInterceptors().add(new ClientHttpRequestInterceptor() {
    
            @Override
            public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                    ClientHttpRequestExecution execution) throws IOException {
                //Intercept each of the requests performed by this template 
                //and add the client access token in the Authorization header
                HttpRequest wrapper = new HttpRequestWrapper(request);
                if (clientAccessToken != null) {
                    wrapper.getHeaders().set("Authorization",
                            "Bearer " + clientAccessToken.getToken());
                }
                return execution.execute(wrapper, body);
            }
        });
        return template;
    }
    

    当然,你需要确保你在拦截器中有一个正确的clientAccessToken,否则你会得到一个 401 或 403 代码。在这里,您有一个 post 说明如何在 OAuth 中执行此操作(您不需要用户/密码,只需客户端凭据)。

    作为旁注,keycloak 适配器可以方便地管理某些情况,但它们不提供对 keycloak 的所有功能的访问,这是一种更强大的方式。

    【讨论】:

      猜你喜欢
      • 2021-10-25
      • 1970-01-01
      • 1970-01-01
      • 2017-03-11
      • 2018-05-24
      • 2020-02-25
      • 1970-01-01
      • 2019-12-03
      • 2018-09-18
      相关资源
      最近更新 更多