WebClientConfig.java 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. * Copyright 2020-2025 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * https://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package sample.config;
  17. import java.util.function.Supplier;
  18. import javax.net.ssl.KeyManagerFactory;
  19. import javax.net.ssl.TrustManagerFactory;
  20. import io.netty.handler.ssl.SslContext;
  21. import io.netty.handler.ssl.SslContextBuilder;
  22. import reactor.netty.http.client.HttpClient;
  23. import reactor.netty.tcp.SslProvider;
  24. import sample.authorization.DeviceCodeOAuth2AuthorizedClientProvider;
  25. import org.springframework.beans.factory.annotation.Qualifier;
  26. import org.springframework.boot.ssl.SslBundle;
  27. import org.springframework.boot.ssl.SslBundles;
  28. import org.springframework.context.annotation.Bean;
  29. import org.springframework.context.annotation.Configuration;
  30. import org.springframework.http.client.ClientHttpRequestFactory;
  31. import org.springframework.http.client.reactive.ClientHttpConnector;
  32. import org.springframework.http.client.reactive.ReactorClientHttpConnector;
  33. import org.springframework.http.converter.FormHttpMessageConverter;
  34. import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
  35. import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
  36. import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
  37. import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
  38. import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
  39. import org.springframework.security.oauth2.client.endpoint.RestClientClientCredentialsTokenResponseClient;
  40. import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
  41. import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
  42. import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
  43. import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
  44. import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
  45. import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
  46. import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
  47. import org.springframework.util.LinkedMultiValueMap;
  48. import org.springframework.util.MultiValueMap;
  49. import org.springframework.web.client.RestClient;
  50. import org.springframework.web.reactive.function.client.WebClient;
  51. /**
  52. * @author Joe Grandja
  53. * @author Steve Riesenberg
  54. * @since 0.0.1
  55. */
  56. @Configuration(proxyBeanMethods = false)
  57. public class WebClientConfig {
  58. @Bean("default-client-web-client")
  59. public WebClient defaultClientWebClient(
  60. OAuth2AuthorizedClientManager authorizedClientManager,
  61. SslBundles sslBundles) throws Exception {
  62. ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
  63. new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
  64. // @formatter:off
  65. return WebClient.builder()
  66. .clientConnector(createClientConnector(sslBundles.getBundle("demo-client")))
  67. .apply(oauth2Client.oauth2Configuration())
  68. .build();
  69. // @formatter:on
  70. }
  71. @Bean("self-signed-demo-client-web-client")
  72. public WebClient selfSignedDemoClientWebClient(
  73. ClientRegistrationRepository clientRegistrationRepository,
  74. OAuth2AuthorizedClientRepository authorizedClientRepository,
  75. @Qualifier("self-signed-demo-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory,
  76. SslBundles sslBundles) throws Exception {
  77. // @formatter:off
  78. RestClient restClient = RestClient.builder()
  79. .requestFactory(clientHttpRequestFactory.get())
  80. .messageConverters((messageConverters) -> {
  81. messageConverters.clear();
  82. messageConverters.add(new FormHttpMessageConverter());
  83. messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
  84. })
  85. .defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
  86. .build();
  87. // @formatter:on
  88. // @formatter:off
  89. OAuth2AuthorizedClientProvider authorizedClientProvider =
  90. OAuth2AuthorizedClientProviderBuilder.builder()
  91. .clientCredentials(clientCredentials ->
  92. clientCredentials.accessTokenResponseClient(
  93. createClientCredentialsTokenResponseClient(restClient)))
  94. .build();
  95. // @formatter:on
  96. DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
  97. clientRegistrationRepository, authorizedClientRepository);
  98. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  99. ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
  100. new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
  101. // @formatter:off
  102. return WebClient.builder()
  103. .clientConnector(createClientConnector(sslBundles.getBundle("self-signed-demo-client")))
  104. .apply(oauth2Client.oauth2Configuration())
  105. .build();
  106. // @formatter:on
  107. }
  108. @Bean
  109. public OAuth2AuthorizedClientManager authorizedClientManager(
  110. ClientRegistrationRepository clientRegistrationRepository,
  111. OAuth2AuthorizedClientRepository authorizedClientRepository,
  112. @Qualifier("default-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
  113. // @formatter:off
  114. RestClient restClient = RestClient.builder()
  115. .requestFactory(clientHttpRequestFactory.get())
  116. .messageConverters((messageConverters) -> {
  117. messageConverters.clear();
  118. messageConverters.add(new FormHttpMessageConverter());
  119. messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
  120. })
  121. .defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
  122. .build();
  123. // @formatter:on
  124. // @formatter:off
  125. OAuth2AuthorizedClientProvider authorizedClientProvider =
  126. OAuth2AuthorizedClientProviderBuilder.builder()
  127. .authorizationCode()
  128. .refreshToken()
  129. .clientCredentials(clientCredentials ->
  130. clientCredentials.accessTokenResponseClient(
  131. createClientCredentialsTokenResponseClient(restClient)))
  132. .provider(new DeviceCodeOAuth2AuthorizedClientProvider())
  133. .build();
  134. // @formatter:on
  135. DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
  136. clientRegistrationRepository, authorizedClientRepository);
  137. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  138. // Set a contextAttributesMapper to obtain device_code from the request
  139. authorizedClientManager.setContextAttributesMapper(DeviceCodeOAuth2AuthorizedClientProvider
  140. .deviceCodeContextAttributesMapper());
  141. return authorizedClientManager;
  142. }
  143. private static ClientHttpConnector createClientConnector(SslBundle sslBundle) throws Exception {
  144. KeyManagerFactory keyManagerFactory = sslBundle.getManagers().getKeyManagerFactory();
  145. TrustManagerFactory trustManagerFactory = sslBundle.getManagers().getTrustManagerFactory();
  146. // @formatter:off
  147. SslContext sslContext = SslContextBuilder.forClient()
  148. .keyManager(keyManagerFactory)
  149. .trustManager(trustManagerFactory)
  150. .build();
  151. // @formatter:on
  152. SslProvider sslProvider = SslProvider.builder().sslContext(sslContext).build();
  153. HttpClient httpClient = HttpClient.create().secure(sslProvider);
  154. return new ReactorClientHttpConnector(httpClient);
  155. }
  156. private static OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> createClientCredentialsTokenResponseClient(
  157. RestClient restClient) {
  158. RestClientClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =
  159. new RestClientClientCredentialsTokenResponseClient();
  160. clientCredentialsTokenResponseClient.addParametersConverter(authorizationGrantRequest -> {
  161. MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
  162. // client_id parameter is required for tls_client_auth method
  163. parameters.add(OAuth2ParameterNames.CLIENT_ID, authorizationGrantRequest.getClientRegistration().getClientId());
  164. return parameters;
  165. });
  166. clientCredentialsTokenResponseClient.setRestClient(restClient);
  167. return clientCredentialsTokenResponseClient;
  168. }
  169. }