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 to be used as default for oauth2-client flows #5607

Closed
jgrandja opened this issue Jul 30, 2018 · 13 comments
Closed
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue

Comments

@jgrandja
Copy link
Contributor

We should register a RestOperations @Bean in OAuth2ClientConfiguration that is configured with default settings and is used as the default via setRestOperations(restOperations) in the various oauth2-client flows - see #5601.

The well-known @Bean should be named springSecurityOAuth2RestOperations and automatically be injected/used as the default in all oauth2-client flows. The user has the ability to register a @Bean with the same name, which will effectively override the default.

For now, the default @Bean will be configured with default connect and read timeout settings for 30 secs. However, the user may override this @Bean and provide custom settings for Proxy, SSL, HTTP Client backing implementation, etc.

@jgrandja jgrandja added type: enhancement A general enhancement in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) labels Jul 30, 2018
@jgrandja jgrandja added this to the 5.1.0.RC1 milestone Jul 30, 2018
@rwinch
Copy link
Member

rwinch commented Jul 30, 2018

I'm not sure we should be quite as granular as springSecurityOAuth2RestOperations. Otherwise we run into a pretty slippery slope on bean names. Do we add springSecurityOAuth2ClientRestOperations, springSecurityOAuth2ResourceServerRestOperations, etc? Likely springSecurityRestOperations is good enough or potentially even just a Bean rather than the name.

@jgrandja
Copy link
Contributor Author

Likely springSecurityRestOperations is good enough or potentially even just a Bean rather than the name.

I'm fine with springSecurityRestOperations. But I'm not sure about...

even just a Bean rather than the name

How do we know RestOperations bean is meant for oauth2-client flows? The user may have configured a bean for another use case within the app.

@jgrandja jgrandja modified the milestones: General Backlog, 5.1.0.RC2 Aug 21, 2018
@rwinch rwinch closed this as completed in 28537fa Sep 7, 2018
@rwinch rwinch reopened this Sep 7, 2018
@rwinch rwinch modified the milestones: 5.1.0.RC2, 5.1.0 Sep 7, 2018
@jgrandja jgrandja modified the milestones: 5.1.0, 5.1.1 Sep 19, 2018
@jgrandja jgrandja modified the milestones: 5.1.1, 5.1.x Oct 12, 2018
@jgrandja jgrandja modified the milestones: 5.1.x, 5.2.x Oct 19, 2018
@jgrandja jgrandja removed this from the 5.2.x milestone Apr 23, 2019
@jgrandja jgrandja added this to the 5.3.x milestone Nov 18, 2019
@jgrandja jgrandja modified the milestones: 5.3.x, 5.4.x Feb 10, 2020
@jgrandja
Copy link
Contributor Author

jgrandja commented Apr 13, 2020

Instead of registering a well-known @Bean name, we should discover the @Bean in OAuth2ClientConfiguration by type only.

For the @Bean type, we should consider borrowing the design from JwtDecoderFactory for a possible solution.

For example, OidcIdTokenDecoderFactory implements JwtDecoderFactory, which is supplied a ClientRegistration to determine the type of JwtDecoder to create for the specific client.

public interface JwtDecoderFactory<C> {
	// context - provides contextual information used to create a specific JwtDecoder
	JwtDecoder createDecoder(C context);
}

public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
	@Override
	public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
                ....
	}

We could have a similar design for creating RestOperations, as follows:

public interface RestOperationsFactory<C> {
	// context - provides contextual information used to create a specific RestOperations
	RestOperations createRestOperations(C context);
}

public final class OAuth2ClientRestOperationsFactory implements RestOperationsFactory<ClientRegistration> {
	@Override
	public RestOperations createRestOperations(ClientRegistration clientRegistration) {
                ....
	}

If a user needs to provide a configured RestOperations to be used in the various oauth2-client flows (eg. ClientRegistrations) then they can register a RestOperationsFactory<ClientRegistration> @Bean, which will be discovered in OAuth2ClientConfiguration and directly configured in the components implementing the oauth2-client flows.

For example, the RestOperations created from the RestOperationsFactory<ClientRegistration> @Bean would be configured directly to DefaultAuthorizationCodeTokenResponseClient.setRestOperations().

We'll need to figure out how to configure the RestTemplate in ClientRegistrations. We could expose public static void setRestOperations(RestOperations restOperations) and the RestOperationsFactory<ClientRegistration> @Bean would supply the RestOperations. But the issue here is that there is no ClientRegistration context that can be provided to RestOperationsFactory<ClientRegistration> so some further thought is required for this case.

@rwinch
Copy link
Member

rwinch commented Apr 14, 2020

Again I think this is too granular. We don't want to add factories for everything that can be created.

@jgrandja
Copy link
Contributor Author

@rwinch Can you elaborate why you think this design is too granular?

We don't want to add factories for everything that can be created

Why do you think a factory is not suitable here? FYI, we only have one factory defined in OAuth 2.0 codebase thus far - JwtDecoderFactory. I'm just trying to understand as your comment does not provide much of an explanation.

Is there another solution you have in mind that you can propose?

@rwinch
Copy link
Member

rwinch commented Apr 16, 2020

If you are creating RestOperationsFactory<ClientRegistration> you might as well just define the DefaultAuthorizationCodeTokenResponseClient bean in the first place.

Perhaps we could allow users to define their own ResourceRetriever and that would be used throughout Nimbus? Although that likely misses things like creating the ClientRegistrations.

Alternatively, I could see looking for a RestTemplate as a default for all of Spring Security, but I don't want to have custom factories for each Bean

@jgrandja
Copy link
Contributor Author

jgrandja commented Apr 17, 2020

If you are creating RestOperationsFactory you might as well just define the DefaultAuthorizationCodeTokenResponseClient bean in the first place.

Registering DefaultAuthorizationCodeTokenResponseClient (and other clients) will still involve a lot of manual configuration from the user, which is what we are trying to solve. The factory approach I have in mind will be a lot less configuration from user. I don't feel I'm getting my idea across. It might be best to put together a draft PR to demonstrate.

I could see looking for a RestTemplate as a default for all of Spring Security

Ok, so it sounds like you are favouring a well-known bean name, as per comment?

@rwinch
Copy link
Member

rwinch commented Apr 17, 2020

Yes. Can we just have a single well known bean name for all of security? I doubt that most setups will need multiple different RestTemplate instances. If they do, they can always define the beans explicitly

@xenoterracide
Copy link

one possible exception, tests.

@larsw
Copy link

larsw commented May 5, 2020

I don't have any strong opinions with regards to the design of the API, but we've met this obstacle while trying to customize the trust store to be used for the outgoing connection against the authorization server.

After reading through the source, we found out that the RestTemplate instance was new'ed locally, and as far as we could tell, was not externally customizable.

It was quite confusing to discover that the client used RestTemplate internally with one way of doing customization, while the the client we're using to do the actual API call is using WebFlux with another customization strategy :-/

a) Is there any known workarounds for doing this (apart from using the java command line option to set the trust store or change the "global" cacerts file) ?
b) If not, will this issue be fixed in the foreseeable future?

