Web service con CXF y http basic authentication

De ChuWiki

Vamos a hacer un ejemplo sencillo de web service, tanto cliente como servidor, con http basic authentication. Este tipo de autentificación va sobre la cabecera http de los mensajes, por lo que queda fuera del protocolo SOAP del web service.


El servidor[editar]

Por el lado del servidor lo normal es dejar el tema de la http basic authentication al servidor (Apache Tomcat o el que sea). De todas formas, si nuestro web service es standalone, podemos hacer lo siguiente.

Cogemos esta implementación de la clase BasicAuthAuthorizationInterceptor.java y la añadimos a nuestro Endpoint

Aquí la clase


public class BasicAuthAuthorizationInterceptor extends
        SoapHeaderInterceptor {
    private Map<String,String users;
    protected Logger log = Logger.getLogger(getClass());
    @Override public void handleMessage(Message message)
            throws Fault {
        // This is set by CXF
        AuthorizationPolicy policy = message.get(
            AuthorizationPolicy.class);
        // If the policy is not set, the user did not specify
        // credentials. A 401 is sent to the client to indicate
        // that authentication is required
        if (policy == null) {
            sendErrorResponse(message,
                HttpURLConnection.HTTP_UNAUTHORIZED);
            return;
        }
        // Verify the password
        String realPassword = getAcualPassword(
            policy.getUserName());
        if (realPassword == null ||
                !realPassword.equals(policy.getPassword())) {
            log.warn("Invalid username or password for user: "
                + policy.getUserName());
            sendErrorResponse(message,
                HttpURLConnection.HTTP_FORBIDDEN);
        }
    }
    private void sendErrorResponse(Message message,
            int responseCode) {
        Message outMessage = getOutMessage(message);
        outMessage.put(Message.RESPONSE_CODE,
            responseCode);
        // Set the response headers
        Map responseHeaders = message.get(
            Message.PROTOCOL_HEADERS);
        if (responseHeaders != null) {
            responseHeaders.put("WWW-Authenticate",
                Arrays.asList(new String[]{"Basic realm=realm"}));
            responseHeaders.put("Content-Length",
                Arrays.asList(new String[]{"0"}));
        }
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            close(outMessage);
        } catch (IOException e) {
            log.warn(e.getMessage(), e);
        }
    }
    private Message getOutMessage(Message inMessage) {
        Exchange exchange = inMessage.getExchange();
        Message outMessage = exchange.getOutMessage();
        if (outMessage == null) {
            Endpoint endpoint = exchange.get(Endpoint.class);
            outMessage = endpoint.getBinding().createMessage();
            exchange.setOutMessage(outMessage);
        }
        outMessage.putAll(inMessage);
        return outMessage;
    }
    private Conduit getConduit(Message inMessage)
            throws IOException {
        Exchange exchange = inMessage.getExchange();
        EndpointReferenceType target = exchange.get(
            EndpointReferenceType.class);
        Conduit conduit =
            exchange.getDestination().getBackChannel(
            inMessage, null, target);
        exchange.setConduit(conduit);
        return conduit;
    }
    private void close(Message outMessage)
            throws IOException {
        OutputStream os = outMessage.getContent(
            OutputStream.class);
        os.flush();
        os.close();
    }
}

y aquí el añadido al Endpoint

package com.chuidiang.ejemplos.ws;

import java.util.HashMap;
import java.util.Map;

import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.log4j.BasicConfigurator;

public class Prueba {

   public static void main(String[] args) {
      BasicConfigurator.configure();

      EndpointImpl jaxWsEndpoint = (EndpointImpl) EndpointImpl
            .publish(
                  "http://172.30.210.35:8080/UnWebService",
                  new UnWebService());

      Endpoint cxfEndpoint = jaxWsEndpoint.getServer().getEndpoint();

      BasicAuthAuthorizationInterceptor basic = new BasicAuthAuthorizationInterceptor();
      // Un map con usuarios y password validos.
      Map<String, String> usuarios = new HashMap<String, String>();
      usuarios.put("joe", "password");
      basic.setUsers(usuarios);
      cxfEndpoint.getInInterceptors().add(basic);

   }
}

Unicamente, después de instanciarla, debemos pasarle un Map con los usuarios y password válidos. Podemos modificar por dentro la clase para que obtenga o verifique usuarios y password de otra manera.

Después de instanciarla, se añade a los interceptors.

El cliente[editar]

Ahora solo nos queda que nuestro cliente, en sus peticiones, ponga el usuario password en la cabecera http. Esto se puede hacer de la siguiente forma

package com.chuidiang.ejemplos.ws;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;

public class PruebaCliente {

   /**
    * @param args
    * @throws IOException
    */
   public static void main(String[] args) throws IOException {
      try {
         UnWebServiceService cliente = new UnWebServiceService(
               new URL(
                     "http://172.30.210.35:8080/UnWebService?wsdl"));
         UnWebServiceImpl servicio = cliente
               .getUnWebServiceServicePort();

         org.apache.cxf.endpoint.Client client = ClientProxy
               .getClient(servicio);

         HTTPConduit http = (HTTPConduit) client.getConduit();
         http.getAuthorization().setUserName("joe");
         http.getAuthorization().setPassword("password");

         System.out.println(servicio.unMetodo(unosParametros);

      } catch (MalformedURLException e) {
         e.printStackTrace();
      }
   }

}

UnWebServiceService y UnWebServiceImpl son las clases generadas por wsdl2java para el cliente. Con getConduit() y haciendo el cast a HTTPConduit, podemos configurar cosas de la cabecera http. Simplemente fijar dos parametros: usuario, password.

Ya sólo nos queda llamar al web service.