Skip to content
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

Closed

Conversation

jgrandja
Copy link
Contributor

@jgrandja jgrandja commented Jun 19, 2020

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

WebClientReactiveAuthorizationCodeTokenResponseClient
WebClientReactiveRefreshTokenTokenResponseClient
WebClientReactiveClientCredentialsTokenResponseClient
WebClientReactivePasswordTokenResponseClient

DefaultReactiveOAuth2UserService

OAuth 2.0 Resource Server

NimbusOpaqueTokenIntrospector

OAuth 2.0 Resource Server WebFlux

NimbusReactiveOpaqueTokenIntrospector

JOSE

NimbusJwtDecoder
NimbusJwtDecoderJwkSupport
JwtDecoderProviderConfigurationUtils
JwtDecoders

JOSE WebFlux

NimbusReactiveJwtDecoder
ReactiveRemoteJWKSource
ReactiveJwtDecoders

@jgrandja jgrandja requested review from jzheaux and rwinch June 19, 2020 20:20
@jgrandja jgrandja added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement labels Jun 19, 2020
@jgrandja jgrandja added this to the 5.4.0-M2 milestone Jun 19, 2020
Copy link
Member

@rwinch rwinch left a 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(
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

@jgrandja jgrandja Jun 22, 2020

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

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

@jgrandja jgrandja Jun 30, 2020

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?

Copy link
Member

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);
Copy link
Member

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?

Copy link
Contributor Author

@jgrandja jgrandja Jun 22, 2020

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?

Copy link
Member

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.

Copy link
Contributor Author

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();
Copy link
Contributor

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?

Copy link
Contributor Author

@jgrandja jgrandja Jun 26, 2020

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.

Copy link
Contributor Author

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.

@jgrandja
Copy link
Contributor Author

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.

@jgrandja jgrandja closed this Nov 18, 2021
@jgrandja jgrandja added the status: declined A suggestion or change that we don't feel we should currently apply label Nov 18, 2021
@jgrandja jgrandja deleted the gh-5607-default-restoperations branch July 21, 2023 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: declined A suggestion or change that we don't feel we should currently apply type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants