我正在尝试查找WebClient使用的示例。
我的目标是使用Spring 5 WebClient使用https和自签名证书查询REST服务。
有任何示例吗?
我正在尝试查找WebClient使用的示例。
我的目标是使用Spring 5 WebClient使用https和自签名证书查询REST服务。
有任何示例吗?
看起来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时,我们配置了不安全的sslContext,并将此httpClient传递给全局使用的ReactorClientHttpConnector。
另一个选项是使用不安全的sslContext配置TcpClient,并使用它来创建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()
更多信息请参考:
更新: 同样代码的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();
httpClient = httpClient.secure(tcpClient.configuration().sslProvider());
来继续使用TcpClient
,或者使用HttpClientConfig
作为解决方法,直到reactor-netty#1382问题得到修复。 - Mert Z.查看使用示例不安全的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();
}
为了适应spring-boot 2.0->2.1的变化,必须进行编辑。
如果您想编写生产代码,另一种方法是创建一个spring bean,使用spring-boot服务器的设置修改注入的WebClient,其中包括信任库和密钥库的位置。在客户端中,如果您使用双向ssl,则只需要提供密钥库。不确定为什么ssl设置没有预配置并且不能像真正很酷的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);
}
};
}
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();
}
}
.keyManager((PrivateKey) keyStore.getKey(keyAlias, keyStorePass.toCharArray()))
更改为.keyManager(keyManagerFactory)
,然后服务器终于接受了证书。 - SkywarpSslContext 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();
对于那些可能在如何使用响应式WebFlux webClient 消费 https 保护的 REST API 上遇到困难的人
您需要创建两个东西
注意:请查看上述项目中与两个Spring Boot应用程序共享的密钥库。并以编程方式添加了keyManagerFactory和TrustManagerFactory。
// for REST API 1 with sslContext1
WebClient webClient1 = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.secure(sslContextSpec -> sslContextSpec.sslContext(sslContext1))
))
.build();
// for REST API 1 with sslContext2
WebClient webClient2 = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.secure(sslContextSpec -> sslContextSpec.sslContext(sslContext2))
))
.build();
我们完成了!
另外,需要指出的是,默认情况下这些客户端将共享事件循环组,这是推荐的。但是,如果您使用runOn
或使用ReactorResourceFactory
进行配置,则不会共享。有关资源的更多信息,请参见此处:https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client-builder-reactor-resources