Skip to content

Provide support for OAuth2 in Spring Config Client.  #2348

@cobar79

Description

@cobar79

I would like to implement OAuth2 resource server in the Spring Config Server and require JWT for all configuration requests.

Is your feature request related to a problem? Please describe.

I can't seem to find any method to implement a Spring Security OAuth2 Client Provider to the Spring Cloud Client call.
Overriding ConfigServicePropertySourceLocator
#177 and Adding Generic "Authorization" support for Config Client do not appear to work with current import connection framework "optional:configserver:http://${env.config.hostname}:${env.config.port}/config-server"
The call to the Configuration Server is made before the ConfigServicePropertySourceLocator is instantiated.

Describe the solution you'd like
I would like to use the same implementation used for Machine to Machine OAuth2 communication where by the Spring Security handles obtaining and refreshing the Bearer Token to be passed to the Spring Config Server calls on startup. Preferably a WebClient over RestTemplate solution.

    @Bean
    ReactiveClientRegistrationRepository clientRegistrations(
        @Value("${spring.security.oauth2.client.provider.keycloak-client.token-uri}") String tokenUri,
        @Value("${spring.security.oauth2.client.registration.keycloak-client.client-id}") String clientId,
        @Value("${spring.security.oauth2.client.registration.keycloak-client.client-secret}") String clientSecret,
        @Value("${spring.security.oauth2.client.registration.keycloak-client.scope}") String scope,
        @Value("${spring.security.oauth2.client.registration.keycloak-client.authorization-grant-type}") String authorizationGrantType
    ) {

        Collection<String> scopeList = Arrays.asList(scope.split(","));

        ClientRegistration registration = ClientRegistration
            .withRegistrationId(registrationId)
            .tokenUri(tokenUri)
            .clientId(clientId)
            .clientSecret(clientSecret)
            .scope(scopeList)
            .authorizationGrantType(new AuthorizationGrantType(authorizationGrantType))
            .build();
        return new InMemoryReactiveClientRegistrationRepository(registration);
    }

    @Bean(value = "authWebClient")
    WebClient authWebClient(ReactiveClientRegistrationRepository clientRegistrations) {
        InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations);
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, clientService);
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth.setDefaultClientRegistrationId(registrationId);
        return WebClient.builder()
            .filter(oauth)
            .baseUrl(baseUrl)
            .build();
    }

Describe alternatives you've considered
A method to intercept the Spring Config Server rest call, call the IDP and include the Bearer Token manually, there by bypassing the current RestTemplate and any Basic Authentication.

Additional context
Spring Boot 3.1.2, Spring Security 6.1.2
Keycloak OAuth2 implementation

Configuration Server Security

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
        MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
        http.anonymous(AbstractHttpConfigurer::disable);
        http.csrf(AbstractHttpConfigurer::disable);
        http.authorizeHttpRequests(authorize -> authorize
            .requestMatchers(mvcMatcherBuilder.pattern("/" + appName + "/actuator/**")).hasRole("actuator")
            .requestMatchers(mvcMatcherBuilder.pattern("/" + appName + "/**")).hasRole("m2m")
            //Permit webhook refresh access without authorization
            .requestMatchers(mvcMatcherBuilder.pattern("/" + appName + "/monitor")).permitAll()
            .anyRequest()
            .authenticated()
        );

        http.oauth2ResourceServer(authorize ->
            authorize.jwt(jwt -> jwt.jwtAuthenticationConverter(keycloakJwtTokenConverter))
                .authenticationEntryPoint(customAuthenticationEntryPoint)
        );

        http.sessionManagement(session -> session
            .sessionCreationPolicy(stateless? SessionCreationPolicy.STATELESS: SessionCreationPolicy.IF_REQUIRED)
        );
        return http.build();
    }

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions