Spring boot update-nél régi OAuth alapú backend megoldás upgrade

Probléma

Spring boot update-nél már teljesen személyre szabott korábbi OAuth2RestTemplate-t helyett már más megoldásra van szükség.

Előfeltételek

A szabványtól pár helyen eltérnek a végpontok, amiket használnunk kell:

  • POST helyett GET van használva, mind az access token-nél, mind a refresh token-nél
  • Más headerben megy a basic auth
  • Proxy-n keresztül kell menni az összes kérésnek
  • Cacheljük a tokeneket

Megoldás 1

WebClient-t és ServletOAuth2AuthorizedClientExchangeFilterFunction-t használjunk. Apró szépség hiba a DefaultRefreshTokenTokenResponseClient és DefaultClientCredentialsTokenResponseClient is deprecated már.

@Configuration
public class WebClientConfig {

    @Value("${proxy.host}")
    String proxyHost;
    @Value("${proxy.port}")
    int proxyPort;
    @Value("${proxy.username}")
    String proxyUsername;
    @Value("${proxy.password}")
    String proxyPassword;

    private ClientHttpConnector getClientHttpConnector() {
        HttpClient httpClient = HttpClient.create()
                .proxy(proxy -> proxy
                        .type(ProxyProvider.Proxy.HTTP)
                        .host(proxyHost)
                        .port(proxyPort)
                        .username(proxyUsername)
                        .password(s -> proxyPassword)
                )
                ;
        return new ReactorClientHttpConnector(httpClient);
    }

    @Bean("oAuthProxyWebClient")
    public WebClient oAuthProxyWebClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        var oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth2.setDefaultClientRegistrationId("testoidc");

