Reputation: 6043
I am using Spring Security 5.5 to perform an access token request and recently upgraded to 5.5.1, and now my client secret is rejected by my OAuth 2.0 provider. This was caused by a bug fix to URL encode client credentials per RFC 6749 Section 2.3.1.
Since my OAuth 2.0 provider is non-compliant, I would like to revert to the old behavior in Spring Security 5.5.0, and send my client credentials without URL encoding.
From the reference documentation, if I define a @Bean
of type OAuth2AuthorizedClientManager
:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
// @formatter:off
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// @formatter:on
return authorizedClientManager;
}
How do I configure it to use a custom converter to set the credentials?
Note: Relates to this question but addresses Servlet support instead of Reactive support with WebClient.
Upvotes: 5
Views: 6418
Reputation: 6043
Suppose you have the following configuration:
spring:
security:
oauth2:
client:
registration:
test-client:
provider: spring
client-id: aladdin
client-secret: "open sesame"
authorization-grant-type: client_credentials
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: resource:read
provider:
spring:
authorization-uri: http://auth-server:9000/oauth/authorize
token-uri: http://auth-server:9000/oauth/token
In the example from the OP, the OAuth2AuthorizedClientManager
supports client_credentials
grant to make an access token request. This would be useful for ex. if we want to implement the following (fictional) endpoint:
@RestController
public class TokenController {
@GetMapping("/token")
public OAuth2AccessToken token(@RegisteredOAuth2AuthorizedClient("test-client") OAuth2AuthorizedClient testClient) {
return testClient.getAccessToken();
}
}
The OAuth2AuthorizedClientManager
provides the OAuth2AuthorizedClient
that is injected in this example. It can be configured with a custom converter as follows:
Note: The following is an expanded example adapted from the Client Credentials section of the reference documentation.
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
// @formatter:off
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials((builder) ->
builder.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient())
.build())
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// @formatter:on
return authorizedClientManager;
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
// @formatter:off
OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
new OAuth2ClientCredentialsGrantRequestEntityConverter();
requestEntityConverter.setHeadersConverter(headersConverter());
DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
new DefaultClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
// @formatter:on
return accessTokenResponseClient;
}
private static <T extends AbstractOAuth2AuthorizationGrantRequest> Converter<T, HttpHeaders> headersConverter() {
// @formatter:off
Converter<T, ClientRegistration> clientRegistrationConverter =
AbstractOAuth2AuthorizationGrantRequest::getClientRegistration;
return clientRegistrationConverter
.andThen((clientRegistration) -> {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
headers.setContentType(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
return headers;
});
// @formatter:on
}
If we wanted to support the authorization_code
grant for logging into the application, we could use the following configuration (with the above methods and @Bean
definitions contained within):
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2Login(oauth2Login -> oauth2Login
.tokenEndpoint(tokenEndpoint -> tokenEndpoint
.accessTokenResponseClient(authorizationCodeAccessTokenResponseClient())));
// @formatter:on
return http.build();
}
@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
// @formatter:off
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.setHeadersConverter(headersConverter());
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
// @formatter:on
return accessTokenResponseClient;
}
// ...
}
Upvotes: 9