@phisad
Copy link

phisad commented May 14, 2020

We also ran into a similar issue, when facing a http proxy to be configured.

First, we simply put the Spring Security 5.2.2.RELEASE modules spring-security-oauth2-resource-server and spring-security-oauth2-jose into the classpath to get the default configurations. (Spring Boot 2.2.5.RELEASE)

This lead to the following exception on startup, because the initial openid-configuration was not fetchable because of the companies internal http proxy.

Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://login.microsoftonline.com/fcb2b37b-5da0-466b-9b83-0014b67a7c78/v2.0/.well-known/openid-configuration": Operation timed out (Connection timed out); nested exception is java.net.ConnectException: Operation timed out (Connection timed out)

Then we set the JVM arguments '-Dhttps.proxyHost' and '-Dhttps.proxyPort' which lead to a successful retrieval of the openid-configuration.

Nonetheless, another error occured now at runtime, when an incoming token had to be validated. The NimbusReactiveJwtDecoder is trying to fetch the jwks using a WebClient.

Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Operation timed out: login.microsoftonline.com/40.126.1.166:443
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint Request to GET https://login.microsoftonline.com/fcb2b37b-5da0-466b-9b83-0014b67a7c78/discovery/v2.0/keys [DefaultWebClient]

The reason for this is that we are using Spring Cloud Gateway which requires Webflux. Now, with Webflux on the classpath, the reactive WebClient is initialized. In constrast to this, the initial retrieval went fine, because JwtDecoderProviderConfigurationUtils uses a RestTemplate which is applying the JVM args.

The main problem here is as far as I can see that the Netty WebClient does not honor the JVM arguments. (reactor/reactor-netty#887 (comment))

So we had to configure the WebClient to be aware of the proxy. Unfortunately, the NimbusReactiveJwtDecoder is by default using a processor() with a web client created by WebClient.create() instead of using a pre-configured one.

Thus in the end, we had to provide a fully customized decoder. We copied the default configuration procedure and applied builder.webClient(proxyWebClient()) on the builder.

@Bean
public ReactiveJwtDecoder jwtDecoder() {
    final Map<String, Object> configuration = JwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation);
    JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, oidcIssuerLocation);
    final OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(oidcIssuerLocation);
    final NimbusReactiveJwtDecoder jwtDecoder = withJwkSetUri(configuration.get("jwks_uri").toString())
        .webClient(proxyWebClient())
        .build();
    jwtDecoder.setJwtValidator(jwtValidator);
    return jwtDecoder;
}

It would be really nice to make at least webClient(...) available to ReactiveJwtDecoders or NimbusReactiveJwtDecoder. The best option would be a spring-provided workaround for creating the WebClient or making the WebClient automatically aware of proxy arguments.

@Budlee
Copy link
Contributor

Budlee commented May 24, 2020

One point here is that as the resource server can now support multiple tennants you may not want to use the same Rest Template for each issuer. You may have different setups for each one (different proxies, different security commections).
I have found an issue with this in the JwtDecoderProviderConfigurationUtils.
the RestTemplate not being exposed is a problem.

I have made a draft PR to open it up. By no means do I think this is the correct solution,
however it is a start on being able to open this up. would be great to get some feedback on thoughts
#8588

@jgrandja
Copy link
Contributor Author

Closing in favour of #8882. Please provide any additional feedback there.

@jgrandja jgrandja removed this from the 5.4.x milestone Jul 28, 2020
@jgrandja jgrandja added status: duplicate A duplicate of another issue and removed type: enhancement A general enhancement labels Jul 28, 2020
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: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

6 participants