        return WebClient.builder()
                .apply(oauth2.oauth2Configuration())
                 // Proxy-n keresztül kell menni az összes kérésnek
                .clientConnector(getClientHttpConnector())
                .build();
    }

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService authorizedClientService) {

        var provider = OAuth2AuthorizedClientProviderBuilder.builder()
                .refreshToken(r -> r.accessTokenResponseClient(
                        getDefaultRefreshTokenTokenResponseClient()

                ))
                .clientCredentials(c -> c.accessTokenResponseClient(
                        getDefaultClientCredentialsTokenResponseClient()
                ))
                .build();

        var manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository,
                authorizedClientService);
        manager.setAuthorizedClientProvider(provider);

        return manager;
    }

    private DefaultRefreshTokenTokenResponseClient getDefaultRefreshTokenTokenResponseClient() {
        DefaultRefreshTokenTokenResponseClient accessTokenResponseClient = new DefaultRefreshTokenTokenResponseClient();
        OAuth2RefreshTokenGrantRequestEntityConverter requestEntityConverter
                = new OAuth2RefreshTokenGrantRequestEntityConverter() {
            /** @see AbstractOAuth2AuthorizationGrantRequestEntityConverter#convert */
            @Override
            public RequestEntity<?> convert(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) {
                RequestEntity<?> converted = super.convert(authorizationGrantRequest);
                // POST helyett GET van használva mint access mint refresh token-él.
                return new RequestEntity<>(null, converted.getHeaders(), HttpMethod.GET, converted.getUrl());
            }
        };

        /** @see DefaultOAuth2TokenRequestHeadersConverter#convert */
        requestEntityConverter.setHeadersConverter(grantRequest -> {
            HttpHeaders headers = new HttpHeaders();
            headers.setAccept(List.of(MediaType.APPLICATION_JSON));
            ClientRegistration clientRegistration = grantRequest.getClientRegistration();

            headers.set(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());

            headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
            // Más headerben megy a basic auth.
            headers.set("customer_header_name", headers.getFirst(HttpHeaders.AUTHORIZATION));
            headers.remove(HttpHeaders.AUTHORIZATION);

            headers.set(OAuth2ParameterNames.REFRESH_TOKEN, grantRequest.getRefreshToken().getTokenValue());

            return headers;

        });

        accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

        // Proxy-n keresztül kell menni az összes kérésnek
        accessTokenResponseClient.setRestOperations(getRestTemplateWithProxy());
        return accessTokenResponseClient;
    }


    private DefaultClientCredentialsTokenResponseClient getDefaultClientCredentialsTokenResponseClient() {
        DefaultClientCredentialsTokenResponseClient accessTokenResponseClient = new DefaultClientCredentialsTokenResponseClient();

        OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter
                = new OAuth2ClientCredentialsGrantRequestEntityConverter() {
            /** @see AbstractOAuth2AuthorizationGrantRequestEntityConverter#convert   */
            @Override
            public RequestEntity<?> convert(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
                RequestEntity<?> converted = super.convert(authorizationGrantRequest);
                // POST helyett GET van használva mint access mint refresh token-él.
                return new RequestEntity<>(null, converted.getHeaders(), HttpMethod.GET, converted.getUrl());
            }
        };

        /** @see DefaultOAuth2TokenRequestHeadersConverter#convert  */
        requestEntityConverter.setHeadersConverter(grantRequest -> {
            HttpHeaders headers = new HttpHeaders();
            headers.setAccept(List.of(MediaType.APPLICATION_JSON));
            ClientRegistration clientRegistration = grantRequest.getClientRegistration();

            headers.set(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());

            headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
            // Más headerben megy a basic auth
            headers.set("customer_header_name", headers.getFirst(HttpHeaders.AUTHORIZATION));
            headers.remove(HttpHeaders.AUTHORIZATION);

            return headers;

        });
        accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

        // Proxy-n keresztül kell menni az összes kérésnek
        accessTokenResponseClient.setRestOperations(getRestTemplateWithProxy());
        return accessTokenResponseClient;
    }

    private RestTemplate getRestTemplateWithProxy() {
        ClientHttpRequestFactory factory = getClientHttpRequestFactory();

        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.setMessageConverters(List.of(new OAuth2AccessTokenResponseHttpMessageConverter()));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

        return restTemplate;
    }

    private ClientHttpRequestFactory getClientHttpRequestFactory() {
        HttpHost proxy = new HttpHost("http", proxyHost, proxyPort);

        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
                new AuthScope(proxyHost, proxyPort),
                new UsernamePasswordCredentials(proxyUsername, proxyPassword.toCharArray())
        );
        CloseableHttpClient httpClient = HttpClients.custom()
                .setRoutePlanner(new DefaultProxyRoutePlanner(proxy))
                .setDefaultCredentialsProvider(credentialsProvider)
                .build();

        return new HttpComponentsClientHttpRequestFactory(httpClient);
    }

    @Bean
    public OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
	    // Cacheljük a tokeneket
        return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
    }
}

valamint

# Client registration
spring.security.oauth2.client.registration.dtoidc.client-id=client-id
spring.security.oauth2.client.registration.dtoidc.client-secret=client-secret
spring.security.oauth2.client.registration.dtoidc.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.dtoidc.client-name=client-name

# Provider
spring.security.oauth2.client.provider.dtoidc.token-uri=https://example.com/generate/accessToken
spring.security.oauth2.client.provider.dtoidc.refresh-token-uri==https://example.com/generate/refreshToken

# Proxy
proxy.host=proxy.example.com
proxy.port=8080
proxy.username=username
proxy.password=password

és egy TestController ami csak átproxyza a hívást

@RestController
public class TestController {

    @Autowired
    @Qualifier("oAuthProxyWebClient")
    WebClient webClient;

    @GetMapping(value = "oAuthProxyWebClient", produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<String> SendWithOAuthProxyWebClient() {
        Map<String, Object> body = Map.ofEntries(
                entry("prop1", "value1"),
                entry("prop2", List.of(
                        Map.of(
                                "name", "name1",
                                "value", "value2"
                        )
                )),
                entry("prop3", List.of("value3-1", "value3-2"))
        );


        return webClient.post()
                .uri("https://example.com/events")
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(body)
                .exchangeToMono(clientResponse -> clientResponse.toEntity(String.class))
                .block();

    }
}
2025.11.05.