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:


4 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...
This comment has been removed by the author.
Ancy merina said...

I like this blog, saved to my bookmarks.I have got some important suggestions from it.
Web developers in bangalore
Website Design and Development Companies in Bangalore
ECommerce Web Design Company in bangalore
Outsource magento ecommerce services india