security-oauth2定制oauth2令牌请求的授权头?

jtoj6r0c  于 2021-09-29  发布在  Java
关注(0)|答案(1)|浏览(581)

我正在尝试通过网络客户端电话升级到SpringSecurity 5.5.1。我发现oauth2 clientid和secret现在是url编码的 AbstractWebClientReactiveOAuth2AccessTokenResponseClient ,但我的令牌提供程序不支持此操作(例如,如果机密包含 + 字符仅当它作为 + 不如 %2B ). 我知道这被看作是spring安全方面的一个bug修复),但我不能让令牌提供者轻易地改变其行为。
所以我试着找到一种方法来解决这个问题。
[文件](https://docs.spring.io/spring-security/site/docs/current/reference/html5/#customizing-当您使用webclient配置(这是我的情况)时,关于如何自定义访问令牌请求的访问令牌请求似乎不适用。
为了删除clientid/secret编码,我必须从中扩展和复制大部分现有代码 AbstractWebClientReactiveOAuth2AccessTokenResponseClient 自定义 WebClientReactiveClientCredentialsTokenResponseClient 因为它的大部分都具有私有/默认可见性。我在spring安全项目中的一个增强问题中对此进行了跟踪。
是否有更简单的方法自定义令牌请求的授权头,以跳过url编码?

wdebmtf2

wdebmtf21#

关于定制的一些API肯定有改进的余地,而且可以肯定的是,来自社区的这些类型的问题/请求/问题将继续有助于突出这些领域。
关于 AbstractWebClientReactiveOAuth2AccessTokenResponseClient 特别是,目前无法重写内部方法来填充数据库中的基本身份验证凭据 Authorization 标题。但是,您可以自定义 WebClient 用于进行api调用的。如果它在您的用例中是可接受的(暂时,在处理行为更改和/或添加自定义选项时),您应该能够在 WebClient .
下面是一个将创建 WebClient 能够使用 OAuth2AuthorizedClient :

  1. @Configuration
  2. public class WebClientConfiguration {
  3. @Bean
  4. public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
  5. // @formatter:off
  6. ServerOAuth2AuthorizedClientExchangeFilterFunction exchangeFilterFunction =
  7. new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
  8. exchangeFilterFunction.setDefaultOAuth2AuthorizedClient(true);
  9. return WebClient.builder()
  10. .filter(exchangeFilterFunction)
  11. .build();
  12. // @formatter:on
  13. }
  14. @Bean
  15. public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
  16. ReactiveClientRegistrationRepository clientRegistrationRepository,
  17. ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
  18. // @formatter:off
  19. WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
  20. new WebClientReactiveClientCredentialsTokenResponseClient();
  21. accessTokenResponseClient.setWebClient(createAccessTokenResponseWebClient());
  22. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  23. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  24. .clientCredentials(consumer ->
  25. consumer.accessTokenResponseClient(accessTokenResponseClient)
  26. .build())
  27. .build();
  28. DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
  29. new DefaultReactiveOAuth2AuthorizedClientManager(
  30. clientRegistrationRepository, authorizedClientRepository);
  31. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  32. // @formatter:on
  33. return authorizedClientManager;
  34. }
  35. protected WebClient createAccessTokenResponseWebClient() {
  36. // @formatter:off
  37. return WebClient.builder()
  38. .filter((clientRequest, exchangeFunction) -> {
  39. HttpHeaders headers = clientRequest.headers();
  40. String authorizationHeader = headers.getFirst("Authorization");
  41. Assert.notNull(authorizationHeader, "Authorization header cannot be null");
  42. Assert.isTrue(authorizationHeader.startsWith("Basic "),
  43. "Authorization header should start with Basic");
  44. String encodedCredentials = authorizationHeader.substring("Basic ".length());
  45. byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials);
  46. String credentialsString = new String(decodedBytes, StandardCharsets.UTF_8);
  47. Assert.isTrue(credentialsString.contains(":"), "Decoded credentials should contain a \":\"");
  48. String[] credentials = credentialsString.split(":");
  49. String clientId = URLDecoder.decode(credentials[0], StandardCharsets.UTF_8);
  50. String clientSecret = URLDecoder.decode(credentials[1], StandardCharsets.UTF_8);
  51. ClientRequest newClientRequest = ClientRequest.from(clientRequest)
  52. .headers(httpHeaders -> httpHeaders.setBasicAuth(clientId, clientSecret))
  53. .build();
  54. return exchangeFunction.exchange(newClientRequest);
  55. })
  56. .build();
  57. // @formatter:on
  58. }
  59. }

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

  1. @ExtendWith(MockitoExtension.class)
  2. public class WebClientConfigurationTests {
  3. private WebClientConfiguration webClientConfiguration;
  4. @Mock
  5. private ExchangeFunction exchangeFunction;
  6. @Captor
  7. private ArgumentCaptor<ClientRequest> clientRequestCaptor;
  8. @BeforeEach
  9. public void setUp() {
  10. webClientConfiguration = new WebClientConfiguration();
  11. }
  12. @Test
  13. public void exchangeWhenBasicAuthThenDecoded() {
  14. WebClient webClient = webClientConfiguration.createAccessTokenResponseWebClient()
  15. .mutate()
  16. .exchangeFunction(exchangeFunction)
  17. .build();
  18. when(exchangeFunction.exchange(any(ClientRequest.class)))
  19. .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK).build()));
  20. webClient.post()
  21. .uri("/oauth/token")
  22. .headers(httpHeaders -> httpHeaders.setBasicAuth("aladdin", URLEncoder.encode("open sesame", StandardCharsets.UTF_8)))
  23. .retrieve()
  24. .bodyToMono(Void.class)
  25. .block();
  26. verify(exchangeFunction).exchange(clientRequestCaptor.capture());
  27. ClientRequest clientRequest = clientRequestCaptor.getValue();
  28. String authorizationHeader = clientRequest.headers().getFirst("Authorization");
  29. assertThat(authorizationHeader).isNotNull();
  30. String encodedCredentials = authorizationHeader.substring("Basic ".length());
  31. byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials);
  32. String credentialsString = new String(decodedBytes, StandardCharsets.UTF_8);
  33. String[] credentials = credentialsString.split(":");
  34. assertThat(credentials[0]).isEqualTo("aladdin");
  35. assertThat(credentials[1]).isEqualTo("open sesame");
  36. }
  37. }
展开查看全部

相关问题