使用 ssl 的 Spring 5 WebClient
Posted
技术标签:
【中文标题】使用 ssl 的 Spring 5 WebClient【英文标题】:Spring 5 WebClient using ssl 【发布时间】:2018-01-07 04:21:15 【问题描述】:我正在尝试查找使用 WebClient 的示例。
我的目标是使用 Spring 5 WebClient 使用 https 和自签名证书查询 REST 服务
有什么例子吗?
【问题讨论】:
在 Spring 5.1 中,ReactorClientHttpConnector 不再具有接收选项的构造函数。此处描述了一种可能的解决方案:blog.rohrpostix.net/spring-webflux-webclient-using-ssl 那个页面不见了 【参考方案1】:对于那些可能一直坚持如何使用响应式 WebFlux webClient
使用受 https 保护的 REST API 的人你想创建两个东西
-
https 启用 REST API - https://github.com/mghutke/HttpsEnabled
另一个 REST API 作为客户端与 WebClient 使用以上一个 - https://github.com/mghutke/HttpsClient
注意:请通过上述项目查看与上述两个 Spring Boot 应用程序共享的密钥库。并以编程方式添加了 keyManagerFactory 和 TrustManagerFactory。
【讨论】:
【参考方案2】:必须对此进行编辑,以适应 spring-boot 2.0->2.1 的更改。
另一种方式,如果你想编写生产代码,创建一个这样的 spring bean,它修改注入的 WebClient,使用来自 spring-boot 服务器的设置,用于信任库和密钥库的位置。在客户端,如果您使用的是 2-way-ssl,您只需要提供 Keystore。不确定,为什么 ssl-stuff 没有预先配置且易于注入,类似于非常酷的 spring-boot 服务器设置。
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
.
.
.
@Bean
WebClientCustomizer configureWebclient(@Value("$server.ssl.trust-store") String trustStorePath, @Value("$server.ssl.trust-store-password") String trustStorePass,
@Value("$server.ssl.key-store") String keyStorePath, @Value("$server.ssl.key-store-password") String keyStorePass, @Value("$server.ssl.key-alias") String keyAlias)
return (WebClient.Builder webClientBuilder) ->
SslContext sslContext;
final PrivateKey privateKey;
final X509Certificate[] certificates;
try
final KeyStore trustStore;
final KeyStore keyStore;
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePass.toCharArray());
List<Certificate> certificateList = Collections.list(trustStore.aliases())
.stream()
.filter(t ->
try
return trustStore.isCertificateEntry(t);
catch (KeyStoreException e1)
throw new RuntimeException("Error reading truststore", e1);
)
.map(t ->
try
return trustStore.getCertificate(t);
catch (KeyStoreException e2)
throw new RuntimeException("Error reading truststore", e2);
)
.collect(Collectors.toList());
certificates = certificateList.toArray(new X509Certificate[certificateList.size()]);
privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyStorePass.toCharArray());
Certificate[] certChain = keyStore.getCertificateChain(keyAlias);
X509Certificate[] x509CertificateChain = Arrays.stream(certChain)
.map(certificate -> (X509Certificate) certificate)
.collect(Collectors.toList())
.toArray(new X509Certificate[certChain.length]);
sslContext = SslContextBuilder.forClient()
.keyManager(privateKey, keyStorePass, x509CertificateChain)
.trustManager(certificates)
.build();
HttpClient httpClient = HttpClient.create()
.secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
webClientBuilder.clientConnector(connector);
catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException e)
throw new RuntimeException(e);
;
这里是你使用 Webclient 的部分:
import org.springframework.web.reactive.function.client.WebClient;
@Component
public class ClientComponent
public ClientComponent(WebClient.Builder webClientBuilder, @Value("$url") String url)
this.client = webClientBuilder.baseUrl(solrUrl).build();
【讨论】:
当WebClientBuilder被注入到组件中时,会自动使用WebClientCustomizer bean进行自定义? 是的,就是这样。如果我没记错的话,我可以将 WebClient 直接注入到正常应用程序中,但必须从构建器中获取它以进行单元测试。我不知道为什么会这样,但是,您可能必须尝试 - 如果您可以直接获取 WebClient,请随时在此处更正。 我无法让它工作。我通过注入和显式创建 SslContext 进行了尝试,我将其作为选项传递给 ReactorClientHttpConnector,然后我将其传递给构建器以构建 WebClient。但是我打电话的服务器说我没有出示证书。我仔细检查了客户端和服务器端的密钥库和信任库,它们是有效的。此外,可以通过为 2-way TLS 和 SOAP UI 配置的 RestTemplate 访问服务器。 我修复了它,但不确定为什么你的不起作用。我用keystore初始化了一个KeyManagerFactory
后把.keyManager((PrivateKey) keyStore.getKey(keyAlias, keyStorePass.toCharArray()))
改成了.keyManager(keyManagerFactory)
,服务器终于接受了证书。
我可以测试我的旧代码,但问题是,它显然是私钥的证书链和信任链之间的区别,即使它包含相同的密钥。我现在发布的代码已经过测试和工作,但是证书链中密钥的排序可能会破坏信任链(至少我看到了这种情况)。希望现在这对其他人更有帮助。我不知道为什么这个标准用例如此复杂。【参考方案3】:
看起来 Spring 5.1.1 (Spring boot 2.1.0) 从 ReactorClientHttpConnector
中删除了 HttpClientOptions
,因此在创建 ReactorClientHttpConnector
的实例时无法配置选项
现在可行的一个选项是:
val sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build()
val httpClient = HttpClient.create().secure t -> t.sslContext(sslContext)
val webClient = WebClient.builder().clientConnector(ReactorClientHttpConnector(httpClient)).build()
基本上在创建 HttpClient 时,我们正在配置 insecure sslContext,然后将这个 httpClient 传递给全局ReactorClientHttpConnector
使用。
另一个选项是为TcpClient
配置不安全的sslContext,并使用它来创建HttpClient
实例,如下图所示:
val sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build()
val tcpClient = TcpClient.create().secure sslProviderBuilder -> sslProviderBuilder.sslContext(sslContext)
val httpClient = HttpClient.from(tcpClient)
val webClient = WebClient.builder().clientConnector(ReactorClientHttpConnector(httpClient)).build()
更多信息:
https://docs.spring.io/spring/docs/5.1.1.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-builder-reactor https://netty.io/4.0/api/io/netty/handler/ssl/util/InsecureTrustManagerFactory.html更新:相同代码的 Java 版本
SslContext context = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(context));
WebClient wc = WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
【讨论】:
"另一个选项是使用不安全的 sslContext 配置 TcpClient 并使用它来创建 HttpClient 实例"似乎随着 Spring Boot 2.4.0 的发布而被打破(reactor-净值 1.0.1)。您需要添加httpClient = httpClient.secure(tcpClient.configuration().sslProvider());
以继续使用TcpClient
或使用HttpClientConfig
作为解决方法,直到reactor-netty#1382 的修复程序发布。【参考方案4】:
查看使用 insecure TrustManagerFactory 的示例,它信任所有 X.509 证书(包括自签名证书)而无需任何验证。文档中的重要说明:
切勿在生产中使用此 TrustManagerFactory。它纯粹是为了测试目的,因此非常不安全。
@Bean
public WebClient createWebClient() throws SSLException
SslContext sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
ClientHttpConnector httpConnector = HttpClient.create().secure(t -> t.sslContext(sslContext) )
return WebClient.builder().clientConnector(httpConnector).build();
【讨论】:
感谢您的回答,我还需要设置读取和连接超时,如何实现? 有一条评论。最后一行应该是 WebClient.builder().clientConnector(httpConnector).build();否则不会编译。 这个解决方案在将 Spring Boot 升级到 2.1.0 后停止工作,这带来了 Spring 5.1.1,***.com/a/53147631/2172731 这对我使用 Spring Security 5.1.1 有效。以上是关于使用 ssl 的 Spring 5 WebClient的主要内容,如果未能解决你的问题,请参考以下文章
Spring-boot 为辅助 SSL 侦听器配置 client-auth=need
Spring boot jpa mysql 连接数据库SSL错误
如何使用带有 SSL 的 Spring WebSocketClient?