How to send custom parameters in the Request Body of Token Request in Spring Security OAuth2

85 views Asked by At

I'm working on an application written in Spring Boot (3.2.3) with Spring-Security (6.2.2) which connects to an external service over REST (implemented with Open Feign Client) and authenticated with a Bearer JWT Token.

The JWT token used for the external service has to be obtained using OAuth2.

In the documentation of the external service it is explained how to obtain the jwt token using Postman, as follows:

  • grant type: authorization code
  • callback url: ...
  • client id: ...
  • client secret: ...
  • auth url: https://provider-name/authorize
  • access token url: https://provider-name/token
  • from advanced, on Auth Request section, set "token_content_type": "jwt" (this translates to a query parameter for auth link)
  • from advanced, on Token Request section, set "token_content_type": "jwt", Send in: "Request body".

Using Postman I managed to retrieve the JWT token successfully. However, I am not sure how to fully achieve this with Spring.

My setup is as follows: application.yaml

spring:
  security:
    oauth2:
      client:
        registration:
          provider-name:
            client-id: ...
            client-secret: ...
            authorization-grant-type: authorization_code
            redirect-uri: "https://localhost:8080/a/token"
        provider:
          provider-name:
            authorization-uri: "https://provider-name/authorize"
            token-uri: "https://provider-name/token"

My Security config looks something like this:

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
         return http
                .authorizeHttpRequests(auth -> {
                    auth.requestMatchers("/a/token").permitAll();
                })
                 .oauth2Login(oauth2 ->
                         oauth2.authorizationEndpoint(endpoint ->
                                 endpoint.authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository))))
                 .formLogin(withDefaults())
                 .build();
    }

where CustomAuthorizationRequestResolver is:

@Component
public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {

    private final OAuth2AuthorizationRequestResolver defaultResolver;

    public CustomAuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
        this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/oauth2/authorize");
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
        OAuth2AuthorizationRequest authorizationRequest = this.defaultResolver.resolve(request);
        if (authorizationRequest != null) {
            authorizationRequest = customizeAuthorizationRequest(authorizationRequest);
        }
        return authorizationRequest;
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
        OAuth2AuthorizationRequest authorizationRequest = this.defaultResolver.resolve(request, clientRegistrationId);
        if (authorizationRequest != null) {
            authorizationRequest = customizeAuthorizationRequest(authorizationRequest);
        }
        return authorizationRequest;
    }

    private OAuth2AuthorizationRequest customizeAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest) {
        if (!authorizationRequest.getAuthorizationUri().contains("provider-name")) {
            return authorizationRequest;
        }
        return OAuth2AuthorizationRequest.from(authorizationRequest)
                .additionalParameters(Map.of("token_content_type", "jwt"))
                .build();
    }

}

which works fine for setting the query parameter for the Auth Request.

When I call my application endpoint: https://localhost:8080/oauth2/authorize/provider-name the request is forwarded properly towards my provider and I get a response to /a/token with a code which, from my understandings, I have to send it back towards the /token endpoint.

However, I do not know how to send the same key-value pair (token_conent_type/jwt) on the request body of the Token Request. Isn't Spring Security supposed to do this? How to intercept and append my custom request body parameter?

Any suggestions?

Thank you!!

1

There are 1 answers

2
Marius Manastireanu On

First of all it seems that Spring expects the redirect-uri to be defined as: "{baseUrl}/login/oauth2/code/provider-name".

Because I got that different the framework did not manage to intercept the auth code received and call the token endpoint.

Secondly, I did the following improvements:

    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
        accessTokenResponseClient.setRequestEntityConverter(new CustomRequestEntityConverter());
        return accessTokenResponseClient;
    }

where CustomRequestEntityConverter is:

public class CustomRequestEntityConverter implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {

    private OAuth2AuthorizationCodeGrantRequestEntityConverter defaultConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();

    @Override
    public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
        RequestEntity<?> defaultRequestEntity = defaultConverter.convert(authorizationCodeGrantRequest);
        if (defaultRequestEntity == null) {
            return null;
        }

        HttpHeaders headers = defaultRequestEntity.getHeaders();
        MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
        if (defaultRequestEntity.getBody() instanceof MultiValueMap) {
            formParameters.putAll((MultiValueMap<String, String>) defaultRequestEntity.getBody());
        }
        formParameters.add("token_content_type", "jwt");

        return new RequestEntity<>(formParameters, headers, defaultRequestEntity.getMethod(), defaultRequestEntity.getUrl());
    }
}

and linked this to the security config, as such:

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
         return http
                 .oauth2Login(oauth2 ->
                         oauth2.authorizationEndpoint(endpoint ->
                                 endpoint.authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository)))
                                 .tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenResponseClient(accessTokenResponseClient))
                 )
                 .build();
    }

And that's about it. Happy coding!