Servicios Web SOAP con JAX-WS, Spring y CXF (III): Securización TLS + BASIC

Última actualización: 03/03/2019

logo java

En la mayoría de servicios web será necesario incluir mecanismos tanto de autenticación como de cifrado que «securicen» la utilización del servicio. En SOAP se pueden utilizar varias alternativas, las dos que probablemente se utilizan con mayor frecuencia son las siguientes:

  • HTTPS(TLS) + BASIC: La autenticación BASIC consiste simplemente en el envío en el header del request de un par usuario/contraseña y con el uso del protocolo TLS las comunicaciones se cifran lo que garantiza la confidencialidad de los datos enviados. Esta combinación es fácil de implementar y suele aplicarse tanto a servicios web SOAP como REST proporcionando en muchos casos un nivel de seguridad suficiente.
  • WsSecurity: es un protocolo específico para SOAP y proporciona un elevado nivel de seguridad ya que, entre otras características, permite realizar una firma y cifrado de los mensajes a nivel de aplicación mientras que con TLS sólo garantizamos el cifrado y validez del mensaje a través de la red hasta el servidor de destino. Este mecanismo implica una pequeña merma del rendimiento ya que las aplicaciones clientes y servidor deben firmar los mensajes.

En esta tercera parte del tutorial se va a implementar el mecanismo TLS + BASIC en los ejemplos de los capítulos anteriores.

  1. Servidor
  2. Clientes
  3. Securización TLS + BASIC
  4. Handlers

TLS

En primer lugar, debemos disponer del certificado digital que identificará al servidor que publica el WS y que permitirá cifrar la comunicación con el mismo. En producción es más que recomendable pagar por un certificado emitido por una empresa emisora oficialmente reconocida como Thawte o Symantec que permita asegurar la verdadera identidad del servidor y evitar por ejemplo ataques de tipo Man in the middle, así como el reconocimiento de «sitio web seguro» por parte de los navegadores. Otra opción más económica es utilizar los certificados gratuitos de Let’s encrypt.

Para desarrollar será suficiente con generar nuestro propio certificado con la herramienta keytool incluida en la jdk, o bien utilizar una interfaz gráfica para la misma como KeyStore Explorer. Con el siguiente comando se puede crear un nuevo keystore de tipo jks en el fichero indicado.

keytool -genkey -keyalg RSA -alias test -keystore /home/test/keystore-test.jks -storepass password -validity 360 -keysize 2048

Ahora será necesario configurar el servidor web que publica el WS para que utilice el protocolo TLS con el certificado que acabamos de generar. Esta configuración depende de cada servidor, en el caso de Tomcat 8 que es el servidor que utilizo habitualmente basta con descomentar las siguientes líneas en el fichero {tomcat}/conf/server.xml.

<!--<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" />
    -->

Se indica también el keystore, el alias y su contraseña

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS"
               keyAlias="test"
               keystoreFile="/home/test/keystore-test.jks"
               keystorePass="password" />

Adicionalmente se debería comenta la configuración del conector para http estándar y así aceptar sólo peticiones mediante https.

El servicio web de ejemplo estará ahora accesible en la url https://localhost:8443/spring-cxf-ws/ws/v/1/teamService?wsdl y no es necesario realizar ningún cambio en el mismo. Si se abre esta url a través de un navegador éste avisará de que el certificado, al no haber sido expedido por un emisor reconocido, no es fiable.

https tomcat selfsigned

Cliente

