SSL con WebClient de Spring
Configuración SSL para WebClient de Spring
A continuación os mostraremos como configurar SSL con WebClient de Spring y como resolver uno de los problemas más comunes que nos pueden surgir cuando configuramos un WebClient, que es como configurarlo para una conexión SSL.
Si no tienes experiencia con WebClient te recomendamos nuestro anterior artículo, sobre como utilizar e implementarlo, pulsa aquí para ir.
Librerías necesarías para añadir SSL en WebClient
Antes de comenzar necesitamos añadir las siguientes librerías a nuestro pom.xml, así que vamos a incluirlas:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.projectreactor</groupId> <artifactId>reactor-spring</artifactId> <version>${reactor-version}</version> </dependency> <!--netty--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-reactor-netty</artifactId> </dependency> <dependency> <groupId>io.projectreactor.ipc</groupId> <artifactId>reactor-netty</artifactId> <version>0.7.15.RELEASE</version> </dependency>
Vamos a usar diferentes clases de SSL de netty, el cual es un framework que nos permite realizar apliaciones asíncronas event-driven.
Manos a la obra
Ahora vamos a realizar dos diferentes implementaciones, la primera de ella va a ser una configuración que confiará en todos los certificados; y la segunda será con un certificado.
Implementación WebClient confiando en todos los cerficados X.509 (InsecureTrustManagerFactory)
@Bean public WebClient webClient(String baseUrl) throws Exception { var sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build(); var httpConnector = new ReactorClientHttpConnector(options -> { options.sslContext(sslContext); options.option(ChannelOption.SO_TIMEOUT, timeout); }); return WebClient.builder().clientConnector(httpConnector) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .baseUrl(baseUrl).build(); }
Para poder añadir un contexto SSL vamos a realizar los sguientes 3 pasos:
- Crear el objeto SslContext
- Definir un httpConnector
- Crear un WebClient con el connector definido.
Implementción WebClient con Certificado
Hay que tener en cuenta que la solución propuesta anteriormente no es recomendable usarla en producción. Así que aquí os mostramos como realizar una implementación con seguridad:
@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(trustore); } catch (KeyStoreException ex) { throw new RuntimeException("Error getting truststore", e1); } }) .map(trustore -> { try { return trustStore.getCertificate(trustore); } catch (KeyStoreException ex) { throw new RuntimeException("Error getting truststore", ex); } }) .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); } }; }
En este fragmento de código hacemos los siguientes pasos:
- Leemos la configuración de donde están el trustore y el keystore.
- Definimos el cliente con el contexto ssl.
- Creamos el WebClient con el HttpClient definido.
Conclusión:
En este artículo hemos visto como configurar SSL con WebClient de Spring, el cual fue introducido en la versión 5 de Spring para ir sustituyendo al RestTemplate. Debido a que este cliente es reactivo, para realizar la configuración para SSL, hemos tenido que introducir una librería también reactiva como netty.
La implementación de seguridad para poder utilizar este cliente, se hace bastante asequible gracias al uso de Spring y de las clases que nos aporta netty.
1 pensamiento sobre “SSL con WebClient de Spring”