SSL enabled RESTful services are quite easier to develop and test using Jersey, Grizzly and RestTemplate.

Jersey (resource development)
Grizzly Web Server (resource configuration and deployment)
Spring 3 RestTemplate backed by Commons HTTP Client (resource access)

In a moment, you will notice how all these nicely fit the bill. Let us start with the POM for the maven fans.

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.web</artifactId>
            <version>3.0.0.M4</version>
        </dependency>
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>1.1.2-ea</version>
        </dependency>
        <dependency>
            <groupId>com.sun.grizzly</groupId>
            <artifactId>grizzly-servlet-webserver</artifactId>
            <version>1.9.18a</version>
        </dependency>
        <dependency>
            <groupId>org.apache.log4j</groupId>
            <artifactId>com.springsource.org.apache.log4j</artifactId>
            <version>1.2.15</version>
        </dependency>
    </dependencies>

Configuring Log4j is very useful as you could see the commons Client debug messages and http wire headers which are quite useful for debugging in case if you were lost in translation.

Putting together all these pieces working did not take much of my time. I did not have to do anything fancy here as I just reused most of the sample code from the Jersey HTTPS sample and Commons HTTP Client SSL sample.

Lets dive into the Spring Config, which does most of the wiring of HTTP Client and RestTemplate.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="sslClient" class="spring3.restclient.RestSSLClient">
        <constructor-arg ref="restTemplate"/>
        <constructor-arg ref="credentials"/>
    </bean>

    <bean id="httpClientParams" class="org.apache.commons.httpclient.params.HttpClientParams">
        <property name="authenticationPreemptive" value="true"/>
        <property name="connectionManagerClass"
                  value="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/>
    </bean>
    <bean id="httpClient" class="org.apache.commons.httpclient.HttpClient">
        <constructor-arg ref="httpClientParams"/>
    </bean>
    <bean id="credentials" class="org.apache.commons.httpclient.UsernamePasswordCredentials">
        <constructor-arg value="admin"/>
        <constructor-arg value="adminadmin"/>
    </bean>
    <bean id="httpClientFactory" class="org.springframework.http.client.CommonsClientHttpRequestFactory">
        <constructor-arg ref="httpClient"/>
    </bean>

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <constructor-arg ref="httpClientFactory"/>
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            </list>
        </property>
    </bean>
</beans>

The below code configures a Grizzly Server with SSL support for server side certificates, Basic Auth filter and Jersey resource servlet.

import com.sun.grizzly.SSLConfig;
import com.sun.grizzly.http.embed.GrizzlyWebServer;
import com.sun.grizzly.http.servlet.ServletAdapter;
import com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.sun.jersey.samples.https_grizzly.auth.SecurityFilter;

import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;

public class GrizzlyServer {

    private static GrizzlyWebServer webServer;

    public static final URI BASE_URI = getBaseURI();

    private static URI getBaseURI() {
        return UriBuilder.fromUri("https://localhost/").port(getPort(4463)).build();
    }

    private static int getPort(int defaultPort) {
        String port = System.getenv("JERSEY_HTTP_PORT");
        if (null != port) {
            try {
                return Integer.parseInt(port);
            } catch (NumberFormatException e) {
            }
        }
        return defaultPort;
    }

    protected static void startServer() {

        webServer = new GrizzlyWebServer(getPort(4463), ".", true);

        // add Jersey resource servlet

        ServletAdapter jerseyAdapter = new ServletAdapter();
        jerseyAdapter.addInitParameter("com.sun.jersey.config.property.packages", "server.https.auth;server.https.resource");
        jerseyAdapter.setContextPath("/");
        jerseyAdapter.setServletInstance(new ServletContainer());

        // add security filter (which handles http basic authentication)
        jerseyAdapter.addInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, SecurityFilter.class.getName());
        // add authorization filter
        jerseyAdapter.addInitParameter(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, RolesAllowedResourceFilterFactory.class.getName());

