Glenn Van Schil
Glenn Van Schil

Reputation: 1099

How to secure a Spring soap service with certificates

I managed to create a soap server and client that requires a username and password to call functions. The following code shows how i've done this

Client:

package hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor;
import org.springframework.ws.soap.security.xwss.callback.SpringUsernamePasswordCallbackHandler;

@Configuration
public class SoftlayerConfiguration {

    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("be.elision.soap.cloud");
        return marshaller;
    }

    @Bean
    public SoftlayerClient softlayerClient(Jaxb2Marshaller marshaller) {
        SoftlayerClient client = new SoftlayerClient();
        client.setDefaultUri("http://192.168.137.107:8080/ws");
        client.setMarshaller(marshaller);
        client.setUnmarshaller(marshaller);
        client.setInterceptors(new ClientInterceptor[]{securityInterceptor()});
        return client;
    }

    @Bean
    XwsSecurityInterceptor securityInterceptor() {
        XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
        securityInterceptor.setSecureRequest(true);
        securityInterceptor.setValidateRequest(true);
        securityInterceptor.setCallbackHandler(callbackHandler());
        securityInterceptor.setPolicyConfiguration(new ClassPathResource("securityPolicy.xml"));
        return securityInterceptor;
    }

    @Bean
    SpringUsernamePasswordCallbackHandler callbackHandler() {
        SecurityContextHolder.setContext(new SecurityContextImpl());
        SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("user", "password"));
        return new SpringUsernamePasswordCallbackHandler();
    }

}

Server:

package be.elision.main;

import java.util.Collections;
import java.util.List;

import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.server.EndpointInterceptor;
import org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor;
import org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor;
import org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler;
import org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
    @Bean
    public ServletRegistrationBean messageDispatcherServlet(
            ApplicationContext applicationContext) {
        MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        servlet.setTransformWsdlLocations(true);
        return new ServletRegistrationBean(servlet, "/ws/*");
    }

    @Bean(name = "devices")
    public DefaultWsdl11Definition defaultWsdl11Definition(
            XsdSchema devicesSchema) {
        DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
        wsdl11Definition.setPortTypeName("DevicesPort");
        wsdl11Definition.setLocationUri("/ws");
        wsdl11Definition.setTargetNamespace("http://elision.be/soap/cloud");
        wsdl11Definition.setSchema(devicesSchema);
        return wsdl11Definition;
    }

    // toevoegen van xsd aan bean
    @Bean
    public XsdSchema devicesSchema() {
        return new SimpleXsdSchema(new ClassPathResource("devices.xsd"));
    }

    @Bean
    XwsSecurityInterceptor securityInterceptor() {
        XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
        securityInterceptor.setCallbackHandler(callbackHandler());
        securityInterceptor.setPolicyConfiguration(new ClassPathResource(
                "securityPolicy.xml"));
        return securityInterceptor;
    }

    @Bean
    SimplePasswordValidationCallbackHandler callbackHandler() {
        SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler();
        callbackHandler.setUsersMap(Collections
                .singletonMap("user", "password"));
        return callbackHandler;
    }

    @Bean
    PayloadLoggingInterceptor payloadLoggingInterceptor() {
        return new PayloadLoggingInterceptor();
    }

    @Bean
    PayloadValidatingInterceptor payloadValidatingInterceptor() {
        final PayloadValidatingInterceptor payloadValidatingInterceptor = new PayloadValidatingInterceptor();
        payloadValidatingInterceptor.setSchema(new ClassPathResource(
                "devices.xsd"));
        return payloadValidatingInterceptor;
    }

    @Override
    public void addInterceptors(List<EndpointInterceptor> interceptors) {
        interceptors.add(payloadLoggingInterceptor());
        interceptors.add(payloadValidatingInterceptor());
        interceptors.add(securityInterceptor());
    }

}

Security policy:

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:UsernameToken digestPassword="false" useNonce="false" />
</xwss:SecurityConfiguration>

But now i want to replace this username and password authentication with a certificate. I prefere not to do this in xml, but rather implement this in my existing code as shown above.

Upvotes: 0

Views: 6761

Answers (1)

M92
M92

Reputation: 66

We finaly figured this out some months ago, from what I remember our configuration looked like this.

add this to your web.xml

<servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

this needs to be present in your mvc-dispatcher-servlet.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/web-services
        http://www.springframework.org/schema/web-services/web-services-2.0.xsd">

    <context:component-scan base-package="com.yourpackage" />
    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory" />
        <property name="defaultUri"
            value="${backend.ip}devices" />
        <property name="interceptors">

            <list>
                <ref local="xwsSecurityInterceptor" />
            </list>
        </property>

    </bean>

    <bean id="xwsSecurityInterceptor"
        class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
        <property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" />
        <property name="callbackHandlers">
            <list>
                <ref bean="keyStoreHandler" />
            </list>
        </property>
    </bean>

    <bean id="keyStore"
        class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="password" value="yourpassword" />
        <property name="location" value="/WEB-INF/yourkeystore.jks" />
    </bean>

    <bean id="keyStoreHandler"
        class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="keyStore" ref="keyStore" />
        <property name="privateKeyPassword" value="yourpassword" />
        <property name="defaultAlias" value="client" />
    </bean>

    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

    <!-- LOAD PROPERTIES -->
    <context:property-placeholder
        location="WEB-INF/config.properties"
        ignore-unresolvable="true" />

    <mvc:resources mapping="/resources/**" location="/resources/" />
    <mvc:annotation-driven />

</beans>

The jks file is a java keystore file wich holds your certificate.

SecurityPolicy.xml

    <xwss:SecurityConfiguration dumpMessages="true"     xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
   <xwss:Sign includeTimestamp="true">
         <xwss:X509Token certificateAlias="yourcertificatealias" /> 
    </xwss:Sign> 
</xwss:SecurityConfiguration>

This is all done using the xws-security library from oracle, you need the following dependency for this.

<dependency>
        <groupId>com.sun.xml.wss</groupId>
        <artifactId>xws-security</artifactId>
        <version>3.0</version>
        <exclusions>
            <exclusion>
                <groupId>javax.xml.crypto</groupId>
                <artifactId>xmldsig</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

We finnaly got this working after we found some good explanation about certificate authentication in this awesome book!

'Spring Web Services 2 Cookbook by Hamidreza Sattari'

If you have any further questions regarding this implementation then just ask away! :d

Upvotes: 1

Related Questions