-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Register RestOperations @Bean for OAuth 2.0 Client #8732
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
Conversation
rwinch
left a comment
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.
| } | ||
|
|
||
| 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 RestOperations 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
NimbusOpaqueTokenIntrospectorOAuth 2.0 Resource Server WebFlux
NimbusReactiveOpaqueTokenIntrospectorJOSE
JOSE WebFlux