8

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.

marco
  • 163
  • 1
  • 1
  • 9

8 Answers8

6

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

Jelle van Es
  • 613
  • 6
  • 20
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.

marco
  • 163
  • 1
  • 1
  • 9
  • 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. – user1859244 Dec 30 '21 at 12:06
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)
Oleg
  • 187
  • 3
  • 5
2

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();
  }
Kirby
  • 15,127
  • 10
  • 89
  • 104
Rahul Gupta
  • 1,079
  • 2
  • 15
  • 27
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
Abhinaba Chakraborty
  • 3,488
  • 2
  • 16
  • 37
  • 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. – marco Jul 22 '20 at 08:35
  • @marco 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-server` – Abhinaba Chakraborty Jul 22 '20 at 08:59
  • 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.UserDetailsService – marco Jul 22 '20 at 10:42
  • No @marco the `spring-boot-starter-oauth2-client` contains `spring-security-core` – Abhinaba Chakraborty Jul 22 '20 at 11:16
  • share your code after you have done all these changes. Please use https://friendpaste.com/ or github gists or some tool if you think your question is becoming very large – Abhinaba Chakraborty Jul 22 '20 at 11:18
  • 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 exceptions – marco Jul 23 '20 at 06:04
  • @marco I gave an update in my answer. Please try that out – Abhinaba Chakraborty Jul 23 '20 at 06:13
  • 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 project – marco Jul 23 '20 at 06:23
  • Ohh so you have multi modules. That might be the issue. Spring might not be able to do component scan – Abhinaba Chakraborty Jul 23 '20 at 06:24
  • I don't see why it should be an issue now, these modules have been there for a long time and everything has been working – marco Jul 23 '20 at 06:27
  • @marco 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 change – Abhinaba Chakraborty Jul 23 '20 at 06:29
  • Please paste as much code as you can (if not here, then https://friendpaste.com/ ) . And lets continue this discussion in a chat room. I am creating one. – Abhinaba Chakraborty Jul 23 '20 at 06:30
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/218411/discussion-between-abhinaba-chakraborty-and-marco). – Abhinaba Chakraborty Jul 23 '20 at 06:31
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
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

Mukesh
  • 1
  • 1
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 :)

Walnussbär
  • 567
  • 5
  • 19