Summary
There is no way currently to pass custom form parameters as part of OAuth2ClientCredentialsGrantRequestEntityConverter: https://github.com/spring-projects/spring-security/blob/master/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverter.java
Some IDPs (Auth0 for example) require an "audience" value.
Comment From: jgrandja
@fritzdj This is actually possible. You would need to supply DefaultClientCredentialsTokenResponseClient.setRequestEntityConverter() with an implementation of Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>>, similar to how OAuth2ClientCredentialsGrantRequestEntityConverter is implemented.
I'm going to close this issue as answered.
Comment From: fritzdj
@jgrandja, thanks for the quick response. How is it possible to do that though? We are not seeing a way to update that / the class that uses it is not autowired.
Comment From: jgrandja
Here is a sample config
@Configuration
public class WebClientConfig {
@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
// TODO Obtain your custom Converter
Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> customRequestEntityConverter = null;
DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
clientCredentialsTokenResponseClient.setRequestEntityConverter(customRequestEntityConverter);
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrationRepository, authorizedClientRepository);
oauth2.setClientCredentialsTokenResponseClient(clientCredentialsTokenResponseClient);
return WebClient.builder()
.apply(oauth2.oauth2Configuration())
.build();
}
}
Comment From: fritzdj
Thanks again @jgrandja, this is great. What about if we are not using WebFlux?
Comment From: fritzdj
To be more specific, we want to use the @RegisteredOAuth2AuthorizedClient annotation.
Comment From: jgrandja
@fritzdj In that case, you can use this @Configuration
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final ClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientRepository authorizedClientRepository;
public WebConfig(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientRepository = authorizedClientRepository;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
clientCredentialsTokenResponseClient.setRequestEntityConverter(
new CustomClientCredentialsGrantRequestEntityConverter());
OAuth2AuthorizedClientArgumentResolver authorizedClientArgumentResolver =
new OAuth2AuthorizedClientArgumentResolver(this.clientRegistrationRepository, this.authorizedClientRepository);
authorizedClientArgumentResolver.setClientCredentialsTokenResponseClient(clientCredentialsTokenResponseClient);
argumentResolvers.add(authorizedClientArgumentResolver);
}
private static class CustomClientCredentialsGrantRequestEntityConverter implements Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> {
private final Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> defaultRequestEntityConverter =
new OAuth2ClientCredentialsGrantRequestEntityConverter();
@Override
public RequestEntity<?> convert(OAuth2ClientCredentialsGrantRequest source) {
return this.defaultRequestEntityConverter.convert(source);
}
}
}
Comment From: jgrandja
@fritzdj I just added issue #6572 to allow for an easier configuration from the one I provided. If you're interested in submitting a PR for this I'd be happy to guide you through the process?
Comment From: fritzdj
@jgrandja, I would love to give that a shot. I see the contributor guidelines for the project so I will try this out.
Comment From: wangyue82lf
Hi @jgrandja ,
I have same the issue, but we need to use HttpSecurity.oauth2Login() custom header for client_credential.
But I found TokenEndpointConfig.accessTokenResponseClient() must use OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>.
I want some like the one below. Do you have any suggest?
`
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/getToken")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2Login(oauth2 ->
oauth2.authorizationEndpoint(authorization -> authorization.authorizationRequestResolver(
new CustomAuthorizationRequestResolver(this.clientRegistrationRepository(), "/getToken")))
.redirectionEndpoint(redirection -> redirection.baseUri("/getToken"))
.tokenEndpoint(token -> token.accessTokenResponseClient(this.accessTokenResponseClient()))
.userInfoEndpoint(userInfo -> System.out.println(userInfo)));
return http.build();
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient() {
DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
clientCredentialsTokenResponseClient.setRequestEntityConverter(new CustomRequestEntityConverter());
OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
tokenResponseHttpMessageConverter.setAccessTokenResponseConverter(new CustomTokenResponseConverter());
RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
clientCredentialsTokenResponseClient.setRestOperations(restTemplate);
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = clientCredentialsTokenResponseClient;
return accessTokenResponseClient;
}`
Comment From: jgrandja
@wangyue82lf See the following references on how to customize DefaultClientCredentialsTokenResponseClient:
Comment From: wangyue82lf
@wangyue82lf See the following references on how to customize
DefaultClientCredentialsTokenResponseClient:
@jgrandja Thank you for your help. I'm using JDBC implementation for saving token. But after getting token response. The database did not save refresh token, although I returned refresh token. I need to use refresh as below. but it's not working. I also see the document. How do I make refresh work?
`
@Bean
public ClientRegistration clientRegistration() {
return ClientRegistration
.withRegistrationId("Custom")
.tokenUri("XXXX")
.clientId("Custom")
.clientSecret("XXXX")
.registrationId("Custom")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
// keep save token in memory
return new InMemoryClientRegistrationRepository(this.clientRegistration());
}
@Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService(JdbcOperations jdbcOperations) {
// keep save token in DB
return new JdbcOAuth2AuthorizedClientService(jdbcOperations, this.clientRegistrationRepository());
}
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests -> authorizeRequests.antMatchers(HttpMethod.POST, "/getToken").authenticated())
.headers()
.httpStrictTransportSecurity()
.disable()
.and()
.oauth2Login();
return http.build();
}
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
CustomOAuth2ClientCredentialsTokenResponseClient credentialsClient,
CustomOAuth2ClientRefreshTokenResponseClient refreshClient) {
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
.builder()
.clientCredentials(clientCredentials -> clientCredentials.accessTokenResponseClient(credentialsClient))
.refreshToken(refreshToken -> refreshToken.accessTokenResponseClient(refreshClient))
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository,
oAuth2AuthorizedClientService);
manager.setAuthorizedClientProvider(authorizedClientProvider);
return manager;
}
@Bean
public CustomOAuth2ClientCredentialsTokenResponseClient credentialsClient(client client) {
return new CustomOAuth2ClientCredentialsTokenResponseClient(client);
}
@Bean
public CustomOAuth2ClientRefreshTokenResponseClient refreshClient(client client) {
return new CustomOAuth2ClientRefreshTokenResponseClient(client);
}
`