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.
¿Por qué usar SSL con Spring?
Hay varias razones por las cuales es recomendable usar SSL (Secure Sockets Layer) con WebClient:
- Seguridad de la comunicación: SSL proporciona una capa de seguridad adicional al cifrar los datos transmitidos entre el cliente y el servidor. Esto ayuda a proteger la información confidencial de posibles ataques de interceptación o espionaje.
- Integridad de los datos: SSL incluye mecanismos para verificar la integridad de los datos, lo que garantiza que no hayan sido modificados durante la transmisión. Esto evita la manipulación de datos por parte de terceros malintencionados.
- Autenticación del servidor: SSL permite autenticar el servidor, lo que garantiza que el cliente se está conectando al servidor correcto y no a una entidad maliciosa. Esto ayuda a prevenir ataques de intermediarios y garantiza una comunicación segura y confiable.
- Cumplimiento normativo: En muchas industrias y entornos regulados, se exige el uso de SSL para proteger la privacidad y seguridad de los datos sensibles, como información personal, datos financieros o registros médicos. Usar SSL con WebClient ayuda a cumplir con estos requisitos normativos.
- Confianza del usuario: Al implementar SSL, se transmite confianza a los usuarios al garantizar que la comunicación sea segura y protegida. Esto puede mejorar la reputación de su aplicación o servicio y generar confianza entre los usuarios.
En resumen, el uso de SSL con WebClient brinda seguridad, integridad y autenticación a la comunicación entre el cliente y el servidor. Ayuda a proteger los datos sensibles, cumplir con los estándares de seguridad y generar confianza en su plataforma.
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”