        webServer.addGrizzlyAdapter(jerseyAdapter, new String[]{"/"});


        // Grizzly ssl configuration
        SSLConfig sslConfig = new SSLConfig();

        sslConfig.setNeedClientAuth(true); // don't work - known grizzly bug, will be fixed in 2.0.0

        // set up security context
        String keystore_server = Thread.currentThread().getContextClassLoader().getResource("keystore_server").getFile();
        String truststore_server = Thread.currentThread().getContextClassLoader().getResource("truststore_server").getFile();

        sslConfig.setKeyStoreFile(keystore_server); // contains server keypair
        sslConfig.setKeyStorePass("secret");
        sslConfig.setTrustStoreFile(truststore_server); // contains client certificate
        sslConfig.setTrustStorePass("secret");

        webServer.setSSLConfig(sslConfig);

        // turn server side client certificate authentication on

//        ((SSLSelectorThread) webServer.getSelectorThread()).setNeedClientAuth(true);

        try {
            // start Grizzly embedded server //
            System.out.println(String.format("Jersey app started with WADL at %sapplication.wadl", BASE_URI));
            webServer.start();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    protected static void stopServer() {
        webServer.stop();
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        startServer();
        System.out.println("Hit return to stop...");
        System.in.read();
        stopServer();
    }
}

Here’s slightly modified version of the sample Jersey Security filter which would handle the HTTP basic authentication on the server. The auth helper classes (AuthenticationExceptionMapper, AuthenticationException) are found here.

package com.sun.jersey.samples.https_grizzly.auth;

import com.sun.jersey.api.container.MappableContainerException;
import com.sun.jersey.core.util.Base64;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import java.security.Principal;

public class SecurityFilter implements ContainerRequestFilter {

    @Context
    UriInfo uriInfo;
    private static final String REALM = "HTTPS Example authentication";

    public ContainerRequest filter(ContainerRequest request) {
        User user = authenticate(request);
        request.setSecurityContext(new Authorizer(user));
        return request;
    }

    private User authenticate(ContainerRequest request) {
        // Extract authentication credentials
        String authentication = request.getHeaderValue(ContainerRequest.AUTHORIZATION);
        if (authentication == null) {
            throw new MappableContainerException
                    (new AuthenticationException("Authentication credentials are required", REALM));
        }
        if (!authentication.startsWith("Basic ")) {
            return null;
            // additional checks should be done here
            // "Only HTTP Basic authentication is supported"
        }
        authentication = authentication.substring("Basic ".length());
        String[] values = new String(Base64.base64Decode(authentication)).split(":");
        if (values.length < 2) {
            throw new WebApplicationException(400);
            // "Invalid syntax for username and password"
        }
        String username = values&#91;0&#93;;
        String password = values&#91;1&#93;;
        if ((username == null) || (password == null)) {
            throw new WebApplicationException(400);
            // "Missing username or password"
        }

        // Validate the extracted credentials
        User user = null;

        if (username.equals("john") && password.equals("secret")) {
            user = new User("john", "user");
            System.out.println("USER 'John Doe' AUTHENTICATED");
        } else if (username.equals("jane") && password.equals("secret")) {
            user = new User("jane", "user");
            System.out.println("USER 'Jane Doe' AUTHENTICATED");
        } else if (username.equals("admin") && password.equals("adminadmin")) {
            user = new User("admin", "admin");
            System.out.println("ADMIN AUTHENTICATED");
        } else {
            System.out.println("USER NOT AUTHENTICATED");
            throw new MappableContainerException(new AuthenticationException("Invalid username or password\r\n", REALM));
        }
        return user;
    }

    public class Authorizer implements SecurityContext {

        private User user;
        private Principal principal;

        public Authorizer(final User user) {
            this.user = user;
            this.principal = new Principal() {

                public String getName() {
                    return user.username;
                }
            };
        }

        public Principal getUserPrincipal() {
            return this.principal;
        }

        public boolean isUserInRole(String role) {
            return (role.equals(user.role));
        }

