SOAP security using WSS4J in Apache CXF in Java/Kotlin
Tags: java, kotlin, security
Published: 2020-12-16 11:23:45

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](https://www.oasis-open.org/committees/download.php/13392/wss-v1.1-spec-pr-UsernameTokenProfile-01.htm) - Web Service Security - Official specification of securing SOAP Web Services
* [Apache CXF](https://cxf.apache.org/) - implementation of SOAP protocol in Java. Used both for server side and client
* [Apache WSS4J](https://ws.apache.org/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:
```kotlin
// 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")
    }
}
```

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) :
```xml
<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>
```

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 :
```kotlin
val wss4jProps = mapOf(
    WSHandlerConstants.ACTION to WSHandlerConstants.USERNAME_TOKEN,
    WSHandlerConstants.PASSWORD_TYPE to WSConstants.PASSWORD_DIGEST,
    WSHandlerConstants.PW_CALLBACK_REF to PasswordCallback(username, password)
)
```

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:
```kotlin
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
        }
    }
}
```

Now, finally, we can add our new interceptor to the endpoint configuration:
```kotlin
// in java you will access inInterceptors through the getter
this.inInterceptors.add(WSS4JInInterceptor(wss4jProps))
```

## 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:
```kotlin
JaxWsProxyFactoryBean().apply {
    this.serviceClass = SomePort::class.java
    this.address = wsAddress
    this.outInterceptors.add(LoggingOutInterceptor())
    this.inInterceptors.add(LoggingInInterceptor())
}.create() as SmpsPort
```

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:
```kotlin
private val wss4jProps = mapOf(
    WSHandlerConstants.ACTION to WSHandlerConstants.USERNAME_TOKEN,
    WSHandlerConstants.USER to username,
    WSHandlerConstants.PW_CALLBACK_REF to PasswordCallback(username, password)
)
```

Then, we can configure the interceptor in `JaxWsProxyFactoryBean`:
```kotlin
this.outInterceptors.add(WSS4JOutInterceptor(wss4jProps))
```

### 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:
```kotlin
jaxWsProxyFactoryBean.username = username
jaxWsProxyFactoryBean.password = password
```

## Summary
We have seen how to configure CXF's UsernameToken security.
We ended up with the endpoint configured:
```kotlin
// 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")
    }
}
```

And the client for use in other modules or for test purposes:
```kotlin
@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
```



#### *Reference*
[Web Service Security Specification](https://www.oasis-open.org/committees/download.php/13392/wss-v1.1-spec-pr-UsernameTokenProfile-01.htm)
[Apache CXF WSS](https://cxf.apache.org/docs/ws-security.html)
[Using Apache WSS4j](https://ws.apache.org/wss4j/using.html)
[More advanced examples of WSS4j](https://ws.apache.org/wss4j/package.html)

#### 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 :)