javawss4jxfire

wss4j:1.6.5 - Add BinarySecurityToken to SOAP request


We have an old application written in Java that uses and old version of wss4j to generate the SOAP requests and xFire to transmit those requests.

This project uses a few dependencies that I believe are relevant:

Frankly, I have little experience with SOAP and zero experience with this wss4j library.

From looking online, I was unable to find a lot of helpful resources for this older version of wss4j.

With all of the developers who worked on this project having left the company before I joined, I'm hoping to find some help from the vast knowledge found on SO.


A change in the SOAP service has been made by an external team for which we need to modify our client.

We need to be able to add a BinarySecurityToken to the Security section of the header as well as an arbitrary element to the Header.

From what I've read online, the BinarySecurityToken is supposed to be used to encode certificate data, so I'm not sure if we're trying to use it correctly, but frankly we don't have a choice as the change has been made to the service and we have to adapt the client to meet it's new requirements :(

Below is an example or what our application currently sends; it does not include a BinarySecurityToken in the Security element or an authMethod element in the Header element:

<soap:Header>
    <wsse:Security soap:mustUnderstand="1">
        <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-b92109ac-4a13-444d-b8e5-19c400f5910f">
            <wsse:Username>some_username</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">some_password</wsse:Password>
            <wsse:Nonce>Daaaaa1bBbbbbBb2cccccc==</wsse:Nonce>
            <wsu:Created>2022-09-19T09:10:31Z</wsu:Created>
        </wsse:UsernameToken>
    </wsse:Security>
    <wsa:Action>some_text</wsa:Action>
    <wsa:MessageID>some_text_value</wsa:MessageID>
    <wsa:ReplyTo>
        <wsa:Address>some_text_value</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To endpointID="some_text_value">http://172.31.9.216:443/P/V3_20090316L/XML/FICR_AR062004CA</wsa:To>
    <wsa:From endpointID="some_text_value">
        <wsa:Address>some_text_value</wsa:Address>
    </wsa:From>
    <language>en</language>
</soap:Header>

The expected SOAP request must contain a header that looks like the below, it must include a BinarySecurityToken in the Security element and an authMethod element in the Header element:

<soap:Header>
    <wsse:Security soap:mustUnderstand="1">
        <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-b92109ac-4a13-444d-b8e5-19c400f5910f">
            <wsse:Username>some_username</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"> </wsse:Password>
            <wsse:Nonce>Daaaaa1bBbbbbBb2cccccc==</wsse:Nonce>
            <wsu:Created>2022-09-19T09:10:31Z</wsu:Created>
        </wsse:UsernameToken>
        <wsse:BinarySecurityToken ValueType="authToken" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-c0c6c469-235a-42e5-869c-cec77b1c2dbd">some_token_which_will_be_base64_encoded</wsse:BinarySecurityToken>
    </wsse:Security>
    <wsa:Action>some_text</wsa:Action>
    <wsa:MessageID>some_text_value</wsa:MessageID>
    <wsa:ReplyTo>
        <wsa:Address>some_text_value</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To endpointID="some_text_value">http://172.31.9.216:443/P/V3_20090316L/XML/FICR_AR062004CA</wsa:To>
    <wsa:From endpointID="some_text_value">
        <wsa:Address>some_text_value</wsa:Address>
    </wsa:From>
    <language>en</language>
    <ehr:authMethod xmlns:ehr="some_text_value">authToken</ehr:authMethod>
</soap:Header>

Below an a sample of the current code:

I cleaned-up the code a little to make it legible and added full class-declarations where I thought important.

Transmitter.java

public class Transmitter
{
    
    public org.w3c.dom.Document sendRequest(
        Document reqMsg,
        String serverUrlAndLocation,
        String action,
        String msgId,
        String receiverNetAddr,
        String senderNetAddr,
        String toEndpointID,
        String fromEndpointID,
        String username,
        String authToken,
        String testCase
    )
        final EHIP_routingServiceClient prclient = new EHIP_routingServiceClient(serverUrlAndLocation);
        final EHIP_routingPortType prpt = prclient.getEHIP_routingPort0();
        final org.codehaus.xfire.client.Client client = org.codehaus.xfire.client.Client.getInstance(prpt);
        
        String interactionId = stripVersion(action);
        action = "urn:hl7-org:v3:" + action;
        
        final RxDOMInHandler rxHandler = new RxDOMInHandler(testCase);
        
        client.addInHandler   (rxHandler);      // creates the DOM for the SOAP msg;
        client.addOutHandler  (new org.codehaus.xfire.util.dom.DOMOutHandler.DOMOutHandler());
        client.addFaultHandler(new org.codehaus.xfire.util.dom.DOMOutHandler.DOMOutHandler());
        client.addInHandler   (new org.codehaus.xfire.util.LoggingHandler());
        client.addOutHandler  (new RxLoggingHandler(testCase, interactionId));
        client.addFaultHandler(new RxLoggingHandler(testCase, interactionId));
        
        this.enableUsernameTokenEClaims(
            client,
            username,
            authToken
        );
        
        this.enableWSAddressing(
            client,
            action,
            msgId,
            receiverNetAddr,
            senderNetAddr,
            toEndpointID,
            fromEndpointID
        );
        