        public boolean isSecure() {
            return "https".equals(uriInfo.getRequestUri().getScheme());
        }

        public String getAuthenticationScheme() {
            return SecurityContext.BASIC_AUTH;
        }
    }

    public class User {

        public String username;
        public String role;

        public User(String username, String role) {
            this.username = username;
            this.role = role;
        }
    }
}
&#91;/sourcecode&#93;

The resource class is very simple. The resource methods are access controlled using the JSR-250 annotation @RolesAllowed. The methods are self-explanatory and they are just coded for illustration, not a fool-proof implementation. In this sample, the Grizzly server would perform server-side certificate authentication and HTTP Basic authentication, in addition to basic authorization checks.

&#91;sourcecode language='java'&#93;
import com.sun.jersey.core.util.Base64;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/")
public class HttpsResource {

    @GET
    @RolesAllowed({"admin"})
    @Path("/locate/{username}")
    public Response getUserLocation(@Context HttpHeaders headers, @PathParam("username") String username) {
        // you can get username from HttpHeaders
        System.out.println("Service: GET / User Location for : " + username + " requested by " + getUser(headers));
        return Response.ok("Billings, Montana").type(MediaType.TEXT_PLAIN).build();
    }

    @GET
    @RolesAllowed({"admin", "user"})
    public Response getUserPin(@Context HttpHeaders headers) {
        // you can get username from HttpHeaders
        System.out.println("Service: GET / User Pin for: " + getUser(headers));
        return Response.ok("1234").type(MediaType.TEXT_PLAIN).build();
    }

    private String getUser(HttpHeaders headers) {
        String auth = headers.getRequestHeader("authorization").get(0);

        auth = auth.substring("Basic ".length());
        String&#91;&#93; values = new String(Base64.base64Decode(auth)).split(":");

        String username = values&#91;0&#93;;
        String password = values&#91;1&#93;;

        return username;
    }
}
&#91;/sourcecode&#93;

The following steps guide to create sample client and server certificates using the JDK keytool utility. The self-signed certificates are used for demonstration purposes only. In reality, this would be performed by a Certificate Authority (for ex: Verisign).

<strong><ul>generate client and server keys:</ul></strong>

keytool -genkey -keystore keystore_client -alias clientKey -dname "CN=www.aruld.info, OU=R&D, O=Vasun Technologies, L=Billings, ST=Montana, C=US"
keytool -genkey -keystore keystore_server -alias serverKey -dname "CN=www.aruld.info, OU=R&D, O=Vasun Technologies, L=Billings, ST=Montana, C=US"

<strong><ul>generate client and server certificates:</ul></strong>

keytool -export -alias clientKey -rfc -keystore keystore_client > client.cert
keytool -export -alias serverKey -rfc -keystore keystore_server > server.cert

<strong><ul>import certificates to corresponding truststores:</ul></strong>

keytool -import -alias clientCert -file client.cert -keystore truststore_server
keytool -import -alias serverCert -file server.cert -keystore truststore_client


SSL helper classes (AuthSSLProtocolSocketFactory, AuthSSLX509TrustManager, AuthSSLInitializationError) for the client-side are used from the Commons Client SSL <a href="http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/">contrib</a> samples.

RestTemplate is injected into the RestSSLClient which uses the Commons Client APIs to set the credentials and configures the keystore and truststore on the client-side.


import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.contrib.ssl.AuthSSLProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.springframework.http.client.CommonsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.HashMap;

public class RestSSLClient {
    private final RestTemplate restTemplate;
    private final HttpClient client;
    private Credentials credentials;
    private static final int HTTPS_PORT = 4463;
    private static final String HTTPS_GET = "https://localhost:4463/";
    private static final String HTTPS_GET_LOCATION = "https://localhost:4463/locate/{username}";
    private static final String HTTPS = "https";
    private static final String HTTPS_HOST = "localhost";

