spring-boot ReactiveClientRegistrationRepository not found
Asked Answered
P

8

12

Environment: Spring Boot 2.3.1, Java 11

I have tried out a few things already (also comparing with the sample-app by spring), but so far I have been unsuccessful in creating a WebClient that requires a ReactiveClientRegistrationRepository.

I get the following exception when starting up my spring-boot application:

required a bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository'

The way I understand spring-boot-autoconfigure it should use the ReactiveOAuth2ClientAutoConfiguration, as in the yml-file the required properties are given.

Following some code-snippets, I can provide more if something is missing to get the whole context

Main-Class

@Slf4j
@SpringBootApplication
@EnableConfigurationProperties(MyAppConfigurationProperties.class)
public class MyApp{

    public static void main(final String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

Configuration:

@Configuration
//@Import(ReactiveOAuth2ClientAutoConfiguration.class) // in the test it works with this, but should not be required: spring-boot-autoconfigure
public class MyRestClientConfig {

  @Bean
    WebClient myWebClient(WebClient.Builder builder, ReactiveClientRegistrationRepository clientRegistrations) {
//content not relevant to this problem
}
}

Configuration for security

@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableWebSecurity
@EnableWebFluxSecurity
public class SecurityConfig {
}

application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          providerid:
            authorization-grant-type: "client_credentials"
            client-id: "myClientId"
            client-secret: "mySecret"
            user-info-authentication-method: header
        provider:
          providerid:
            token-uri: "working token-uri"

I tried with different dependencies, so some may not be required. Which dependencies are actually required?

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.springframework.security.oauth.boot</groupId>-->
<!--            <artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
<!--        </dependency>-->
        <!--        <dependency>-->
        <!--            <groupId>org.springframework.security.oauth</groupId>-->
        <!--            <artifactId>spring-security-oauth2</artifactId>-->
        <!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-oauth2-client</artifactId>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.security</groupId>-->
<!--            <artifactId>spring-security-oauth2-core</artifactId>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.security</groupId>-->
<!--            <artifactId>spring-security-oauth2-jose</artifactId>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-webflux</artifactId>-->
<!--        </dependency>-->

In an integration-test the Spring-Boot-Application starts up

@EnableConfigurationProperties
@Import(ReactiveOAuth2ClientAutoConfiguration.class)  // in the test it works with this, but should not be required: spring-boot-autoconfigure, can be omitted if added in MyRestClientConfig
@ComponentScan(basePackages = "com.mycompany")
public class ManualClientTester {
}

EDIT 1: Debug of Positive Matches for Autoconfiguration

In test where it works:

============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------
   ReactiveOAuth2ClientAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'reactor.core.publisher.Flux', 'org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity', 'org.springframework.security.oauth2.client.registration.ClientRegistration' (OnClassCondition)
      - NoneNestedConditions 0 matched 1 did not; NestedCondition on ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.ServletApplicationCondition not a servlet web application (ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition)

   ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration matched:
      - OAuth2 Clients Configured Condition found registered clients myClientId (ClientsConfiguredCondition)
      - @ConditionalOnMissingBean (types: org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; SearchStrategy: all) did not find any beans (OnBeanCondition)

   ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration matched:
      - @ConditionalOnBean (types: org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; SearchStrategy: all) found bean 'clientRegistrationRepository' (OnBeanCondition)

   ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration#authorizedClientRepository matched:
      - @ConditionalOnMissingBean (types: org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; SearchStrategy: all) did not find any beans (OnBeanCondition)

   ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration#authorizedClientService matched:
      - @ConditionalOnMissingBean (types: org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; SearchStrategy: all) did not find any beans (OnBeanCondition)

When starting my spring boot application:

============================
CONDITIONS EVALUATION REPORT
============================

Negative matches:
-----------------

   ReactiveOAuth2ClientAutoConfiguration:
      Did not match:
         - NoneNestedConditions 1 matched 0 did not; NestedCondition on ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.ServletApplicationCondition found 'session' scope (ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition)
      Matched:
         - @ConditionalOnClass found required classes 'reactor.core.publisher.Flux', 'org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity', 'org.springframework.security.oauth2.client.registration.ClientRegistration' (OnClassCondition)

EDIT 2: After changing as suggested, I have now the following:

@EnableReactiveMethodSecurity
@EnableWebFluxSecurity
public class SecurityConfig {
}

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

Furthermore all used versions of spring-projects:

    <spring-amqp.version>2.2.7.RELEASE</spring-amqp.version>
    <spring-batch.version>4.2.4.RELEASE</spring-batch.version>
    <spring-boot.version>2.3.1.RELEASE</spring-boot.version>
    <spring-data-releasetrain.version>Neumann-SR1</spring-data-releasetrain.version>
    <spring-framework.version>5.2.7.RELEASE</spring-framework.version>
    <spring-hateoas.version>1.1.0.RELEASE</spring-hateoas.version>
    <spring-integration.version>5.3.1.RELEASE</spring-integration.version>
    <spring-kafka.version>2.5.2.RELEASE</spring-kafka.version>
    <spring-ldap.version>2.3.3.RELEASE</spring-ldap.version>
    <spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
    <spring-retry.version>1.2.5.RELEASE</spring-retry.version>
    <spring-security.version>5.3.3.RELEASE</spring-security.version>
    <spring-session-bom.version>Dragonfruit-RELEASE</spring-session-bom.version>
    <spring-ws.version>3.0.9.RELEASE</spring-ws.version>
    <spring.boot.version>2.3.1.RELEASE</spring.boot.version>
    <spring.cloud.version>Hoxton.SR5</spring.cloud.version>

The problem still exists.

Plagio answered 22/7, 2020 at 7:36 Comment(0)
L
10

I ran into the same problem and noticed that the application created a ClientRegistrationRepository instead of a ReactiveClientRegistrationRepository. Somewhere in Spring boot the @EnableWebSecurity was added (we need the @EnableWebFluxSecurity in this case).

To fix the problem I've added the following property:

spring.main.web-application-type: reactive

If you're also using @SpringBootTest to test your application you also need to add the property there.

@SpringBootTest(properties = ["spring.main.web-application-type=reactive]")

or by setting the web environment to NONE

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)

The reason why this happens is also explained in this answer: Error when using @EnableWebFluxSecurity in springboot

Leifeste answered 5/1, 2021 at 14:47 Comment(0)
C
3

I solved this by writing this code

@Bean("oauthWebClient")
  WebClient webClient(ClientRegistrationRepository clientRegistrations) {
    InMemoryReactiveClientRegistrationRepository registrationRepository = new InMemoryReactiveClientRegistrationRepository(
        clientRegistrations.findByRegistrationId("reg-id"));

    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
        new ServerOAuth2AuthorizedClientExchangeFilterFunction(
            registrationRepository,
            new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
    oauth.setDefaultClientRegistrationId("reg-id");
    return WebClient.builder()
        .filter(oauth)
        .build();
  }
Constellation answered 12/5, 2022 at 12:40 Comment(0)
P
2

I'm still not happy about my solution, but I ended up doing the following:

    @Bean
    public ReactiveClientRegistrationRepository reactiveClientRegistrationRepository(OAuth2ClientProperties oAuth2ClientProperties) {
        List<ClientRegistration> clientRegistrations = new ArrayList<>();

        // because autoconfigure does not work for an unknown reason, here the ClientRegistrations are manually configured based on the application.yml
        oAuth2ClientProperties.getRegistration()
                .forEach((k, v) -> {
                    String tokenUri = oAuth2ClientProperties.getProvider().get(k).getTokenUri();
                    ClientRegistration clientRegistration = ClientRegistration
                            .withRegistrationId(k)
                            .tokenUri(tokenUri)
                            .clientId(v.getClientId())
                            .clientSecret(v.getClientSecret())
                            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                            .build();
                    clientRegistrations.add(clientRegistration);
                });

        return new InMemoryReactiveClientRegistrationRepository(clientRegistrations);
    }

I use the spring-properties for OAuth and then create the ReactiveClientRegistrationRepository based on those properties.

Plagio answered 18/8, 2020 at 6:47 Comment(1)
The above answer from Jelle van Es is actually the right one, I also went down the ReactiveClientRegistrationRepository route initially, then rain into a whole world of hurt as soon as I started working with CORS and CSRF.Nonmaterial
B
2

Works this way:

@Bean
public WebClient webClient(ClientRegistrationRepository clientRegistrationRepository) {
    InMemoryReactiveClientRegistrationRepository registrationRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistrationRepository.findByRegistrationId("REG_ID"));
    InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(registrationRepository);
    AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager clientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(registrationRepository, clientService);

    return WebClient.builder()
            .baseUrl(BASEURL)
            .filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientManager))
            .build();
}