El cliente debe configurarse para utilizar TLS y, por seguridad, validar que el certificado del servidor sea el correcto. Los pasos a seguir son los siguientes

  1. Obtener el certificado utilizado por el servidor si no disponemos del mismo. Se puede hacer fácilmente con un navegador, en el caso de Firefox se abre la url y se pulsa en el «candado» que aparece en la barra de direcciones para mostrar los detalles de la página.

    firefor export certificate

    En la pestaña seguridad se pulsa «Ver certificado» para mostrar los detalles del mismo. En la nueva ventana que se abre, en la pestaña «Detalles» con el botón «Exportar» guardamos el certificado como x.509 (PEM).

  2. Importar el certificado en un keystore con el siguiente comando:
    keytool -import -alias wscert -file /home/demo/Descargas/certificado.cer -keystore keystorecliente
  3. El keystore debe ser físicamente accesible por el cliente, una buena opción es incluirlo en el directorio /src/main/resources para que sea empaquetado en la propia aplicación. Hay que tener en cuenta que los certificados tienen fecha de caducidad por lo que llegado el momento habrá que actualizarlo.
  4. En el applicationContext.xml hay que definir al menos un http-conduit para utilizar SSL
    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:jaxws="http://cxf.apache.org/jaxws"
      xmlns:http-conf="http://cxf.apache.org/transports/http/configuration"
            xmlns:sec="http://cxf.apache.org/configuration/security"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.2.xsd
            http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
            http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd
            http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd">
    
    <context:property-placeholder location="classpath:config.properties" />
    
    <jaxws:client id="teamServiceClient"
                  serviceClass="com.danielme.demo.jaxws.cxf.ws.ITeamService"
                  address="${endpoint}" />
                  
    <http-conf:conduit name="*.http-conduit">
         <http-conf:tlsClientParameters disableCNCheck="false">       
           <sec:trustManagers>
             <sec:keyStore type="JKS" password="password"
                resource="/keystorecliente.jks"  />
           </sec:trustManagers>
         </http-conf:tlsClientParameters>
     </http-conf:conduit>             
      
    </beans>
    

    El parámetro name del http-conf:conduit indica las url de los servicios web a los que se aplicará la configuración del mismo. La expresión «*.http-conduit» indica que se aplicará a todos, pero también se podría indicar parte de una url con
    https://localhost:8443/spring-cxf-ws/ws/* o directamente un endpoint en concreto https://localhost:8443/spring-cxf-ws/ws/v/1/teamService.

    En resource se indica la ubicación del keystore relativa al classpath. Otras opciones son utilizar file para indicar una ruta absoluta o url para indicar una ubicación web.

    La opción disableCNCheck permite deshabilitar la comprobación de que el nombre del host al que se conecte el cliente coincida con el valor CN del certificado. Si por ejemplo el CN es «midominio.com» y nos conectamos a localhost, obtendremos el siguiente error:

    The https URL hostname does not match the Common Name (CN) on the server certificate in the client's truststore

    Esta opción sólo debería utilizarse para desarrollar si fuera necesario pues es una validación que aumentará la seguridad del cliente.

  5. Por último, hay que proporcionar la nueva url del WS que en el proyecto de ejemplo se encuentra en el fichero /src/main/resources/config.properties

La configuración del certificado digital que hemos visto en el XML se puede realizar de forma programática con el siguiente código (ejemplo de cliente CXF sin Spring). Nota: en el tutorial las contraseñas se han definido en el código por simplicidad, pero deberían establecerse en un fichero externo.

// client
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setServiceClass(ITeamService.class);
jaxWsProxyFactoryBean.setAddress(properties.getProperty("endpoint"));
ITeamService teamServiceClient = (ITeamService) jaxWsProxyFactoryBean.create();

// certificado digital
TLSClientParameters tlsParams = new TLSClientParameters();
KeyStore keystore = KeyStore.getInstance("JKS");
String password = "password";
keystore.load(Main.class.getClassLoader().getResourceAsStream("keystorecliente.jks"), password.toCharArray());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keystore, trustpass.toCharArray());

TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(keystore);
TrustManager[] tm = trustFactory.getTrustManagers();
tlsParams.setTrustManagers(tm);
tlsParams.setDisableCNCheck(true);
Client proxy = ClientProxy.getClient(teamServiceClient);
((HTTPConduit) proxy.getConduit()).setTlsClientParameters(tlsParams);