    public RestSSLClient(RestTemplate restTemplate, Credentials credentials) {
        this.restTemplate = restTemplate;
        this.credentials = credentials;
        CommonsClientHttpRequestFactory factory = (CommonsClientHttpRequestFactory) restTemplate.getRequestFactory();
        this.client = factory.getHttpClient();
        client.getState().setCredentials(AuthScope.ANY, credentials);
        try {
            URL keystore_client = Thread.currentThread().getContextClassLoader().getResource("keystore_client").toURI().toURL();
            URL truststore_client = Thread.currentThread().getContextClassLoader().getResource("truststore_client").toURI().toURL();
            ProtocolSocketFactory protocolSocketFactory = new AuthSSLProtocolSocketFactory(keystore_client, "secret",
                    truststore_client, "secret");
            Protocol authhttps = new Protocol(HTTPS, protocolSocketFactory, HTTPS_PORT);
            Protocol.registerProtocol(HTTPS, authhttps);
            client.getHostConfiguration().setHost(HTTPS_HOST, HTTPS_PORT, authhttps);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    public void setCredentials(String user, String pass) {
        this.credentials = new UsernamePasswordCredentials(user, pass);
        client.getState().setCredentials(AuthScope.ANY, credentials);
    }

    public String get() {
        return restTemplate.getForObject(HTTPS_GET, String.class);
    }

    public String getLocation(String user) {
        Map<String, String> vars = new HashMap<String, String>();
        vars.put("username", user);
        return restTemplate.getForObject(HTTPS_GET_LOCATION, String.class, vars);
    }
}

The test code which invokes the SSL configured resource is shown below.

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Spring3RestSSLClient {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-ssl.xml");
        RestSSLClient client = applicationContext.getBean("sslClient", RestSSLClient.class);
        System.out.println("John's Location : " + client.getLocation("john"));
        System.out.println("Jane's Location : " + client.getLocation("jane"));
        client.setCredentials("john", "secret");
        System.out.println("John Doe's Pin : " + client.get());
        client.setCredentials("jane", "secret");
        System.out.println("Jane Doe's Pin : " + client.get());
    }
}

WADL for this resource can be accessed from https://localhost:4463/application.wadl. You could access the URL from a browser as the server side client certificate authentication is disabled in the GrizzlyServer. (Uncommenting line # 70 would enable server side client cert auth, but this would force the browser to use the generated client keys). Test it yourself, you would be presented with a basic auth dialog (valid user/pass/role: admin/adminadmin/admin, john/secret/user, jane/secret/user) and you could access the resource methods with specified roles. I have tested with Firefox and Chrome. Enjoy!

Possibly Related Posts:


ChromeChrome incubates from beta to a stable release in just 100 days after its public beta launch. Chrome has become more stable and fast which is why I have been using it most often these days. It has fixed tons of bugs according to the sources. This release has at least fixed two of my sought after issues, one outlined here and other is the ability to render XML directly within the browser. It used to render XML as text in the beta release, but now it renders a well formatted XML, which is quite useful when I quickly browse through some maven POMs. The other cool feature I like is the ability to create application shortcuts and launch them as desktop applications. You can have a peek at my quick launch bar.

Windows Quick Launch bar

Its built in tools for developers is worth a mention. It comes with a task manager which measures memory usage in multi-process browser. You can look more details by clicking the “Stats for nerds” in the task manager or you can directly enter “about:memory”. It also has tools for debugging JavaScript.

Chrome Task Manager

I have been heavily using Chrome in my new laptop and liking its chemistry, but not an addict so far. 🙂

Possibly Related Posts:


It has been a wonderful week with my ramblings with CXF and SSL. But, it all ended up in good mood. Sometimes, it is so hard to find how to use certain features with CXF. I am a great fan of CXF because of its simplicity and intuitive feature set. It provides an array of integration points with best of breed technologies, but sadly most of the crux is buried under its system tests or CXF user mailing list archive. The documentation front is really lacking its meat from the “source”. So, people end up debugging CXF which is the only weapon to invade your configuration issues.

The community around CXF is very healthy and gained substantial momentum in all fronts for the past year or so. CXF user mailing list is such an awful resource. Its the first resort to get your queries answered and if you don’t get a response, don’t wait, just debug yourself. The resources are very limited and their support is really appreciated.

I wanted to contribute a sample for programmatic setup of SSL with CXF using embedded Jetty, so people get it right at the first time. So, I ported the sample “wsdl_first_https” which is bundled with CXF 2.1.1 distribution to a “java_first_https” scenario. The “wsdl_first_https” sample uses Spring based configuration for SSL. Though this scenario seem to fit well where the application uses spring as its underlying DI framework. For people who wish not to care about Spring, why would they want to unnecessarily bundle Spring jars for achieving CXF/SSL support in their application. But, this all depends on what approach you chose to build your services with CXF (either using Spring config or Java APIs). This is just a simple case for building this demo in “no-Spring” way. It opens a whole world of possibilities for dynamically configuring security in your embedded Jetty container.

Lets take the simple Greeter service which greets the client with its name sent as an input parameter.

@WebService
public interface Greeter {