Properties:

spring.security.oauth2.client.registration.REG_ID.client-id=CLIENT_ID
spring.security.oauth2.client.registration.REG_ID.client-name=CLIENT_NAME
spring.security.oauth2.client.registration.REG_ID.client-secret=SECRET
spring.security.oauth2.client.registration.REG_ID.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.REG_ID.scope=SCOPE
spring.security.oauth2.client.provider.REG_ID.issuer-uri=PATH_TO_.well-known/openid-configuration_SITE

EDIT:

Add the following before the return statement:

ServerOAuth2AuthorizedClientExchangeFilterFunction clientExchangeFilterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientManager);
    clientExchangeFilterFunction.setDefaultClientRegistrationId("REG_ID");

And replace filter in the return statement with:

.filter(clientExchangeFilterFunction)
Braided answered 8/9, 2021 at 11:28 Comment(0)
P
0

Update:

Looks like the "user-info-authentication-method" (userInfoAuthenticationMethod) is part of the Provider and not the Registration. And please remove the double quotes too.

spring:
  security:
    oauth2:
      client:
        registration:
          providerid:
            authorization-grant-type: client_credentials
            client-id: myClientId
            client-secret: mySecret
        provider:
          providerid:
            token-uri: <working token-uri>
            user-info-authentication-method: header

