1. Introducción
En este post veremos como arrancar un microservicio de SpringBoot haciendo uso del protocolo https. Os podéis descargar el código de ejemplo de mi GitHub aquí.
Tecnologías empleadas:
- Java 8
- Gradle 3.1
- SpringBoot 1.5.2.RELEASE
- Spring 4.3.7.RELEASE
2. Estructura del proyecto
3. Configuración para crear el conector https
El fichero build.gradle
group 'com.jorgehernandezramirez.spring.springboot' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'org.springframework.boot' apply plugin: 'maven' sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.spring.springboot.https.Application" } repositories { mavenCentral() } dependencies { compile("org.springframework.boot:spring-boot-starter-web") }
El fichero main que arranca SpringBoot
package com.jorgehernandezramirez.spring.springboot.https; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public Application(){ //For Spring } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Nos creamos un controlador de pruebas.
package com.jorgehernandezramirez.spring.springboot.https.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/ping") public class TestController { @RequestMapping public String doAlive() { return "Alive!"; } @RequestMapping("/rest") public String doRestAlive() { return new RestTemplate() .getForObject("https://localhost:8443/ping", String.class); } }
A continuación viene lo verdaderamente relevante. Vamos a crear un almacén de claves y situarlo en la raíz de nuestro classpath. Para ello vamos a utilizar la herramienta keytool
que viene integrada en la jdk.
keytool -genkey -alias springboot -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
PD: La password de nuestro almacén de claves, que se nos solicitó en la creación del mismo, fue 12345678
Para habilitar la configuración para que nuestro servidor escuche peticiones por el protocolo https, deberemos hacer.
server: port: 8443 ssl: key-store: classpath:keystore.p12 key-store-password: 12345678 keyStoreType: PKCS12 keyAlias: springboot
4. Configuración para habilitar el conector http y https
La configuración vista anteriormente sólo permite atacar al servidor utilizando el protocolo https. Sin embargo si estamos interesados en habilitar, además, el protocolo http deberemos hacer.
Configuración del conector http
Configuración del conector https
Lo deberemos hacer a través de la creación del objeto EmbeddedServletContainerFactory
package com.jorgehernandezramirez.spring.springboot.https.configuration; import org.apache.catalina.connector.Connector; import org.apache.coyote.http11.Http11NioProtocol; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.net.ssl.HttpsURLConnection; import java.io.IOException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; @Configuration public class SSLTomcatConfiguration { private static final String CONNECTOR_CLASS = "org.apache.coyote.http11.Http11NioProtocol"; private static final String HTTPS = "https"; private static final Integer PORT = 8443; private static final String KEYSTORE_FILE = "classpath:keystore.p12"; private static final String KEYSTORE_PASSWORD = "12345678"; @Bean public EmbeddedServletContainerFactory servletContainer() throws KeyManagementException, NoSuchAlgorithmException, InterruptedException, IOException { final TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); tomcat.addAdditionalTomcatConnectors(createSslConnector()); return tomcat; } private Connector createSslConnector() throws InterruptedException, IOException { final Connector connector = new Connector(CONNECTOR_CLASS); final Http11NioProtocol protocol = (Http11NioProtocol)connector.getProtocolHandler(); connector.setScheme(HTTPS); connector.setSecure(true); connector.setPort(PORT); protocol.setSSLEnabled(true); protocol.setKeystoreFile(KEYSTORE_FILE); protocol.setKeystorePass(KEYSTORE_PASSWORD); return connector; } }
5. Probando la aplicación
La respuesta obtenida fue
Alive!
La respuesta obtenida fue
There was an unexpected error (type=Internal Server Error, status=500).
I/O error on GET request for “https://localhost:8443/ping”: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException
La petición anterior hace una llamada rest a un controlador que hace a su vez una llamada a /ping haciendo uso de la clase RestTemplate de Spring.
La excepción se produce ya que el certificado que ha sido enviado por el servidor no es confiable, produciendose la excepción SunCertPathBuilderException.
Solución
Exportar el certificado de nuestro almacén de claves
Importarlo al almacén de confianza
Vamos a importar nuestro certificado mycertificate.cer al almacén de confianza por defecto que utiliza la JDK y que se encuentra en $JAVA_HOME/jre/lib/security/cacerts
keytool -import -keystore $JAVA_HOME/jre/lib/security/cacerts -alias springboot -file mycertificate.cer
PD: Por defecto, la contraseña del almacén de confianza cacerts de la JDK es changeit
Si volvemos a atacar a la url ya funcionaría
La respuesta obtenida fue
Alive!