    String greetMe(String me);
}


@javax.jws.WebService(name = "Greeter", serviceName = "SOAPService",
                      targetNamespace = "http://apache.org/hello_world_soap_http")
public class GreeterImpl implements Greeter {
    private static final Logger LOG =
        Logger.getLogger(GreeterImpl.class.getPackage().getName());

    public String greetMe(String me) {
        LOG.info("Executing operation greetMe");
        System.out.println("Executing operation greetMe");
        System.out.println("Message received: " + me + "\n");
        return "Hello " + me;
    }
}

The demo uses the same client and server certificate stores shipped with “wsdl_first_https” demo in CXF 2.1.1. So, don’t forget to include them when running this demo.

The Server side SSL configuration using CXF Java APIs is shown below.

public class Server {

    protected Server() throws Exception {
        System.out.println("Starting Server");
        String address = "https://localhost:9001/SoapContext/SoapPort";
        JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
        sf.setServiceClass(Greeter.class);
        sf.setAddress(address);

        Greeter implementor = new GreeterImpl();
        sf.getServiceFactory().setInvoker(new BeanInvoker(implementor));

        sf = configureSSLOnTheServer(sf, 9001);
        org.apache.cxf.endpoint.Server server = sf.create();
        String endpoint = server.getEndpoint().getEndpointInfo().getAddress();
        
        System.out.println("Server started at " + endpoint);
    }

    private JaxWsServerFactoryBean configureSSLOnTheServer(JaxWsServerFactoryBean sf, int port) {
        try {
            TLSServerParameters tlsParams = new TLSServerParameters();
            KeyStore keyStore = KeyStore.getInstance("JKS");
            String password = "password";
            File truststore = new File("certs\\cherry.jks");
            keyStore.load(new FileInputStream(truststore), password.toCharArray());
            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyFactory.init(keyStore, password.toCharArray());
            KeyManager[] km = keyFactory.getKeyManagers();
            tlsParams.setKeyManagers(km);

            truststore = new File("certs\\truststore.jks");
            keyStore.load(new FileInputStream(truststore), password.toCharArray());
            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(keyStore);
            TrustManager[] tm = trustFactory.getTrustManagers();
            tlsParams.setTrustManagers(tm);
            FiltersType filter = new FiltersType();
            filter.getInclude().add(".*_EXPORT_.*");
            filter.getInclude().add(".*_EXPORT1024_.*");
            filter.getInclude().add(".*_WITH_DES_.*");
            filter.getInclude().add(".*_WITH_NULL_.*");
            filter.getExclude().add(".*_DH_anon_.*");
            tlsParams.setCipherSuitesFilter(filter);
            ClientAuthentication ca = new ClientAuthentication();
            ca.setRequired(true);
            ca.setWant(true);
            tlsParams.setClientAuthentication(ca);
            JettyHTTPServerEngineFactory factory = new JettyHTTPServerEngineFactory();
            factory.setTLSServerParametersForPort(port, tlsParams);
        } catch (KeyStoreException kse) {
            System.out.println("Security configuration failed with the following: " + kse.getCause());
        } catch (NoSuchAlgorithmException nsa) {
            System.out.println("Security configuration failed with the following: " + nsa.getCause());
        } catch (FileNotFoundException fnfe) {
            System.out.println("Security configuration failed with the following: " + fnfe.getCause());
        } catch (UnrecoverableKeyException uke) {
            System.out.println("Security configuration failed with the following: " + uke.getCause());
        } catch (CertificateException ce) {
            System.out.println("Security configuration failed with the following: " + ce.getCause());
        } catch (GeneralSecurityException gse) {
            System.out.println("Security configuration failed with the following: " + gse.getCause());
        } catch (IOException ioe) {
            System.out.println("Security configuration failed with the following: " + ioe.getCause());
        }

        return sf;
    }