Also a suggestion - to avoid possible conflicting/ incompatible dependencies, please use dependency management like this and try to have all spring boot starters. eg the spring security library comes as part of both spring-boot-starter-oauth2-client and spring-boot-starter-oauth2-resource-server:

    <dependencyManagement>
     <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.3.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Just these 2 dependencies should do the work: (these are picked from Gradle file, please change them to POM equivalent)

implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

For tests, probably you may need this:

testImplementation 'org.springframework.security:spring-security-test:5.3.3.RELEASE'

You should not mix the two together:

@EnableWebSecurity
@EnableWebFluxSecurity

If your application is reactive, then just use @EnableWebFluxSecurity.

And coming to @EnableGlobalMethodSecurity(securedEnabled = true) , this is described here, and is recommended to remove that and use this instead:

@EnableReactiveMethodSecurity
Priscilapriscilla answered 22/7, 2020 at 7:51 Comment(13)
I removed all dependencies related to oauth2 and now only use these three spring-boot-starter-webflux, spring-boot-starter-security & spring-security-oauth2-client Furthermore I removed the @EnableWebSecurity annotation. The problem is still there.Plagio
@Plagio did u remove the EnableGlobalMethodSecurity annotation also? And dont use the spring-boot-starter-security .The security libraries will come with spring-security-oauth2-client. If your app is a resource server also, then you need spring-boot-starter-oauth2-resource-serverPriscilapriscilla
I did both additionally now, and I still get the same error. I also think I need the spring-boot-starter-security for org.springframework.security.core.userdetails.UserDetailsServicePlagio
No @Plagio the spring-boot-starter-oauth2-client contains spring-security-corePriscilapriscilla
share your code after you have done all these changes. Please use friendpaste.com or github gists or some tool if you think your question is becoming very largePriscilapriscilla
ok, now I am using spring-boot-starter-oauth2-client, I updated my question above to reflect the way it looks now, I still get the same exceptionsPlagio
@Plagio I gave an update in my answer. Please try that outPriscilapriscilla
doesn't change anything, the Integration-Test still works whereas the spring-boot-application does not start. One of the differences between is the @ComponentScan where in the test I only have a subset (current module). Could there be any problem the way the maven-modules are separated? If potentially I could add some details on the structure of the projectPlagio
Ohh so you have multi modules. That might be the issue. Spring might not be able to do component scanPriscilapriscilla
I don't see why it should be an issue now, these modules have been there for a long time and everything has been workingPlagio
@Plagio often we think that "nothing has changed, then why it is not working", but it's always something like version upgrade, incompatibilities or some small configuration changePriscilapriscilla
Please paste as much code as you can (if not here, then friendpaste.com ) . And lets continue this discussion in a chat room. I am creating one.Priscilapriscilla
Let us continue this discussion in chat.Priscilapriscilla
B
0

it's difficult to provide relevant answer without a stacktrace.

Seem that Spring boot cant create ReactiveClientRegistrationRepository from your properties file.

Try to add a provider property on your client.

oauth2:
  client:
    registration:
      registrationId:
        provider: providerId
        client-id: clientId
        client-secret: secret
        authorization-grant-type: client_credentials
Bennet answered 25/10, 2020 at 22:3 Comment(0)
S
0

ReactiveClientRepositoryRegistration Bean needs to be defined explicitly. You can refer to the spring documentation https://docs.spring.io/spring-security/reference/reactive/oauth2/login/core.html#webflux-oauth2-login-register-reactiveclientregistrationrepository-bean

Sinapism answered 20/4, 2022 at 6:6 Comment(0)
L
0

In my case, I wanted to use the WebClient in a Spring Boot Servlet Stack, whereas the WebClient shall use Client Credentials to be able to access protected resources at external services. https://docs.spring.io/spring-security/site/docs/5.1.1.RELEASE/reference/html/servlet-webclient.html helped me to configure the web client to work in a servlet stack correctly :)

Liverpudlian answered 19/6, 2023 at 10:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.