BASIC

La autenticación puede implementarse en la aplicación, por ejemplo utilizando Spring Security, o bien directamente en el servidor e integrar la aplicación con el mismo. De forma rápida en el caso de Tomcat:

  • En el web.xml de la aplicación se configura la seguridad de acuerdo al estándar JEE. En el ejemplo se requerirá autenticación de tipo BASIC para las url en empiecen por /ws/ y que los usuarios que soliciten acceso deberán tener el rol «test».
     <security-constraint>
        <web-resource-collection>
            <url-pattern>/ws/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
             <role-name>test</role-name>
        </auth-constraint>
    </security-constraint>
    
    <login-config>      
        <auth-method>BASIC</auth-method>
    </login-config> 
    
  • En el fichero {tomcat}/conf/tomcat-users.xml se crea el rol y se añaden los usuarios. Esto sirve como solución provisional ya que en una aplicación «real» lo más habitual es que los usuarios y sus roles estén en una base de datos o directorio LDAP.
    <role rolename="test"/>
    <user username="usuario" password="password" roles="test"/>
    

Si accedemos al wsdl con un navegador nos solicitará las credenciales.

y si ejecutamos de nuevo el cliente obtendremos este error

HTTP response '401: Unauthorized' when communicating with https://localhost:8443/spring-cxf-ws/ws/v/1/teamService?wsdl

Las credenciales pueden establecerse en el applicationContext.xml o de forma programática. En el primer caso se añade la configuración de autenticación al http-conduit.

<http-conf:conduit name="https://localhost:8443/spring-cxf-ws/ws/v/1/teamService">
    <http-conf:tlsClientParameters disableCNCheck="true">       
       <sec:trustManagers>
         <sec:keyStore type="JKS" password="password"
            resource="/keystorecliente.jks"  />
       </sec:trustManagers>
     </http-conf:tlsClientParameters>
     <http-conf:authorization>
            <sec:UserName>usuario</sec:UserName>
       <sec:Password>password</sec:Password>
       <sec:AuthorizationType>Basic</sec:AuthorizationType>
     </http-conf:authorization>
 </http-conf:conduit>   

También puede hacerse en código:

ITeamService teamServiceClient = (ITeamService) applicationContext.getBean("teamServiceClient");    
Map requestContext = ((BindingProvider) teamServiceClient).getRequestContext();
requestContext.put(BindingProvider.USERNAME_PROPERTY, "usuario");
requestContext.put(BindingProvider.PASSWORD_PROPERTY, "password");

En el servidor se puede acceder a las credenciales inyectando WebServiceContext.

@Resource
WebServiceContext wsContext;
    
@Override
public List<Player> getTeam() 
{
  MessageContext messageContext = wsContext.getMessageContext();
  String user = (String) messageContext.get(BindingProvider.USERNAME_PROPERTY);
  String password = (String) messageContext.get(BindingProvider.PASSWORD_PROPERTY);
  logger.info("team requested");
  return team;
}

Código de ejemplo

El proyecto completo se encuentra en GitHub. Puesto que lo visto en esta tercera parte requiere de configuración en el contendor/servidor de aplicaciones, el nuevo código y configuración se encuentra comentado.

3 comentarios sobre “Servicios Web SOAP con JAX-WS, Spring y CXF (III): Securización TLS + BASIC

  1. Daniel, de la forma programática, entiendo que solo debo agregar en el cliente, el binding de usuario y el password, y no agregar nada de configuración. Sin embargo, antes de llegar al servidor me tira, en el mismo cliente, un error de autorización. Qué me puede estar faltando? Muchas gracias!

      1. Gracias por tu respuesta! En realidad el problema que tenía era porque me faltaba configurar en el Weblogic el usuario (similar a lo que vos pusiste del Tomcat)

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.