如何在WebClient中使用SPUNG-SECURITY-OAuth2定制OAuth2令牌请求的授权头部? [英] How to customize the Authorization header of the OAuth2 token request using spring-security-oauth2 with a WebClient?

查看:14
本文介绍了如何在WebClient中使用SPUNG-SECURITY-OAuth2定制OAuth2令牌请求的授权头部?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试通过WebClient调用升级到Spring Security 5.5.1。 我发现OAuth2客户端ID密钥现在是AbstractWebClientReactiveOAuth2AccessTokenResponseClient中的URL编码,但我的令牌提供程序不支持这一点(例如,如果密钥包含+字符,则仅当它作为+而不是%2B发送时才起作用)。 我知道这被视为bug fix from spring-security side),但我不能让令牌提供商轻松更改其行为。

因此我尝试找到解决此问题的方法。

使用WebClient配置时(我的情况就是如此),有关如何自定义访问令牌请求的[文档](https://docs.spring.io/spring-security/site/docs/current/reference/html5/#customizing-the-access-token-request)似乎不适用。

为了删除客户端ID/机密编码,我必须扩展和复制AbstractWebClientReactiveOAuth2AccessTokenResponseClient中的大部分现有代码,以自定义WebClientReactiveClientCredentialsTokenResponseClient,因为它们中的大多数都具有私有/默认可见性。 我在Spring-Security项目的enhancement issue中跟踪到了这一点。

是否有更简单的方法自定义令牌请求的Authorization标头,以跳过URL编码?

推荐答案

关于自定义的一些API肯定还有改进的空间,来自社区的这些类型的问题/请求/问题将继续帮助突出这些方面。

尤其是AbstractWebClientReactiveOAuth2AccessTokenResponseClient,目前无法重写内部方法来填充Authorization标头中的基本身份验证凭据。但是,您可以自定义用于进行API调用的WebClient。如果它在您的用例中是可接受的(暂时,在处理行为更改和/或添加自定义选项时),您应该能够在WebClient中拦截请求。

以下配置将创建能够使用OAuth2AuthorizedClient

WebClient
@Configuration
public class WebClientConfiguration {

    @Bean
    public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
        // @formatter:off
        ServerOAuth2AuthorizedClientExchangeFilterFunction exchangeFilterFunction =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        exchangeFilterFunction.setDefaultOAuth2AuthorizedClient(true);

        return WebClient.builder()
                .filter(exchangeFilterFunction)
                .build();
        // @formatter:on
    }

    @Bean
    public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
        // @formatter:off
        WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
                new WebClientReactiveClientCredentialsTokenResponseClient();
        accessTokenResponseClient.setWebClient(createAccessTokenResponseWebClient());

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
                ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials(consumer ->
                                consumer.accessTokenResponseClient(accessTokenResponseClient)
                                        .build())
                        .build();

        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultReactiveOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        // @formatter:on

        return authorizedClientManager;
    }

    protected WebClient createAccessTokenResponseWebClient() {
        // @formatter:off
        return WebClient.builder()
                .filter((clientRequest, exchangeFunction) -> {
                    HttpHeaders headers = clientRequest.headers();
                    String authorizationHeader = headers.getFirst("Authorization");
                    Assert.notNull(authorizationHeader, "Authorization header cannot be null");
                    Assert.isTrue(authorizationHeader.startsWith("Basic "),
                            "Authorization header should start with Basic");
                    String encodedCredentials = authorizationHeader.substring("Basic ".length());
                    byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials);
                    String credentialsString = new String(decodedBytes, StandardCharsets.UTF_8);
                    Assert.isTrue(credentialsString.contains(":"), "Decoded credentials should contain a ":"");
                    String[] credentials = credentialsString.split(":");
                    String clientId = URLDecoder.decode(credentials[0], StandardCharsets.UTF_8);
                    String clientSecret = URLDecoder.decode(credentials[1], StandardCharsets.UTF_8);

                    ClientRequest newClientRequest = ClientRequest.from(clientRequest)
                            .headers(httpHeaders -> httpHeaders.setBasicAuth(clientId, clientSecret))
                            .build();
                    return exchangeFunction.exchange(newClientRequest);
                })
                .build();
        // @formatter:on
    }

}

此测试演示了内部访问令牌响应的凭据已解码WebClient

@ExtendWith(MockitoExtension.class)
public class WebClientConfigurationTests {

    private WebClientConfiguration webClientConfiguration;

    @Mock
    private ExchangeFunction exchangeFunction;

    @Captor
    private ArgumentCaptor<ClientRequest> clientRequestCaptor;

    @BeforeEach
    public void setUp() {
        webClientConfiguration = new WebClientConfiguration();
    }

    @Test
    public void exchangeWhenBasicAuthThenDecoded() {
        WebClient webClient = webClientConfiguration.createAccessTokenResponseWebClient()
                .mutate()
                .exchangeFunction(exchangeFunction)
                .build();
        when(exchangeFunction.exchange(any(ClientRequest.class)))
                .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK).build()));

        webClient.post()
                .uri("/oauth/token")
                .headers(httpHeaders -> httpHeaders.setBasicAuth("aladdin", URLEncoder.encode("open sesame", StandardCharsets.UTF_8)))
                .retrieve()
                .bodyToMono(Void.class)
                .block();

        verify(exchangeFunction).exchange(clientRequestCaptor.capture());

        ClientRequest clientRequest = clientRequestCaptor.getValue();
        String authorizationHeader = clientRequest.headers().getFirst("Authorization");
        assertThat(authorizationHeader).isNotNull();
        String encodedCredentials = authorizationHeader.substring("Basic ".length());
        byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials);
        String credentialsString = new String(decodedBytes, StandardCharsets.UTF_8);
        String[] credentials = credentialsString.split(":");

        assertThat(credentials[0]).isEqualTo("aladdin");
        assertThat(credentials[1]).isEqualTo("open sesame");
    }

}

这篇关于如何在WebClient中使用SPUNG-SECURITY-OAuth2定制OAuth2令牌请求的授权头部?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