        client.addOutHandler(new RxSoapActionOutHandler(action, false));
        client.addOutHandler(new RxAdjustBodyOutHandler(m_api.getCreationTimeFormatFlag()));
        
        final Object[] params = new Object[] {reqMsg};
        Object[] ret;
        
        try
        {
            ret = client.invoke("processRequest", params);
        }
        catch(XFireFault fault)
        {
            if (fault.getCause() instanceof WstxEOFException)
            {
                final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                final DocumentBuilder documentBuilder = factory.newDocumentBuilder();
                ret = new Document[1];
                ret[0] = documentBuilder.newDocument();
            }
            else
            {
                throw fault;
            }
        }
        
        return rxHandler.getResponse();
    }
    
    private void enableUsernameTokenEClaims(
            Client client,
            String username,
            String authToken
    )
    {
        final java.util.Map<String, Object> usernameTokenConfig = new HashMap<String, Object>();
        
        usernameTokenConfig.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        
        usernameTokenConfig.put(WSHandlerConstants.USER, username);
        usernameTokenConfig.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
        
        final RxPasswordHandler pwdHandler = new RxPasswordHandler();
        pwdHandler.setPassword(authToken, true);
        
        usernameTokenConfig.put(WSHandlerConstants.PW_CALLBACK_REF, pwdHandler);
        
        client.addOutHandler(new WSS4JOutHandler(usernameTokenConfig));
    }
    
    private void enableWSAddressing(
        org.codehaus.xfire.client.Client client,
        String action,
        String msgId,
        String receiverNetAddr,
        String senderNetAddr,
        String toEndpointID,
        String fromEndpointID
    )
    {
        final RxAddressingHeadersFactory200508 factory = new RxAddressingHeadersFactory200508(
            m_api.getLanguage(),
            toEndpointID,
            fromEndpointID
        );
        
        final AddressingHeaders ah = new AddressingHeaders();
        
        if (msgId == null || msgId.trim().equals(""))
        {
            ah.setMessageID(new RandomGUID(true).toString());
        }
        else
        {
            ah.setMessageID(msgId);
        }
        
        final EndpointReference epr1 = factory.createEClaimsDefaultEPR(senderNetAddr);
        
        ah.setFrom(epr1);
        ah.setReplyTo(factory.createDefaultEPR());
        ah.setTo(receiverNetAddr);
        
        ah.setAction(action);
        
        final java.util.Map<String, Object> properties = new HashMap<String, Object> ();
        properties.put(AddressingInHandler.ADRESSING_HEADERS.toString(), ah);
        properties.put(AddressingInHandler.ADRESSING_FACTORY.toString(), factory); 
        
        client.addOutHandler(new RxAddressingOutHandler(properties));
    }
    
}

RxDOMInHandler

public class RxDOMInHandler extends org.codehaus.xfire.util.dom.DOMInHandler
{
    public static final String DOM_MESSAGE = "dom.message";
    private String interactionId = null;
    private String testCase = null;
    private org.w3c.dom.Document response = null;
    
    public RxDOMInHandler()
    {
        super();
        setPhase(org.codehaus.xfire.handler.Phase.PARSE);
        before(org.codehaus.xfire.soap.handler.ReadHeadersHandler.class.getName());
    }
    
    public RxDOMInHandler(String testCase)
    {
        super();
        setPhase(org.codehaus.xfire.handler.Phase.PARSE);
        before(org.codehaus.xfire.soap.handler.ReadHeadersHandler.class.getName());
        this.testCase = testCase;
    }
    
    public void invoke(org.codehaus.xfire.MessageContext context)
            throws Exception
    {
        org.w3c.dom.Document doc = null;
        javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
        dbf.setValidating(false);
        dbf.setIgnoringComments(false);
        dbf.setIgnoringElementContentWhitespace(false);
        dbf.setNamespaceAware(true);
        dbf.setCoalescing(false);
        
        org.codehaus.stax2.XMLStreamReader2 reader2 = (org.codehaus.stax2.XMLStreamReader2) context.getInMessage().getXMLStreamReader();
        doc = org.codehaus.xfire.util.STAXUtils.read(dbf.newDocumentBuilder(), reader2, false);
        this.response = doc;
        
        context.getInMessage().setProperty(DOM_MESSAGE, doc);
        context.getInMessage().setXMLStreamReader(new org.codehaus.xfire.util.stax.W3CDOMStreamReader(doc.getDocumentElement()));
        this.interactionId = RxDOMUtil.getInteractionIdFromDoc(doc);
        RxDOMUtil.writeXmlToFile("xmls/SOAP_Response_" + this.interactionId + "_" + this.testCase + ".xml", doc);
    }
    
    public org.w3c.dom.Document getResponse()
    {
        return response;
    }
    
}

I tried modifying the enableUsernameTokenEClaims method to add a BinarySecurityToken, but so far I have not been able to get that to work.


I have already successfully make this same change to another client of this service, but that client was written in VB and the libraries being used there were either more flexible or less strict.


Solution

  • Based on speaking with coworkers with more experience with these libraries and SOAP in general, it seems that this version of the wss4j library just doesn't support BinarySecurityToken :(

    As such we're going to go a different route to transmit the tokens.