-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
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();
}