Wednesday, July 27, 2016

Enabling WS-Security in Spring Boot using CXF, JAX-WS and JAXB

In my previous post, I've shown how to quickly create a WSDL/SOAP based web service. This post will build on top of that to include WS-Security. We'll be using simple username/password authentication.



Add the following artifact into your Spring Boot pom.xml:


    org.apache.cxf
    cxf-rt-ws-security
    3.1.6


We create a callback handler class to perform our custom authentication. This class implements the CallbackHandler interface:

package com.techtots.security;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WSSecurityCallback implements CallbackHandler {

    
    private static final Logger log = LoggerFactory.getLogger(WSSecurityCallback.class);

    
    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        

        WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];

        log.info("Identifier: " + callback.getIdentifier());

        // you won't be able to retrieve the password using callback.getPassword().
        // to authenticate a user, you'll need to set the password tied to the user.
        // user credentials are typically retrieved from DB or your own authentication source.
        // if the password set here is the same as the password passed by caller, authentication is successful.
        callback.setPassword("wspassword");
                    
    }
}

In order to enable WS-Security, we'll need to change the WebServiceConfig class as below:

@Bean
public EndpointImpl userServiceEndpoint() {
    EndpointImpl ep = new EndpointImpl(springBus(), new UserServiceImpl());
    ep.publish("/UserService");
    
    Endpoint cxfEndPoint = ep.getServer().getEndpoint();
    
    Map inProps = new HashMap<>();
    inProps.put(ConfigurationConstants.ACTION, ConfigurationConstants.USERNAME_TOKEN);
    inProps.put(ConfigurationConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
    inProps.put(ConfigurationConstants.PW_CALLBACK_CLASS, WSSecurityCallback.class.getName());
    
    WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps);
    cxfEndPoint.getInInterceptors().add(wssIn);
    
    return ep;
}

Full WebServiceConfig class as below:

package com.techtots;

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

import javax.security.auth.callback.CallbackHandler;

import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.wss4j.common.ConfigurationConstants;
import org.apache.wss4j.dom.WSConstants;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.techtots.security.WSSecurityCallback;
import com.techtots.services.UserServiceImpl;

@Configuration
public class WebServiceConfig {
    @Bean
    public ServletRegistrationBean dispatcherSerlvet() {
        return new ServletRegistrationBean(new CXFServlet(), "/services/*");
    }
    
    @Bean(name = Bus.DEFAULT_BUS_ID)
    public SpringBus springBus() {
        return new SpringBus();
    }
    
    @Bean
    public CallbackHandler usernameTokenCallback() {
        return new WSSecurityCallback();
    }
    
    @Bean
    public EndpointImpl userServiceEndpoint() {
        EndpointImpl ep = new EndpointImpl(springBus(), new UserServiceImpl());
        ep.publish("/UserService");
        
        Endpoint cxfEndPoint = ep.getServer().getEndpoint();
        
        Map inProps = new HashMap<>();
        inProps.put(ConfigurationConstants.ACTION, ConfigurationConstants.USERNAME_TOKEN);
        inProps.put(ConfigurationConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
        inProps.put(ConfigurationConstants.PW_CALLBACK_CLASS, WSSecurityCallback.class.getName());
        
        WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps);
        cxfEndPoint.getInInterceptors().add(wssIn);
        
        return ep;
        
    }

}

Startup your app and you should see your normal WSDL. But sending a "normal" request will result in the following response:


   
      
         ns1:SecurityError
         A security error was encountered when verifying the message
      
   


You'll need to add the WSSE headers to include username and password:



    
        
            
                wsuser
                wspassword
                v+pC7eS4q1tG+GGolKBrgw==
                2016-07-27T02:48:46.895Z
            
        
    
    
        
            
                techtots
                234234324
                techtots@gmail.com
            
        
    


You can set this via SoapUI under Request Properties as shown in the screenshot below:


3 comments:

Suseela Susiee said...



That is very interesting; you are a very skilled blogger. I have shared your website in my social networks..!

Digital Marketing Services in Chennai

Digital Marketing Company in Chennai

Padhma said...

very interesting security purpose article,but i want more details about it.
Back to original

Patrick Pichler said...

Hi Mike,
thx for the helpful post. I have some problems to retrieve user credentials from my local SQL DB.

I use an userDAO and autowired it in my callback handler, but I always receive a null pointer exception.

Is it possible to give me a hit to retrieve user credentials in a easy way from my local sql db?

Many thx in advance,

patrick