When using org.springframework.security:spring-security-oauth2-client (or org.springframework.boot:spring-boot-starter-oauth2-client) when I don't define any ReactiveOAuth2AuthorizedClientManager in my context, I get:
Consider defining a bean of type 'org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager' in your configuration.
Then all I need to do to make it work is to add this:
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ReactiveClientRegistrationRepository,
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository,
): ReactiveOAuth2AuthorizedClientManager {
return DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository,
)
}
and it all works (as it internally configures a default ReactiveOAuth2AuthorizedClientProvider which supports all grants).
The configuration above doesn't contain any customizations whatsoever, it seems to be a good candidate for a bean definition put into the context by default by the framework.
Comment From: jgrandja
@wujek-srujek ServerOAuth2AuthorizedClientExchangeFilterFunction and OAuth2AuthorizedClientArgumentResolver initialize a default ReactiveOAuth2AuthorizedClientManager if not provided. In the case of OAuth2AuthorizedClientArgumentResolver, it will be configured with a @Bean of type ReactiveOAuth2AuthorizedClientManager if available in the context.
I'm confused why you're getting the message:
Consider defining a bean of type 'org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager' in your configuration.
Can you provide a minimal sample demonstrating the error message?
Comment From: wujek-srujek
I attach a sample. The issue arises when I try to inject a ReactiveOAuth2AuthorizedClientManager somewhere so that I can fetch a token. When you run the app with ./mvnw spring-boot:run it will fail with:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.test.TestController required a bean of type 'org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager' in your configuration.
Go to src/main/kotlin/com/test/TestApplication.kt and remove the comments around authorizedClientManager and start the app again - it will start this time.
(The code still won't work because the client configuration is incomplete but that's not relevant for this issue).
The minimal bean that needs to be in the context is:
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ReactiveClientRegistrationRepository,
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository,
): ReactiveOAuth2AuthorizedClientManager {
return DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository,
)
}
which has no customizations whatsoever and is created only with other auto-configured beans, which makes it seem like a good candidate for a @ConditionalOnMissingBean(ReactiveOAuth2AuthorizedClientManager.class).
Comment From: wujek-srujek
Also, DefaultReactiveOAuth2AuthorizedClientManager instances is created internally when one uses this ServerOAuth2AuthorizedClientExchangeFilterFunction constructor (https://github.com/spring-projects/spring-security/blob/5.7.2/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java#L240) so Spring Security is already doing this in some places, just not in autoconfiguration.
Comment From: jgrandja
@wujek-srujek Thanks for the details. I now understand why you are getting this error message:
Parameter 0 of constructor in com.test.TestController required a bean of type 'org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager' that could not be found.
As mentioned in this comment:
ServerOAuth2AuthorizedClientExchangeFilterFunctionandOAuth2AuthorizedClientArgumentResolverinitialize a defaultReactiveOAuth2AuthorizedClientManagerif not provided
The framework initializes a default instance if a @Bean is not provided by the consuming application. This is a common pattern used within Spring Security. NOTE: The default instance does not need to be configured as a @Bean. The same case applies here as well since the framework does not require a ReactiveOAuth2AuthorizedClientManager @Bean.
This explains why you are getting the error message since TestController is trying to inject a ReactiveOAuth2AuthorizedClientManager @Bean but your application has not defined it.
You will need to define a ReactiveOAuth2AuthorizedClientManager @Bean in order for TestController to initialize properly. Additionally, you will need to use the same @Bean when constructing ServerOAuth2AuthorizedClientExchangeFilterFunction, however, OAuth2AuthorizedClientArgumentResolver will automatically pick up the @Bean so no additional configuration required there.
I'm going to close this as the current behaviour is expected and there is no need for the framework to define a default ReactiveOAuth2AuthorizedClientManager @Bean.
Comment From: wujek-srujek
I think we are misunderstanding each other. I understand what the error is and the cause, I'm just petitioning for the framework to make it go away, if possible.
I understand that I must provide the bean myself. But the framework could do it for me - it could provide a very basic ReactiveOAuth2AuthorizedClientManager (like the one I created myself, just using the other beans, which are provided by the framework) as help for the developer. It could be a conditional @Bean - if the user doesn't provide their own, just like you say. I think this would make using the framework even easier as now developers need to create the manager even though the framework is perfectly capable of doing so. I don't understand why the framework can conditionally provide default ReactiveClientRegistrationRepository and ServerOAuth2AuthorizedClientRepository beans, but not a ReactiveOAuth2AuthorizedClientManager?
Additionally, you will need to use the same @Bean when constructing ServerOAuth2AuthorizedClientExchangeFilterFunction, however, OAuth2AuthorizedClientArgumentResolver will automatically pick up the @Bean so no additional configuration required there.
I don't understand this part - here you can clearly see that a new and distinct instance of DefaultReactiveOAuth2AuthorizedClientManager is created every time an instance of ServerOAuth2AuthorizedClientExchangeFilterFunction is created using the 2-parameter constructor, and it is not exposed as a @Bean anywhere so it cannot be resolved as such by any OAuth2AuthorizedClientArgumentResolver? I also don't understand the role of OAuth2AuthorizedClientArgumentResolver in this case - it is for resolving method parameters (e.g. in controllers) and ServerOAuth2AuthorizedClientExchangeFilterFunction is a WebClient filter to automatically get a token and set it to the request, and it doesn't use OAuth2AuthorizedClientArgumentResolver for this.
Basically what I am thinking of (pseudocode that probably doesn't compile):
@ConditionalOnMissingBean(ReactiveOAuth2AuthorizedClientManager::class)
@Configuration
class Xyz {
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ReactiveClientRegistrationRepository,
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository,
): ReactiveOAuth2AuthorizedClientManager {
return DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository,
)
}
}
Comment From: jgrandja
@wujek-srujek I'll try to be more clear this time.
Let's assume a ReactiveOAuth2AuthorizedClientManager @Bean is registered by default by Spring Boot auto-configuration - just like ReactiveClientRegistrationRepository and ServerOAuth2AuthorizedClientRepository.
The OAuth2AuthorizedClientArgumentResolver would automatically use the default ReactiveOAuth2AuthorizedClientManager @Bean since the autowire logic is in place today. NOTE: This is the underlying component that implements @RegisteredOAuth2AuthorizedClient. See the reference documentation for details.
The ServerOAuth2AuthorizedClientExchangeFilterFunction can also potentially be autowired with the ReactiveOAuth2AuthorizedClientManager @Bean but this would not work without extra configuration because ServerOAuth2AuthorizedClientExchangeFilterFunction is typically not registered as a @Bean. Makes sense?
So even if there is a ReactiveOAuth2AuthorizedClientManager @Bean registered by default, it will only autowire with OAuth2AuthorizedClientArgumentResolver but not ServerOAuth2AuthorizedClientExchangeFilterFunction. This is what I meant by:
Additionally, you will need to use the same
@Beanwhen constructingServerOAuth2AuthorizedClientExchangeFilterFunction, however,OAuth2AuthorizedClientArgumentResolverwill automatically pick up the@Beanso no additional configuration required there.
IMO, it doesn't provide much value to provide a default ReactiveOAuth2AuthorizedClientManager @Bean when only one part is auto-configured and the other part (ServerOAuth2AuthorizedClientExchangeFilterFunction) still needs to have explicit configuration.
On a last note, the Spring Boot project would be responsible for providing a default ReactiveOAuth2AuthorizedClientManager @Bean so this issue should have been logged there instead. Either way, based on the reasoning as per above, it simply doesn't provide full value and IMO is easy enough for the application to define their own ReactiveOAuth2AuthorizedClientManager @Bean, which more than likely will be customized (from the defaults) in a production environment.
Comment From: wujek-srujek
Fair enough.
I would have one more question then, I'm not sure if it's OK to ask it here, I'll do it and you can show me the door if I shouldn't have ;) : without any explicit configuration, each ServerOAuth2AuthorizedClientExchangeFilterFunction instance will get it's own DefaultReactiveOAuth2AuthorizedClientManager, so for example if I prepare 3 WebClients with 1 of those filters each, there will be 3 instances of the manager. The code will compile, the application will run. Is this 'safe' to do, is it OK for there to be 3 distinct managers?
I suppose it is because the actual state related to authorized clients is kept in ReactiveClientRegistrationRepository and ServerOAuth2AuthorizedClientRepository and the managers will use the instances provided by Spring by default (or by the user if configured explicitly). Could you shed some light?
Comment From: jgrandja
@wujek-srujek DefaultReactiveOAuth2AuthorizedClientManager does not maintain any state so it is thread-safe and therefore the 3 instances would not pose a problem.
I suppose it is because the actual state related to authorized clients is kept in
ReactiveClientRegistrationRepositoryandServerOAuth2AuthorizedClientRepositoryand the managers will use the instances
Correct.
However, there is always a cleaner way to do things so I personally would define one ReactiveOAuth2AuthorizedClientManager @Bean and configure it wherever it is used. Either way works though.
Comment From: wujek-srujek
But then I would have to use the ServerOAuth2AuthorizedClientExchangeFilterFunction constructor which doesn't automatically forward HTTP 401 and 403 from the resource server and I would have to implement it myself. Are there plans on providing a constructor that does both, i.e. lets me provide my own manager AND have the automatic 401 and 403 forwarding?
BTW, I finally dawned on me that @ConditionalOnMisingBean etc. is not even your task, it's Spring Boot so the proposal should be issued there. Sorry for the noise.