    public static void main(String args[]) throws Exception {
        System.out.println("The server's security configuration will be done programatically.");
        System.out.println();
        new Server();
        System.out.println("Server ready...");

        Thread.sleep(5 * 60 * 1000);
        System.out.println("Server exiting");
        System.exit(0);
    }
}

The SSL server configuration using Java APIs exactly maps to spring config “CherryServer.cxf” used in the distribution.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
  ** This file configures the Cherry Server.
 -->

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sec="http://cxf.apache.org/configuration/security"
  xmlns:http="http://cxf.apache.org/transports/http/configuration"
  xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration"
  xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
  xsi:schemaLocation="
       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
            http://cxf.apache.org/transports/http-jetty/configuration
            http://cxf.apache.org/schemas/configuration/http-jetty.xsd
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

  <http:destination name="{http://apache.org/hello_world_soap_http}GreeterPort.http-destination"> 
  </http:destination>

  <httpj:engine-factory>
   <httpj:engine port="9001">
    <httpj:tlsServerParameters>
      <sec:keyManagers keyPassword="password">
           <sec:keyStore type="JKS" password="password" 
                file="certs/cherry.jks"/>
      </sec:keyManagers>
      <sec:trustManagers>
          <sec:keyStore type="JKS" password="password"
               file="certs/truststore.jks"/>
      </sec:trustManagers>
      <sec:cipherSuitesFilter>
        <!-- these filters ensure that a ciphersuite with
          export-suitable or null encryption is used,
          but exclude anonymous Diffie-Hellman key change as
          this is vulnerable to man-in-the-middle attacks -->
        <sec:include>.*_EXPORT_.*</sec:include>
        <sec:include>.*_EXPORT1024_.*</sec:include>
        <sec:include>.*_WITH_DES_.*</sec:include>
        <sec:include>.*_WITH_NULL_.*</sec:include>
        <sec:exclude>.*_DH_anon_.*</sec:exclude>
      </sec:cipherSuitesFilter>
      <sec:clientAuthentication want="true" required="true"/>
    </httpj:tlsServerParameters>
   </httpj:engine>
  </httpj:engine-factory>
</beans>

The Client side SSL configuration using CXF Java APIs is shown below.

public class Client {

