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

WebClient reusing inbound WebSecurity on outbound calls #10589

Closed
lashower opened this issue Dec 2, 2021 · 5 comments
Closed

WebClient reusing inbound WebSecurity on outbound calls #10589

lashower opened this issue Dec 2, 2021 · 5 comments
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)

Comments

@lashower
Copy link

lashower commented Dec 2, 2021

Spring Web Starter version: 2.4.12
Spring Web Version: 5.3.12

Issue:
My application implements both a oauth2 resource server and oauth2 client and both use different authentication methods, but I can only get one working at a given time.

My inbound Controllers are protected by a Microsoft ADFS Token which is configured in a WebSecurityConfigurerAdapter that includes a jwt token.

I make an outbound call to another application that uses Pivotal Cloud Foundry UAA service client. When I make the call to that application with a Controller that is protected by my WebSecurityConfigurerAdaptor, it fails with this exception:

java.lang.IllegalArgumentException: principalName cannot be empty
	at org.springframework.util.Assert.hasText(Assert.java:289) ~[spring-core-5.3.12.jar:5.3.12]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ? Request to POST https://externalURL/v1/documents/upload [DefaultWebClient]
Stack trace:
		at org.springframework.util.Assert.hasText(Assert.java:289) ~[spring-core-5.3.12.jar:5.3.12]
		at org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService.loadAuthorizedClient(InMemoryOAuth2AuthorizedClientService.java:78) ~[spring-security-oauth2-client-5.4.9.jar:5.4.9]
		at org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository.loadAuthorizedClient(AuthenticatedPrincipalOAuth2AuthorizedClientRepository.java:80) ~[spring-security-oauth2-client-5.4.9.jar:5.4.9]
		at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:152) ~[spring-security-oauth2-client-5.4.9.jar:5.4.9]
		at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$authorizeClient$24(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:552) ~[spring-security-oauth2-client-5.4.9.jar:5.4.9]
		at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:86) ~[reactor-core-3.4.11.jar:3.4.11]

I have found some outdated examples on how to tune my WebSecurityConfigurerAdapter (when not using jwt) to set a default principalName, but I think it is weird that my outbound calls are using configurations set up for my inbound calls.

Here is the WebClient code I am using

package com.ford.workflow.archive.config;

import java.time.Duration;
import java.util.Optional;
import java.util.function.Consumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

import com.ford.workflow.archive.util.AdOAuth2ClientCredentialsGrantRequestEntityConverter;

import reactor.netty.http.client.HttpClient;

@Configuration
public class WebClientConfiguration {
    @Autowired
    ClientRegistrationRepository clientRegistrationRepository;

    @Autowired
    OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository;

    @Value("${external.endpoint}")
    String serviceUri;

    @Value("${web.client.response.timeout}")
    private long timeout;

    @Bean
    @Qualifier("document-upload-service-client")
    public WebClient customerServiceApicClient() {
        return WebClient.builder()
                .baseUrl(serviceUri).clientConnector(new ReactorClientHttpConnector(HttpClient.create().responseTimeout(Duration.ofSeconds(timeout))))
                .apply(oauth2Configuration("external-service-client", Optional.empty()))
                .build();
    }

    @Bean
    public WebClient getWebClient() {
        var httpClient = getHttpClient();
        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }

    private HttpClient getHttpClient() {
        return HttpClient.create()
                .responseTimeout(Duration.ofSeconds(5));
    }

    private Consumer<WebClient.Builder> oauth2Configuration(String clientRegistrationId, Optional<String> resource) {

        OAuth2AuthorizedClientManager oAuth2ClientManager = getOAuth2AuthorizedClientManager(resource);

        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(oAuth2ClientManager);
        oauth2.setDefaultClientRegistrationId(clientRegistrationId);

        return oauth2.oauth2Configuration();
    }

    public OAuth2AuthorizedClientManager getOAuth2AuthorizedClientManager(Optional<String> resource) {
        DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
        clientCredentialsTokenResponseClient.setRequestEntityConverter(new AdOAuth2ClientCredentialsGrantRequestEntityConverter(resource));

        ClientCredentialsOAuth2AuthorizedClientProvider ccAuthorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
        ccAuthorizedClientProvider.setAccessTokenResponseClient(clientCredentialsTokenResponseClient);
        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, oAuth2AuthorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(ccAuthorizedClientProvider);

        return authorizedClientManager;
    }
}

Is there a different class or tuning for my WebClient that will not reuse components of my inbound WebSecurity?

@rwinch rwinch transferred this issue from spring-projects/spring-framework Dec 6, 2021
@rwinch rwinch assigned jgrandja and unassigned jgrandja Dec 6, 2021
@rwinch rwinch added the status: waiting-for-triage An issue we've not yet triaged label Dec 6, 2021
@rwinch rwinch assigned jgrandja and unassigned rwinch Dec 7, 2021
@jgrandja
Copy link
Contributor

jgrandja commented Dec 9, 2021

@lashower I suspect the issue is related to a misconfiguration. Take a look at this sample, which has a Resource Server that is configured as a Client as well.

See the Controller method, which receives the JwtAuthenticationToken and then makes an outbound call using WebClient.

If you're still having issues after looking at the sample, please provide a minimal reproducible sample so I can efficiently troubleshoot your issue.

@jgrandja jgrandja added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-triage An issue we've not yet triaged labels Dec 9, 2021
@spring-projects-issues
Copy link

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

@spring-projects-issues spring-projects-issues added the status: feedback-reminder We've sent a reminder that we need additional information before we can continue label Dec 16, 2021
@lashower
Copy link
Author

First, Thank you so much for your help. I worked with 2 architects and they had no clue on how to fix this.
While my simple attempt to add in just the .oauth2Client(Customizer.withDefaults()) didn't resolve the issue, I am working on making my code align more to what you gave me.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue status: feedback-reminder We've sent a reminder that we need additional information before we can continue labels Dec 21, 2021
@jgrandja jgrandja added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jan 3, 2022
@spring-projects-issues
Copy link

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

@spring-projects-issues spring-projects-issues added the status: feedback-reminder We've sent a reminder that we need additional information before we can continue label Jan 10, 2022
@spring-projects-issues
Copy link

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

@spring-projects-issues spring-projects-issues removed status: waiting-for-feedback We need additional information before we can continue status: feedback-reminder We've sent a reminder that we need additional information before we can continue labels Jan 17, 2022
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)
Projects
None yet
Development

No branches or pull requests

4 participants