-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Register RestOperations @Bean for OAuth 2.0 Client #8732
Register RestOperations @Bean for OAuth 2.0 Client #8732
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've provided some feedback inline.
@@ -96,4 +98,9 @@ private OAuth2ClientConfigurerUtils() { | |||
} | |||
return (!authorizedClientServiceMap.isEmpty() ? authorizedClientServiceMap.values().iterator().next() : null); | |||
} | |||
|
|||
static <B extends HttpSecurityBuilder<B>> RestOperations getRestOperationsBean(B builder) { | |||
return builder.getSharedObject(ApplicationContext.class).getBean( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it would fail if there isn't a bean by that name and type or if there is more than one bean of this type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason you think we should use a bean name here? We don't typically do the lookups by bean name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it would fail if there isn't a bean by that name and type or if there is more than one bean of this type
OAuth2ClientRestOperationsPostProcessor
defined in OAuth2ClientConfiguration
will register the @Bean
using OAuth2ClientBeanNames.REST_OPERATIONS
so it won't fail. It's expected to be registered.
Is there a reason you think we should use a bean name here?
I thought this is what we decided on, as per comment
Furthermore, an application may register one or more RestOperations
@Bean
so we need to ensure we pick up the correct RestOperations
@Bean
to be used for the client flows - hence the use of bean name.
Also, see tests in OAuth2ClientConfigurationTests
starting at loadContextWhenPostProcessedThenRestOperationsRegistered()
which demonstrates that the @Bean
is registered by default and how a user could override the @Bean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than relying on OAuth2ClientRestOperationsPostProcessor
could we just default the RestOperations
if none was found? This is what we do in other circumstances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what we do in other circumstances
Can you please be specific and provide a code sample I could reference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rwinch Are you referring to the way we conditionally @Import
using ImportSelector
? For example, OAuth2ClientConfiguration.OAuth2ClientWebMvcImportSelector
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We often try to get a Bean if it doesn't exist we create a default instance. An example is PasswordEncoder
@Autowired | ||
@Qualifier(OAuth2ClientBeanNames.REST_OPERATIONS) | ||
void configure(RestOperations restOperations) { | ||
ClientRegistrations.setRestOperations(restOperations); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting a static method from a bean definition is not a typical pattern I find in Spring. Instead, I'd expect to be able to provide the RestOperations to the builder. A few concerns around this design:
First, it is not easy to provide different RestOperation
s for different issuers in a thread safe way. The builders should ideally be thread safe. This is especially true, given the performance improvements being made to Spring and talks around making bean creation multi-threaded.
Another and more immediate concern is that it appears that there is likely a bean ordering issue here. What guarantees this will happen before ClientRegistrations
is used to create a client registration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting a static method from a bean definition is not a typical pattern I find in Spring.
Understood. This is the simplest solution I came up. We're kind of in a bind with this class since it's not designed to be a @Bean
and instead a utility class.
I'd expect to be able to provide the RestOperations to the builder
There is no builder in ClientRegistrations
? I don't understand. Can you clarify what you mean here?
Honestly, I'm not a fan of this static setter either and would be fine not exposing it. But then how do we address #7027? Do we introduce #5543 and deprecate ClientRegistrations
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no builder in ClientRegistrations? I don't understand. Can you clarify what you mean here?
Sorry for being unclear. An option would be to introduce a builder to ClientRegistrations that allows passing in the RestOperations
that would be used.
Honestly, I'm not a fan of this static setter either and would be fine not exposing it. But then how do we address #7027? Do we introduce #5543 and deprecate ClientRegistrations?
I think an option would be that Spring Security leverages the configured RestOperations to pass into the newly added builder in ClientRegistrations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An option would be to introduce a builder to ClientRegistrations
I'm not sure how this would work @rwinch? Maybe I'm missing something.
ClientRegistrations
was designed as a utility final
class with static
methods.
What would ClientRegistrations.Builder.build()
return? An instance of ClientRegistrations
?
|
||
@Bean | ||
BeanDefinitionRegistryPostProcessor defaultOAuth2AuthorizedClientManagerPostProcessor() { | ||
return new DefaultOAuth2AuthorizedClientManagerPostProcessor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having trouble seeing the value of registering OAuth2AuthorizedClientManager
with a known bean name.
Doing so with RestOperations
makes sense since there are many use cases when an application publishes a RestOperations
but doesn't intend it to be used by Spring Security.
What is it about OAuth2AuthorizedClientManager
that requires the same treatment as RestOperations
? Is this a needed pattern for other OAuth 2.0 Client beans?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OAuth2AuthorizedClientManager
is the central interface for client since 5.2. It is used primarily by ServletOAuth2AuthorizedClientExchangeFilterFunction
and OAuth2AuthorizedClientArgumentResolver
. It indirectly uses RestOperations
. The relationship chain is as follows:
OAuth2AuthorizedClientManager
has 1 or more OAuth2AuthorizedClientProvider
OAuth2AuthorizedClientProvider
has an OAuth2AccessTokenResponseClient
OAuth2AccessTokenResponseClient
has a RestOperations
If an application configures a custom RestOperations
then it will automatically be configured with the "default" OAuth2AuthorizedClientManager
and associated OAuth2AuthorizedClientProvider
's, and the user could leverage this default @Bean
instead of explicitly configuring it. It really is meant to be a convenience mechanism.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One important thing to note is that an application may configure more than 1 OAuth2AuthorizedClientManager
in their setup. For example, they may have a background service that performs client_credentials
grant and another OAuth2AuthorizedClientManager
that handles authorization_code
and refresh_token
in the web context.
The "default" OAuth2AuthorizedClientManager
will be wired into the OAuth2AuthorizedClientArgumentResolver
and may be used by ServletOAuth2AuthorizedClientExchangeFilterFunction
.
Closing this PR as it needs more work. I'll submit a new PR when I get a chance to revisit this and apply the necessary changes. |
Issue gh-8882
This PR addresses the Servlet implementation for OAuth 2.0 Client.
The changes in this PR will also be required for OAuth 2.0 Client WebFlux.
In addition to OAuth 2.0 Client, similar changes will need to be applied to OAuth 2.0 Resource Server and the JOSE stack.
I've done all the legwork as to which changes are required for the follow-up PR's:
OAuth 2.0 Client WebFlux
OAuth 2.0 Resource Server
NimbusOpaqueTokenIntrospector
OAuth 2.0 Resource Server WebFlux
NimbusReactiveOpaqueTokenIntrospector
JOSE
JOSE WebFlux