    private static void configureSSLOnTheClient(Object c) {
        org.apache.cxf.endpoint.Client client = ClientProxy.getClient(c);
        HTTPConduit httpConduit = (HTTPConduit) client.getConduit();

        try {
            TLSClientParameters tlsParams = new TLSClientParameters();
            tlsParams.setDisableCNCheck(true);

            KeyStore keyStore = KeyStore.getInstance("JKS");
            String trustpass = "password";

            File truststore = new File("certs\\truststore.jks");
            keyStore.load(new FileInputStream(truststore), trustpass.toCharArray());
            TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(keyStore);
            TrustManager[] tm = trustFactory.getTrustManagers();
            tlsParams.setTrustManagers(tm);

            truststore = new File("certs\\wibble.jks");
            keyStore.load(new FileInputStream(truststore), trustpass.toCharArray());
            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyFactory.init(keyStore, trustpass.toCharArray());
            KeyManager[] km = keyFactory.getKeyManagers();
            tlsParams.setKeyManagers(km);

            FiltersType filter = new FiltersType();
            filter.getInclude().add(".*_EXPORT_.*");
            filter.getInclude().add(".*_EXPORT1024_.*");
            filter.getInclude().add(".*_WITH_DES_.*");
            filter.getInclude().add(".*_WITH_NULL_.*");
            filter.getExclude().add(".*_DH_anon_.*");
            tlsParams.setCipherSuitesFilter(filter);

            httpConduit.setTlsClientParameters(tlsParams);
        } catch (KeyStoreException kse) {
            System.out.println("Security configuration failed with the following: " + kse.getCause());
        } catch (NoSuchAlgorithmException nsa) {
            System.out.println("Security configuration failed with the following: " + nsa.getCause());
        } catch (FileNotFoundException fnfe) {
            System.out.println("Security configuration failed with the following: " + fnfe.getCause());
        } catch (UnrecoverableKeyException uke) {
            System.out.println("Security configuration failed with the following: " + uke.getCause());
        } catch (CertificateException ce) {
            System.out.println("Security configuration failed with the following: " + ce.getCause());
        } catch (GeneralSecurityException gse) {
            System.out.println("Security configuration failed with the following: " + gse.getCause());
        } catch (IOException ioe) {
            System.out.println("Security configuration failed with the following: " + ioe.getCause());
        }
    }

    public static void main(String args[]) {
        System.out.println("The client's security configuration will be done programatically.");
        System.out.println();
        String address = "https://localhost:9001/SoapContext/SoapPort";
        JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
        proxyFactory.setServiceClass(Greeter.class);
        proxyFactory.setAddress(address);

        Greeter client = (Greeter) proxyFactory.create();
        configureSSLOnTheClient(client);

        System.out.println("Invoking greetMe...");
        try {
            String resp = client.greetMe(System.getProperty("user.name"));
            System.out.println("Server responded with: " + resp);
            System.out.println();

        } catch (Exception e) {
            System.out.println("Invocation failed with the following: " + e.getCause());
            System.out.println();
        }

    }
}

The SSL client configuration using Java APIs exactly maps to spring config “WibbleClient.cxf” used in the distribution.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
  ** This file configures the Wibble Client
  -->

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sec="http://cxf.apache.org/configuration/security"
  xmlns:http="http://cxf.apache.org/transports/http/configuration"
  xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
  xsi:schemaLocation="
           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
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

  <http:conduit name="{http://apache.org/hello_world_soap_http}SoapPort.http-conduit">
    <http:tlsClientParameters disableCNCheck="true">
      <sec:trustManagers>
          <sec:keyStore type="JKS" password="password"
               file="certs/truststore.jks"/>
      </sec:trustManagers>
      <sec:keyManagers keyPassword="password">
           <sec:keyStore type="JKS" password="password" 
                file="certs/wibble.jks"/>
      </sec:keyManagers>
      <sec:cipherSuitesFilter>
        <!-- these filters ensure that a ciphersuite with
          export-suitable or null encryption is used,
          but exclude anonymous Diffie-Hellman key change as
          this is vulnerable to man-in-the-middle attacks -->
        <sec:include>.*_EXPORT_.*</sec:include>
        <sec:include>.*_EXPORT1024_.*</sec:include>
        <sec:include>.*_WITH_DES_.*</sec:include>
        <sec:include>.*_WITH_NULL_.*</sec:include>
        <sec:exclude>.*_DH_anon_.*</sec:exclude>
      </sec:cipherSuitesFilter>
    </http:tlsClientParameters>
   </http:conduit>
</beans> 

You can download the NetBeans project here.

Possibly Related Posts: