I am attempting to get a bearer token via a webclient
with the following setup for an integration test of a secured resource server in a servlet application.
spring:
security:
oauth2:
client:
registration:
idp:
clientId: id
clientSecret: secret
authorization-grant-type: client_credentials
scope: read
provider:
idp:
authorization-uri: myidp/authorization.oauth2
token-uri: myidp/token.oauth2
user-info-uri: myidp/userinfo.openid
user-name-attribute: name
And beans,
@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrations,
OAuth2AuthorizedClientRepository authorizedClients) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, authorizedClients);
// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
// oauth.setDefaultOAuth2AuthorizedClient(true);
// (optional) set a default ClientRegistration.registrationId
// oauth.setDefaultClientRegistrationId("client-registration-id");
return WebClient.builder().apply(oauth.oauth2Configuration()).build();
}
and autowiring the webclient to a test and calling it like so,
webClient.get().uri("http://localhost:" + port + "/web/it")
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId("idp")).retrieve()
.bodyToMono(String.class).block();
It is my assumption the exchange function would either get an access token if available, or do a call to get a new one from the IDP. However it will always fail as the HttpSessionOAuth2AuthorizedClientRepository
as the HttpServletRequest
is null.
With the overall test looking like, this feeds into an autoconfiguration to configure some beans for an IDP provider.
@SpringBootTest(classes = WebITApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SpringExtension.class)
@ActiveProfiles("web-it")
class WebJwtIt {
@LocalServerPort
private int port;
@Autowired
private WebClient webClient;
@Test
void testIdpJwt() {
String response = webClient.get().uri("http://localhost:" + port + "/web/it")
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId("ping")).retrieve()
.bodyToMono(String.class).block();
assertThat(response).isEqualTo("pass");
}
@RestController
@SpringBootApplication
@ImportAutoConfiguration(IdpAutoConfiguration.class)
static class WebITApplication implements IdpSecurityAdapter {
@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrations,
OAuth2AuthorizedClientRepository authorizedClients) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, authorizedClients);
// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
// oauth.setDefaultOAuth2AuthorizedClient(true);
// (optional) set a default ClientRegistration.registrationId
// oauth.setDefaultClientRegistrationId("client-registration-id");
return WebClient.builder().apply(oauth.oauth2Configuration()).build();
}
public static void main(String args[]) {
new SpringApplicationBuilder().profiles("web-it").build().run(WebITApplication.class, args);
}
@GetMapping
public String secured() {
return "secured";
}
@GetMapping("/web/it")
public String securedOne() {
return "pass";
}
@Override
public void configure(final HttpSecurity httpSecurity) throws IdpSecurityAdapterException {
try {
httpSecurity.oauth2Client();
} catch (Exception e) {
throw new IdpSecurityAdapterException("Failed to Configure Oauth2Client", e);
}
}
@Override
public IdpProvider getIdpProvider() {
return IdpProvider.MyIdp;
}
}
Is there anyway for a webclient to get the token for me and add it to the request? I know this was possible with the spring-security-oauth:OAuthRestTemplate
and reading the documentation I thought it was possible with a web client.
The problem here is that you are not instancing your WebClient the right way.
As you are on the client side, you do not have access to an OAuth2AuthorizedClientRepository
. This bean is supposed to be linked to a resource sever on which you log into using the .oauth2Login()
method declaration on the HttpSecurity
configuration. These details are explained here : Spring Security 5 Oauth2 Login.
Again, you are on the client side so what you need is an exchange filter function which will trigger a request to an authorization server to get a JWT token. You can use the ServerOAuth2AuthorizedClientExchangeFilterFunction
instead.
You better use a WebClientCustomizer
to add the exchange filter function in the WebClient filter. Why ? Simply because injecting in your Spring application a WebClient.Builder
will allow you accessing native metrics linked to web exchanges.
Hence, you will build your WebClient using a new instance of an UnAuthenticatedServerOAuth2AuthorizedClientRepository
bean like this :
// Use injection to get an in-memory reposiroty or client registrations
@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrations) {
// Provides support for an unauthenticated user such as an application
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
// Build up a new WebClientCustomizer implementation to inject the oauth filter
// function into the WebClient.Builder instance
return new WebClientSecurityCustomizer(oauth);
}
As you are on the client side, you are not associating a user to your process, that's why you can not use any authorized client repository bean instanciation. Check on Spring Security documentation : Spring Security documentation : Class UnAuthenticatedServerOAuth2AuthorizedClientRepository.
I tried to sum up a demo case in the following GitHub project : GitHub - Spring Security OAuth2 Machine-To-Machine scenario.
I hope this gives you more insights on WebClient configuration. Please ask if you have any question.