如何使用Spring Boot WebClient管理HTTPS双向认证,包括Bearer令牌?

huangapple go评论107阅读模式
英文:

How to manage HTTPS Mutual Authentication including Bearer Token with Spring boot WebClient?

问题

我的帖子目标是直接分享我关于以下主题的答案。我还分享了帮助我的链接 => 我正在开发一个基于Spring WebFlux的后端。一个前端Angular应用程序连接到我的后端。
我的后端还通过HTTPS连接到业务服务器,基于双向身份验证。
我必须在每个向业务服务器的头部请求中设置一个承载令牌。
我通过HTTPS通过OAuth 2.0客户端凭据授权流程从服务器检索此令牌。下面是我如何管理实施的答案。

英文:

The goal of my post is to directly share my answer regarding the following topic. I share also the links that helped me => I'm developping a backend based on Spring webflux. A frontEnd angular application is connected to my backend.
My backend is also connected to a business server through HTTPS based on mutual authentication.
I have to set a bearer token in each header request to my business server.
I retrieve this token from a server through OAuth 2.0 client credentials grant flow through HTTPS. My answer below shows how I managed the implementation.

答案1

得分: 0

以下是翻译好的部分:

感谢以下链接:

我找到了以下解决方案:

  1. @Configuration
  2. @EnableWebFluxSecurity
  3. public class Oauth2ClientConfig {
  4. // 基于 OAuth 2.0 客户端凭证授权流的 Bearer Token 管理
  5. @Bean
  6. ReactiveClientRegistrationRepository getRegistration() {
  7. ClientRegistration registration = ClientRegistration
  8. .withRegistrationId("credentialProvider")
  9. .tokenUri("https://rhsso:8446/auth/token")
  10. .clientId("client_id").clientSecret("client_secret")
  11. .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
  12. .build();
  13. return new InMemoryReactiveClientRegistrationRepository(registration);
  14. }
  15. // 使用 HTTPS 进行 Bearer Token 管理
  16. @Bean
  17. public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
  18. ReactiveClientRegistrationRepository clientRegistrationRepository,
  19. ReactiveOAuth2AuthorizedClientService authorizedClientService) throws IOException, KeyStoreException,
  20. NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
  21. WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
  22. final FileInputStream keyStoreFile = new FileInputStream("C:\\keystore\\client_keystore.p12"); // 客户端私钥和客户端证书
  23. final FileInputStream trustStoreFile = new FileInputStream("C:\\truststore\\server_truststore.p12");
  24. final KeyStore keyStore = KeyStore.getInstance("PKCS12");
  25. final KeyStore trustStore = KeyStore.getInstance("PKCS12");
  26. final KeyManagerFactory keyManagerFactory = KeyManagerFactory
  27. .getInstance(KeyManagerFactory.getDefaultAlgorithm());
  28. final TrustManagerFactory trustManagerFactory = TrustManagerFactory
  29. .getInstance(TrustManagerFactory.getDefaultAlgorithm());
  30. String keyStorePassword = "keyStorePassword";
  31. String trustStorePassword = "trustStorePassword";
  32. keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
  33. keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
  34. trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
  35. trustManagerFactory.init(trustStore);
  36. // 通过 sslContext 进行 HTTPS 管理
  37. SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
  38. .trustManager(trustManagerFactory);
  39. SslContext sslContext = sslContextBuilder.build();
  40. HttpClient httpSslClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext));
  41. ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);
  42. accessTokenResponseClient.setWebClient(WebClient.builder().clientConnector(httpConnector).build());
  43. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
  44. .builder().clientCredentials(c -> {
  45. c.accessTokenResponseClient(accessTokenResponseClient);
  46. }).build();
  47. AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
  48. clientRegistrationRepository, authorizedClientService);
  49. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  50. return authorizedClientManager;
  51. }
  52. @Bean("CustomWebClient") //Qualifier to be used by my dedicated ClientAPI service in order to use the right one among available WebClient beans
  53. public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) throws KeyStoreException,
  54. NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
  55. ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
  56. authorizedClientManager);
  57. oauthFunction.setDefaultClientRegistrationId("credentialProvider"); // 链接到上面管理的 Bearer token 的 ID
  58. final FileInputStream keyStoreFile = new FileInputStream("C:\\keystore\\business_client_keystore.p12"); // 客户端私钥和客户端证书
  59. final FileInputStream trustStoreFile = new FileInputStream("C:\\truststore\\business_server_truststore.p12");
  60. final KeyStore keyStore = KeyStore.getInstance("PKCS12");
  61. final KeyStore trustStore = KeyStore.getInstance("PKCS12");
  62. final KeyManagerFactory keyManagerFactory = KeyManagerFactory
  63. .getInstance(KeyManagerFactory.getDefaultAlgorithm());
  64. final TrustManagerFactory trustManagerFactory = TrustManagerFactory
  65. .getInstance(TrustManagerFactory.getDefaultAlgorithm());
  66. String keyStorePassword = "business_keyStorePassword";
  67. String trustStorePassword = "business_trustStorePassword";
  68. keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
  69. keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
  70. trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
  71. trustManagerFactory.init(trustStore);
  72. // 创建 Netty SslContext、HttpClient
  73. SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
  74. .trustManager(trustManagerFactory);
  75. SslContext sslContext = sslContextBuilder.build();
  76. HttpClient httpSslClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext));
  77. // 创建 Reactive ClientHttpConnector & WebClient
  78. final ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);
  79. return WebClient.builder().clientConnector(httpConnector).filter(oauthFunction).build();
  80. }
  81. @Bean
  82. public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
  83. // 禁用前端界面的 CSRF,但通常不建议,只在特殊情况下使用
  84. // 跨域管理以接受安装在本地主机上的前端应用程序,仅允许 POST 方法
  85. http.csrf().disable().cors(corsCustomizer -> {
  86. CorsConfigurationSource configurationSource = request -> {
  87. CorsConfiguration corsConfiguration = new CorsConfiguration();
  88. corsConfiguration.setAllowedOrigins(List.of("localhost:8080"));
  89. corsConfiguration.setAllowedMethods(List.of("POST"));
  90. return corsConfiguration;
  91. };
  92. corsCustomizer.configurationSource(configurationSource);
  93. }).oauth2Client();
  94. return http.build();
  95. }
  96. }

