spring-security: Missing support for private_key_jwt in ClientRegistrations

Describe the bug I’m trying to set up an OAuth2 client against https://ver2.maskinporten.no/, whose .well-known configuration endpoint looks like this:

{
  "issuer": "https://ver2.maskinporten.no/",
  "token_endpoint": "https://ver2.maskinporten.no/token",
  "jwks_uri": "https://ver2.maskinporten.no/jwk",
  "token_endpoint_auth_methods_supported": [ "private_key_jwt" ],
  "grant_types_supported": [ "urn:ietf:params:oauth:grant-type:jwt-bearer" ]
}

I figured I’d try to use the new support for private_key_jwt in Spring Security 5.5.0 (in combination with Spring Boot 2.4.5), but when I start the application I get an error:

Caused by: java.lang.IllegalArgumentException: Only ClientAuthenticationMethod.CLIENT_SECRET_BASIC, ClientAuthenticationMethod.CLIENT_SECRET_POST and ClientAuthenticationMethod.NONE are supported. The issuer "https://ver2.maskinporten.no/" returned a configuration of [private_key_jwt]
	at org.springframework.security.oauth2.client.registration.ClientRegistrations.getClientAuthenticationMethod(ClientRegistrations.java:280) ~[spring-security-oauth2-client-5.5.0.jar:5.5.0]
	at org.springframework.security.oauth2.client.registration.ClientRegistrations.withProviderConfiguration(ClientRegistrations.java:243) ~[spring-security-oauth2-client-5.5.0.jar:5.5.0]
	at org.springframework.security.oauth2.client.registration.ClientRegistrations.lambda$getRfc8414Builder$1(ClientRegistrations.java:190) ~[spring-security-oauth2-client-5.5.0.jar:5.5.0]
	at org.springframework.security.oauth2.client.registration.ClientRegistrations.getBuilder(ClientRegistrations.java:209) ~[spring-security-oauth2-client-5.5.0.jar:5.5.0]
	at org.springframework.security.oauth2.client.registration.ClientRegistrations.fromIssuerLocation(ClientRegistrations.java:145) ~[spring-security-oauth2-client-5.5.0.jar:5.5.0]
	at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getBuilderFromIssuerIfPossible(OAuth2ClientPropertiesRegistrationAdapter.java:83) ~[spring-boot-autoconfigure-2.4.5.jar:2.4.5]
	at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getClientRegistration(OAuth2ClientPropertiesRegistrationAdapter.java:59) ~[spring-boot-autoconfigure-2.4.5.jar:2.4.5]
	at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.lambda$getClientRegistrations$0(OAuth2ClientPropertiesRegistrationAdapter.java:53) ~[spring-boot-autoconfigure-2.4.5.jar:2.4.5]
	at java.base/java.util.HashMap.forEach(HashMap.java:1336) ~[na:na]
	at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(OAuth2ClientPropertiesRegistrationAdapter.java:52) ~[spring-boot-autoconfigure-2.4.5.jar:2.4.5]
	at org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientRegistrationRepositoryConfiguration.clientRegistrationRepository(OAuth2ClientRegistrationRepositoryConfiguration.java:49) ~[spring-boot-autoconfigure-2.4.5.jar:2.4.5]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.6.jar:5.3.6]
	... 83 common frames omitted

To Reproduce I try the following configuration in my application.yml, just to get off the ground:

spring:
  security.oauth2.client:
    provider:
      maskinporten.issuer-uri: https://ver2.maskinporten.no/
    registration:
      maskinporten-private-key-jwt:
        client-id: 1337
        provider: maskinporten
        scope: test:test

Expected behavior I expected this part of the client setup to work “out of the box”, but there appears to be something missing from org.springframework.security.oauth2.client.registration.ClientRegistrations.getClientAuthenticationMethod, maybe? Unless I’m not supposed to have reached that code path at all, in which case I don’t know what to expect.

Sample I put together a small, reproducible sample which uses Maven, hopefully that’s okay.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 17 (6 by maintainers)

Commits related to this issue

Most upvoted comments

@erlendfg alas, no - I haven’t attempted to use ID Porten myself yet.

I know you didn’t ask, but for anybody else who might be in the same pickle: As for Maskinporten, I gave up on using Spring Security’s support for it. I realized that I would have to customize so much of Spring Security, make so many subclasses and implementations of stuff™, that in the end it just wasn’t worth it. This is in part due to Spring Security’s rigidity/commitment to follow the specs only, and partly because Maskinporten doesn’t follow said specs precisely, meaning that shoehorning my Maskinporten-integration into Spring Security proved to be a difficult task.

Instead, I implemented a single @Service with its own RestTemplate and with logic and caching behavior that is pretty transparent to the reader, and then used it inside a ClientHttpRequestInterceptor implementation …:

@RequiredArgsConstructor
public class MyResourceServiceAuthenticationInterceptor implements ClientHttpRequestInterceptor {

    private final MaskinportenService maskinportenService;

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {

        request.getHeaders().setBearerAuth(maskinportenService.fetchAccessToken());

        return execution.execute(request, body);
    }
}

… which I added to the RestTemplate I used to call the target resource. Something along the lines of:

public MyResourceServiceConstructor(RestTemplateBuilder restTemplateBuilder,
                      MyResourceServiceAuthenticationInterceptor authenticationInterceptor) {

    this.restTemplate = restTemplateBuilder
            .interceptors(authenticationInterceptor)
            .build();
}