Expected Behavior
Allow the setting of Resource Indicators for Oauth 2.0 on ClientRegistration
to then be used for authentication requests.
Current Behavior
The current process for setting an RFC 8707 resource indicator might appear to be begin with either
- Implementing a custom
Oauth2AuthorizedClientManager
and in theauthorize
method adding the indicator as a custom attribute entry to theOauth2AuthorizationContext
. - Instantiating an existing
Oauth2AuthorizedClientManager
implementation and setting acontextAttributesMapper
on that instance that would create the resource indicator entry.
However, it seems that this value would ultimately not be applied by a parametersConverter such as the OAuth2ClientCredentialsGrantRequestEntityConverter
, so quite a bit more custom work appears to be necessary.
Context
My specific case involves a client credentials flow involving a resource
indicator rather than core oauth 2 scopes
, using WebClient
and a ServletOAuth2AuthorizedClientExchangeFilterFunction
. I'm also leveraging spring-boot oauth client autoconfiguration properties, but as that project's property values are applied to spring-security ClientRegistration
s starting here seems to make the most sense.
The only existing issues involving resource indicators that I'm aware of are this project's #6972 and the spring-authorization-server project's support request.
Are there any other workarounds that I'm unaware of?
Comment From: pat-mccusker
Prior to spring-security 6.4, my workaround turned out to be roughly the following:
-
Create a custom
OAuth2ClientCredentialsGrantRequestEntityConverter
that could set the resource indicator after the default parameter values had been converted: ```@Component public class ResourceIndicatorRequestEntityConverter extends OAuth2ClientCredentialsGrantRequestEntityConverter { private MapclientResource = new HashMap<>(); @Override protected MultiValueMap
createParameters(OAuth2ClientCredentialsGrantRequest request) { var defaultParameters = super.createParameters(request); String registrationId = request.getClientRegistration().getRegistrationId(); var resource = clientResource.get(registrationId); if (hasText(resource)) { defaultParameters.add("resource", resource); } return defaultParameters;
} }
2. Create a custom `OAuth2AuthorizedClientManager` with this entity converter set on the `DefaultClientCredentialsTokenResponseClient` of the `OAuth2AuthorizedClientProvider` within:
``` @Bean
public OAuth2AuthorizedClientManager resourceIndicatorClientManager(
ClientRegistrationRepository clientRegistrations,
OAuth2AuthorizedClientService authorizedClients,
ResourceIndicatorRequestEntityConverter resourceIndicatorConverter
) {
var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrations, authorizedClients);
var tokenClient = new DefaultClientCredentialsTokenResponseClient();
tokenClient.setRequestEntityConverter(resourceIndicatorConverter);
var provider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(grantBuilder -> grantBuilder.accessTokenResponseClient(tokenClient))
.build();
authorizedClientManager.setAuthorizedClientProvider(provider);
return authorizedClientManager;
}
Pretty straightforward all in all, however starting in 6.4, both OAuth2ClientCredentialsGrantRequestEntityConverter
and DefaultClientCredentialsTokenResponseClient
are marked deprecated for removal with replacements DefaultOAuth2TokenRequestParametersConverter
and RestClientClientCredentialsTokenResponseClient
respectively, both of which are final classes. Minimal extension of the default behavior seems less feasible in light of this, so I'm open to suggestions for a similar approach with the new objects.
Without access to the oauth request fields, AbstractRestClientOAuth2AccessTokenResponseClient#setParametersCustomizer
only appears suitable for constant data, so for example selection of one of many clients' resource indicators is not really possible.
Comment From: pat-mccusker
It turns out the post 6.4 setup is more or less just as straightforward. Instead of extending OAuth2ClientCredentialsGrantRequestEntityConverter
, one need only create a custom implementation of a Converter, turning the above step 1 into
@Component
public class ResourceIndicatorClientCredentialsRequestConverter implements Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> {
private Map<String, String> clientResource = new HashMap<>();
@Override
public @Nullable MultiValueMap<String, String> convert(OAuth2ClientCredentialsGrantRequest tokenRequest) {
String registrationId = tokenRequest.getClientRegistration().getRegistrationId();
var resource = clientResource.get(registrationId);
if (!hasText(resource)) {
return null;
}
var customParams = new LinkedMultiValueMap<String, String>();
customParams.add("resource", resource);
return customParams;
}
}
The only change needed for the authorizedClientManager is changing the tokenClient setup to the following:
var tokenClient = new RestClientClientCredentialsTokenResponseClient();
tokenClient.addParametersConverter(resourceIndicatorConverter);