DEV Community

Ryszard Grodzicki
Ryszard Grodzicki

Posted on

SOAP security using WSS4J in Apache CXF in Java/Kotlin

Securing SOAP communication in CXF using standard WSSE is not a trivial task, especially considering the documentation is quite old and often shows XML Spring bean configuration and other, a bit outdated, stuff.

We will see how to configure the security both on the server and the client side.

Short explanation of terms used:

  • WSS - Web Service Security - Official specification of securing SOAP Web Services
  • Apache CXF - implementation of SOAP protocol in Java. Used both for server side and client
  • Apache WSS4J - A library from Apache that integrates with CXF and implements WSS specification. We will use the UsernameToken method.

(I'm showing Kotlin code but Java should be very similar and easy to convert to)

Setup

So at the start you should have your classes generated from a wsdl file. Then the CXF's Endpoint should be configured with a CXF Bus and implementation of the main WSDL-generated interface.

I used cxf-spring-boot-starter-jaxws to have the Bus already configured. I declared the SomePort implementation before and EndpointImpl like that:

// in Kotlin apply block just makes the object available as this
@Bean
fun endpoint(port: SomePort){
    EndpointImpl(bus, port).apply {
        this.inInterceptors.add(LoggingInInterceptor())
        this.publish("/ws")
    }
}
Enter fullscreen mode Exit fullscreen mode

With the CXF Spring Boot starter mentioned above, it's all that is needed to have the service up and running.

We will work on this definition to include the WS-security.

Server

So, first things first - let's add the needed dependencies. Here's what you need to add to your maven pom.xml (check if newer versions are available) :

<dependency>
    <groupId>org.apache.wss4j</groupId>
    <artifactId>wss4j</artifactId>
    <version>2.3.0</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-ws-security</artifactId>
    <version>3.4.1</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Next, we will add the WSS4JInInterceptor to the endpoint, just like the LoggingInInterceptor was already added.
But before that, we will need to declare some properties for this interceptor :

val wss4jProps = mapOf(
    WSHandlerConstants.ACTION to WSHandlerConstants.USERNAME_TOKEN,
    WSHandlerConstants.PASSWORD_TYPE to WSConstants.PASSWORD_DIGEST,
    WSHandlerConstants.PW_CALLBACK_REF to PasswordCallback(username, password)
)
Enter fullscreen mode Exit fullscreen mode

Here we are declaring the UsernameToken method, the type of password and the password callback reference. It's a class we'll have to implement by ourselves. Here the username and password are predefined and constant, but we may as well use database for verification of multiple users:

class PasswordCallback(
        private val username: String,
        private val password: String,
) : CallbackHandler {
    override fun handle(callbacks: Array<out Callback>) {
        val pc = callbacks[0] as WSPasswordCallback
        if (pc.identifier == username) {
            pc.password = password
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, finally, we can add our new interceptor to the endpoint configuration:

// in java you will access inInterceptors through the getter
this.inInterceptors.add(WSS4JInInterceptor(wss4jProps))
Enter fullscreen mode Exit fullscreen mode

Client

To configure the client we'll use JaxWsProxyFactoryBean and WSS4JOutInterceptor.
So we're starting from a plain configuration without security which looks like this:

JaxWsProxyFactoryBean().apply {
    this.serviceClass = SomePort::class.java
    this.address = wsAddress
    this.outInterceptors.add(LoggingOutInterceptor())
    this.inInterceptors.add(LoggingInInterceptor())
}.create() as SmpsPort
Enter fullscreen mode Exit fullscreen mode

Once again - SomePort is an interface generated from wsdl. wsAddress parameter is the url to our service. There are also in and out logging interceptors.

Security

Here we also have to configure a map with configuration for our WSS4J interceptor, but this time it looks a bit different:

private val wss4jProps = mapOf(
    WSHandlerConstants.ACTION to WSHandlerConstants.USERNAME_TOKEN,
    WSHandlerConstants.USER to username,
    WSHandlerConstants.PW_CALLBACK_REF to PasswordCallback(username, password)
)
Enter fullscreen mode Exit fullscreen mode

Then, we can configure the interceptor in JaxWsProxyFactoryBean:

this.outInterceptors.add(WSS4JOutInterceptor(wss4jProps))
Enter fullscreen mode Exit fullscreen mode

HTTP Basic Authorization

If you need an HTTP Basic authorization then there is a fast way to do so on the client side.
On a previously configured JaxWsProxyFactoryBean, instead of adding interceptor, there are fields for username and password:

jaxWsProxyFactoryBean.username = username
jaxWsProxyFactoryBean.password = password
Enter fullscreen mode Exit fullscreen mode

Summary

We have seen how to configure CXF's UsernameToken security.
We ended up with the endpoint configured:

// in Kotlin apply block just makes the object available as this
@Bean
fun endpoint(port: SomePort){
    EndpointImpl(bus, port).apply {
        this.inInterceptors.add(LoggingInInterceptor())
        this.inInterceptors.add(WSS4JInInterceptor(wss4jProps))
        this.publish("/ws")
    }
}
Enter fullscreen mode Exit fullscreen mode

And the client for use in other modules or for test purposes:

@Bean("smpsProxy")
fun smpsProxy(): SmpsPort =
    JaxWsProxyFactoryBean().apply {
        this.serviceClass = SmpsPort::class.java
        this.address = wsAddress
        this.outInterceptors.add(LoggingOutInterceptor())
        this.outInterceptors.add(WSS4JOutInterceptor(wss4jProps))
        this.inInterceptors.add(LoggingInInterceptor())
    }.create() as SmpsPort
Enter fullscreen mode Exit fullscreen mode

Reference

Web Service Security Specification
Apache CXF WSS
Using Apache WSS4j
More advanced examples of WSS4j

Thanks for reading

If you see anything is missing in this guide, please let me know in the comments. And if you found it useful then give a heart, I'll be grateful :)

Top comments (0)