基于上述配置的 Web 客户端:

  1. @Service
  2. public class ClientAPI {
  3. private WebClient webClient;
  4. private String businessServiceUrl;
  5. @Autowired
  6. public ClientAPI(@Qualifier("CustomWebClient") WebClient webClient) {
  7. this.businessServiceUrl = "https://hostServerName:8081/theService";
  8. this.webClient = webClient;
  9. }
  10. public Mono<Response>
  11. <details>
  12. <summary>英文:</summary>
  13. Thanx to following links :
  14. - &lt;https://stackoverflow.com/questions/62935967/spring-boot-oauth2-clientreactive-mutual-tls-ssl-token-uri&gt;
  15. - &lt;https://stackoverflow.com/questions/45418523/spring-5-webclient-using-ssl&gt;
  16. - &lt;https://stackoverflow.com/questions/62935967/spring-boot-oauth2-clientreactive-mutual-tls-ssl-token-uri&gt;
  17. - &lt;https://stackoverflow.com/questions/17173559/how-to-verify-if-java-sends-the-client-certificate-in-a-mutual-auth-scenario&gt; =&gt; useful to check Mutual authentication in debug mode
  18. I found out the following solution :
  19. ```java
  20. @Configuration
  21. @EnableWebFluxSecurity
  22. public class Oauth2ClientConfig {
  23. // Bearer Token management based on OAuth 2.0 client credentials grant flow
  24. @Bean
  25. ReactiveClientRegistrationRepository getRegistration() {
  26. ClientRegistration registration = ClientRegistration
  27. .withRegistrationId(&quot;credentialProvider&quot;)
  28. .tokenUri(&quot;https://rhsso:8446/auth/token&quot;)
  29. .clientId(&quot;client_id&quot;).clientSecret(&quot;client_secret&quot;)
  30. .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
  31. .build();
  32. return new InMemoryReactiveClientRegistrationRepository(registration);
  33. }
  34. // Bearer Token management using HTTPS
  35. @Bean
  36. public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
  37. ReactiveClientRegistrationRepository clientRegistrationRepository,
  38. ReactiveOAuth2AuthorizedClientService authorizedClientService) throws IOException, KeyStoreException,
  39. NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
  40. WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
  41. final FileInputStream keyStoreFile = new FileInputStream(&quot;C:\\keystore\\client_keystore.p12&quot;); // client private key and client certificate
  42. final FileInputStream trustStoreFile = new FileInputStream(&quot;C:\\truststore\\server_truststore.p12&quot;);
  43. final KeyStore keyStore = KeyStore.getInstance(&quot;PKCS12&quot;);
  44. final KeyStore trustStore = KeyStore.getInstance(&quot;PKCS12&quot;);
  45. final KeyManagerFactory keyManagerFactory = KeyManagerFactory
  46. .getInstance(KeyManagerFactory.getDefaultAlgorithm());
  47. final TrustManagerFactory trustManagerFactory = TrustManagerFactory
  48. .getInstance(TrustManagerFactory.getDefaultAlgorithm());
  49. String keyStorePassword = &quot;keyStorePassword&quot;;
  50. String trustStorePassword = &quot;trustStorePassword&quot;;
  51. keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
  52. keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
  53. trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
  54. trustManagerFactory.init(trustStore);
  55. // HTTPS management through sslContext
  56. SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
  57. .trustManager(trustManagerFactory);
  58. SslContext sslContext = sslContextBuilder.build();
  59. HttpClient httpSslClient = HttpClient.create().secure(ssl -&gt; ssl.sslContext(sslContext));
  60. ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);
  61. accessTokenResponseClient.setWebClient(WebClient.builder().clientConnector(httpConnector).build());
  62. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
  63. .builder().clientCredentials(c -&gt; {
  64. c.accessTokenResponseClient(accessTokenResponseClient);
  65. }).build();
  66. AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
  67. clientRegistrationRepository, authorizedClientService);
  68. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  69. return authorizedClientManager;
  70. }
  71. @Bean(&quot;CustomWebClient&quot;) //Qualifier to be used by my dedicated ClientAPI service in order to use the right one among available WebClient beans
  72. public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) throws KeyStoreException,
  73. NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
  74. ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
  75. authorizedClientManager);
  76. oauthFunction.setDefaultClientRegistrationId(&quot;credentialProvider&quot;); // Link to Bearer token managed above through Its ID
  77. final FileInputStream keyStoreFile = new FileInputStream(&quot;C:\\keystore\\business_client_keystore.p12&quot;); // client private key and client certificate
  78. final FileInputStream trustStoreFile = new FileInputStream(&quot;C:\\truststore\\business_server_truststore.p12&quot;);
  79. final KeyStore keyStore = KeyStore.getInstance(&quot;PKCS12&quot;);
  80. final KeyStore trustStore = KeyStore.getInstance(&quot;PKCS12&quot;);
  81. final KeyManagerFactory keyManagerFactory = KeyManagerFactory
  82. .getInstance(KeyManagerFactory.getDefaultAlgorithm());
  83. final TrustManagerFactory trustManagerFactory = TrustManagerFactory
  84. .getInstance(TrustManagerFactory.getDefaultAlgorithm());
  85. String keyStorePassword = &quot;business_keyStorePassword&quot;;
  86. String trustStorePassword = &quot;business_trustStorePassword&quot;;
  87. keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
  88. keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
  89. trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
  90. trustManagerFactory.init(trustStore);
  91. // Create Netty SslContext, HttpClient
  92. SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
  93. .trustManager(trustManagerFactory);
  94. SslContext sslContext = sslContextBuilder.build();
  95. HttpClient httpSslClient = HttpClient.create().secure(ssl -&gt; ssl.sslContext(sslContext));
  96. // Create Reactive ClientHttpConnector &amp; WebClient
  97. final ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);
  98. return WebClient.builder().clientConnector(httpConnector).filter(oauthFunction).build();
  99. }
  100. @Bean
  101. public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
  102. // disable csrf with frontend interface but generally not recommended otherwise only in special cases
  103. // cors management in order to accept FrontEnd application installed localhost and only POST method is allowed
  104. http.csrf().disable().cors(corsCustomizer -&gt; {
  105. CorsConfigurationSource configurationSource = request-&gt;{
  106. CorsConfiguration corsConfiguration = new CorsConfiguration();
  107. corsConfiguration.setAllowedOrigins(List.of(&quot;localhost:8080&quot;));
  108. corsConfiguration.setAllowedMethods(List.of(&quot;POST&quot;));
  109. return corsConfiguration;
  110. };
  111. corsCustomizer.configurationSource(configurationSource);
  112. }).oauth2Client();
  113. return http.build();
  114. }
  115. }

My webclient based on my beans above :

  1. @Service
  2. public class ClientAPI {
  3. private WebClient webClient;
  4. private String businessServiceUrl;
  5. @Autowired
  6. public ClientAPI(@Qualifier(&quot;CustomWebClient&quot;) WebClient webClient) {
  7. this.businessServiceUrl = &quot;https://hostServerName:8081/theService&quot;;
  8. this.webClient = webClient;
  9. }
  10. public Mono&lt;Response&gt; sendRequest(Request bodyRequest) {
  11. Mono&lt;Response&gt; result = this.webClient.post().uri(this.businessServiceUrl)
  12. .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  13. .body(Mono.just(bodyRequest), Request.class).retrieve().bodyToMono(Response.class);
  14. return result;
  15. }
  16. }

huangapple
  • 本文由 发表于 2023年7月12日 21:28:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76671125.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定