Search code examples
javasoapclientaxisws-security

WSSecurityEngine says password was not supplied during callback even though it is provided


I'm in the process of creating a client for a web service. I keep getting the following error:

AxisFault
    faultCode: {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}InvalidSecurity
    faultSubcode:
    faultString:
        Security Data : General security error (WSSecurityEngine: Callback supplied no password for: TestMerchant)
    faultActor: 
    faultNode: 
    faultDetail: {http://xml.apache.org/axis/}
    stackTrace:
        Security Data : General security error (WSSecurityEngine: Callback supplied no password for: TestMerchant)

This is my environment here:

  • Intellij IDEA IDE
  • Apache Axis
  • Apache WSS4J 1.5.1
  • Apache XML Security 1.4.0
  • JDK 1.6
  • Max OS X

Although searching on the internet provides a lot of examples of how the security header can be added to the request via XML configuration, my requirement is to do this dynamically via the program. So here is my code:

public class AxisClient implements CallbackHandler {

    ServerEnvironment environment;

    AxisClient(ServerEnvironment environment) {
        this.environment = environment;
    }

    public enum ServerEnvironment {
        LIVE("https://ics2ws.ic3.com/commerce/1.x/transactionProcessor"),
        TEST("https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor");

        String url;

        ServerEnvironment (String url) {
            this.url = url;
        }

        public String getUrl() {
            return url;
        }
    }

    public enum Merchant {
        TestMerchant ("testpassword");

        private String transactionKey;

        Merchant(String transactionKey) {
            this.transactionKey = transactionKey;
        }

        public String getTransactionKey() {
            return transactionKey;
        }
    }

    public static void main(String[] argv) {
        String ani = "7162502800";
        String zipCode = "14221";
        String ccNum ="5555555555554444";
        String expMonth = "01";
        String expYear = "15";
        String cvv = "123";
        String unitPrice = "9.99";
        String qty = "2";

        try {
            new AxisClient(ServerEnvironment.TEST).doAuth(Merchant.TestMerchant, ani, zipCode, ccNum, expMonth, expYear, cvv, String.valueOf(new Date().getTime()), unitPrice, qty);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Boolean doAuth(Merchant merchant, String ani, String zipCode, String ccNum, String expMonth, String expYear, String cvv, String id, String unitPrice, String qty) throws Exception {
        Boolean result = false;

        RequestMessage request;
        BillTo billTo;
        Card card;
        PurchaseTotals purchaseTotals;
        Item item;
        Item[] items;
        ReplyMessage reply;

        try {
            // billing info
            billTo = new BillTo();
            billTo.setPhoneNumber(ani);
            billTo.setPostalCode(zipCode);

            // card info
            card = new Card();
            card.setAccountNumber(ccNum);
            card.setExpirationMonth(new BigInteger(expMonth));
            card.setExpirationYear(new BigInteger(expYear));
            card.setCvNumber(cvv);

            // currency info
            purchaseTotals = new PurchaseTotals();
            purchaseTotals.setCurrency("USD");

            // item
            item = new Item();
            item.setId(new BigInteger(id));
            item.setUnitPrice(unitPrice);
            item.setQuantity(new BigInteger(qty));

            // add item to items array
            items = new Item[1];
            items[0] = item;

            // create our request
            request = new RequestMessage();
            request.setMerchantID(merchant.toString());
            request.setCcAuthService(new CCAuthService());
            request.getCcAuthService().setRun("true");

            // add request specific params
            request.setBillTo(billTo);
            request.setCard(card);
            request.setPurchaseTotals(purchaseTotals);
            request.setItem(items);

            reply = post(merchant, request);

            if (reply != null) {
                System.out.println(ReflectionToStringBuilder.toString(reply, ToStringStyle.MULTI_LINE_STYLE));
            }
        }
        catch (Exception e) {
            throw e;
        }

        return result;
    }

    public EngineConfiguration createConfigurationWithSecurityHeaders(Merchant merchant) throws Exception {
        SimpleProvider result;

        Handler securityHandler;
        SimpleChain requestHandler;
        SimpleChain responseHandler;
        Handler pivot;
        Handler transport;

        try {
            result = new SimpleProvider();

            securityHandler = new WSDoAllSender();
            securityHandler.setOption(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
            securityHandler.setOption(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PASSWORD_TEXT);
            securityHandler.setOption(WSHandlerConstants.PW_CALLBACK_REF, this);
            securityHandler.setOption(WSHandlerConstants.USER, merchant.toString());
            securityHandler.setOption(WSHandlerConstants.MUST_UNDERSTAND, "false");

            requestHandler = new SimpleChain();
            requestHandler.addHandler(securityHandler);

            responseHandler = new SimpleChain();
            responseHandler.addHandler(securityHandler);

            pivot = new HTTPSender();

            transport = new SimpleTargetedChain(requestHandler, pivot, responseHandler);

            result.deployTransport(HTTPTransport.DEFAULT_TRANSPORT_NAME, transport);
        }
        catch (Exception e) {
            throw e;
        }

        return result;
    }

    public ReplyMessage post (Merchant merchant, RequestMessage request) throws Exception {
        ReplyMessage result;

        TransactionProcessorLocator locator;
        URL endPoint;
        ITransactionProcessorStub stub;
        EngineConfiguration configuration;

        try {
            locator = new TransactionProcessorLocator();

            // use client config
            configuration = createConfigurationWithSecurityHeaders(merchant);
            locator.setEngineConfiguration(configuration);
            locator.setEngine(new org.apache.axis.client.AxisClient(configuration));

            endPoint = new URL(environment.getUrl());

            stub = (ITransactionProcessorStub) locator.getportXML(endPoint);
            stub._setProperty(WSHandlerConstants.USER, request.getMerchantID());
            stub._setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
            stub._setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PASSWORD_TEXT);
            stub._setProperty(WSHandlerConstants.PW_CALLBACK_REF, this);
            stub._setProperty(WSHandlerConstants.MUST_UNDERSTAND, "false");

            result = stub.runTransaction(request);
        }
        catch (Exception e) {
            throw e;
        }

        return result;
    }

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

        for (Callback callback : callbacks) {

            System.out.println(ReflectionToStringBuilder.toString(callback, ToStringStyle.MULTI_LINE_STYLE));

            if (callback instanceof WSPasswordCallback) {

                WSPasswordCallback passwordCallback = (WSPasswordCallback) callback;

                switch (Merchant.valueOf(passwordCallback.getIdentifer())) {

                    case TestMerchant:
                        passwordCallback.setPassword(Merchant.TestMerchant.getTransactionKey());
                        System.out.println(ReflectionToStringBuilder.toString(passwordCallback, ToStringStyle.MULTI_LINE_STYLE));
                        break;

                    default:
                        throw new UnsupportedCallbackException(callback, "Unrecognized prompt!");
                }
            }
            else {
                throw new UnsupportedCallbackException(callback, "Unrecognized callback!");
            }
        }
    }
}

As you can see from the above, my class implements CallbackHandler and I'm overriding handle() which provides the password for WSPasswordCallback.

Here is the output of the print statement for when I get the callback first:

org.apache.ws.security.WSPasswordCallback@50c713d2[
  identifier=TestMerchant
  password=<null>
  key=<null>
  usage=2
  passwordType=<null>
]

Here is the output for after setting the password:

org.apache.ws.security.WSPasswordCallback@50c713d2[
  identifier=TestMerchant
  password=testpassword
  key=<null>
  usage=2
  passwordType=<null>
]

So I'm not really sure why I keep getting that error message. Any help in solving this issue will be greatly appreciated.

Also recommendations for a different approach (axis2, cxf) are welcome.

Here is my full stack trace if it is of any help:

AxisFault
 faultCode: {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}InvalidSecurity
 faultSubcode: 
 faultString: 
Security Data : General security error (WSSecurityEngine: Callback supplied no password for: TestMerchant)
General security error (WSSecurityEngine: Callback supplied no password for: TestMerchant)

 faultActor: 
 faultNode: 
 faultDetail: 
    {http://xml.apache.org/axis/}stackTrace:
Security Data : General security error (WSSecurityEngine: Callback supplied no password for: TestMerchant)
General security error (WSSecurityEngine: Callback supplied no password for: TestMerchant)

    at org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:222)
    at org.apache.axis.message.SOAPFaultBuilder.endElement(SOAPFaultBuilder.java:129)
    at org.apache.axis.encoding.DeserializationContext.endElement(DeserializationContext.java:1087)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:601)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2939)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:648)
    at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:808)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
    at javax.xml.parsers.SAXParser.parse(SAXParser.java:395)
    at org.apache.axis.encoding.DeserializationContext.parse(DeserializationContext.java:227)
    at org.apache.axis.SOAPPart.getAsSOAPEnvelope(SOAPPart.java:696)
    at org.apache.axis.Message.getSOAPEnvelope(Message.java:435)
    at org.apache.axis.handlers.soap.MustUnderstandChecker.invoke(MustUnderstandChecker.java:62)
    at org.apache.axis.client.AxisClient.invoke(AxisClient.java:206)
    at org.apache.axis.client.Call.invokeEngine(Call.java:2784)
    at org.apache.axis.client.Call.invoke(Call.java:2767)
    at org.apache.axis.client.Call.invoke(Call.java:2443)
    at org.apache.axis.client.Call.invoke(Call.java:2366)
    at org.apache.axis.client.Call.invoke(Call.java:1812)
    at itg.cybersource.axis.ITransactionProcessorStub.runTransaction(ITransactionProcessorStub.java:1284)
    at itg.AxisClient.post(AxisClient.java:208)
    at itg.AxisClient.doAuth(AxisClient.java:132)
    at itg.AxisClient.main(AxisClient.java:75)

    {http://xml.apache.org/axis/}hostname:C02GD302DRJL.local


Security Data : General security error (WSSecurityEngine: Callback supplied no password for: TestMerchant)
General security error (WSSecurityEngine: Callback supplied no password for: TestMerchant)

    at org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:222)
    at org.apache.axis.message.SOAPFaultBuilder.endElement(SOAPFaultBuilder.java:129)
    at org.apache.axis.encoding.DeserializationContext.endElement(DeserializationContext.java:1087)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:601)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2939)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:648)
    at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:808)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
    at javax.xml.parsers.SAXParser.parse(SAXParser.java:395)
    at org.apache.axis.encoding.DeserializationContext.parse(DeserializationContext.java:227)
    at org.apache.axis.SOAPPart.getAsSOAPEnvelope(SOAPPart.java:696)
    at org.apache.axis.Message.getSOAPEnvelope(Message.java:435)
    at org.apache.axis.handlers.soap.MustUnderstandChecker.invoke(MustUnderstandChecker.java:62)
    at org.apache.axis.client.AxisClient.invoke(AxisClient.java:206)
    at org.apache.axis.client.Call.invokeEngine(Call.java:2784)
    at org.apache.axis.client.Call.invoke(Call.java:2767)
    at org.apache.axis.client.Call.invoke(Call.java:2443)
    at org.apache.axis.client.Call.invoke(Call.java:2366)
    at org.apache.axis.client.Call.invoke(Call.java:1812)
    at itg.cybersource.axis.ITransactionProcessorStub.runTransaction(ITransactionProcessorStub.java:1284)
    at itg.AxisClient.post(AxisClient.java:208)
    at itg.AxisClient.doAuth(AxisClient.java:132)
    at itg.AxisClient.main(AxisClient.java:75)

Solution

  • After many days of trying very many different things, I finally figured out the answer to my problem. Talk about the problem being in between the keyboard and the chair!!!!!

    So without further ado here is what the issue was:

    securityHandler.setOption(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PASSWORD_TEXT);
    

    Now WSConstants.PASSWORD_TEXT actually equates to "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText". What I must have really been using is WSConstants.PW_TEXT which equates to "PasswordText" which is exactly what I needed in my code. Once I made the change, everything works beautifully.

    Contrary to popular belief, you can do all this entirely in a programmatic manner. You DO NOT need to configure the WSDD xml for you to intercept a message and handle WS-Security in the SOAP header. To tidy up loose ends, here is what the modified methods look like:

    public EngineConfiguration createConfigurationWithSecurityHeaders(Merchant merchant) throws Exception {
        SimpleProvider result;
    
        Handler securityHandler;
        SimpleChain requestHandler;
        SimpleChain responseHandler;
        Handler pivot;
        Handler transport;
    
        try {
            result = new SimpleProvider();
    
            securityHandler = new WSDoAllSender();
            securityHandler.setOption(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
            securityHandler.setOption(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
            securityHandler.setOption(WSHandlerConstants.USER, merchant.toString());
            securityHandler.setOption(WSHandlerConstants.MUST_UNDERSTAND, "false");
    
            requestHandler = new SimpleChain();
            requestHandler.addHandler(securityHandler);
    
            responseHandler = new SimpleChain();
            responseHandler.addHandler(securityHandler);
    
            pivot = new HTTPSender();
    
            transport = new SimpleTargetedChain(requestHandler, pivot, responseHandler);
    
            result.deployTransport(HTTPTransport.DEFAULT_TRANSPORT_NAME, transport);
        }
        catch (Exception e) {
            throw e;
        }
    
        return result;
    }
    
    public ReplyMessage post (Merchant merchant, RequestMessage request) throws Exception {
        ReplyMessage result;
    
        TransactionProcessorLocator locator;
        URL endPoint;
        ITransactionProcessorStub stub;
        EngineConfiguration configuration;
    
        try {
            locator = new TransactionProcessorLocator();
    
            // use client config
            configuration = createConfigurationWithSecurityHeaders(merchant);
            locator.setEngineConfiguration(configuration);
            locator.setEngine(new org.apache.axis.client.AxisClient(configuration));
    
            endPoint = new URL(environment.getUrl());
    
            stub = (ITransactionProcessorStub) locator.getportXML(endPoint);
            stub._setProperty(WSHandlerConstants.PW_CALLBACK_REF, this);
    
            result = stub.runTransaction(request);
        }
        catch (Exception e) {
            throw e;
        }
    
        return result;
    }
    

    Once these modifications are made, your client will work. Please keep in mind that a few of the settings above are specific to the service I'm integrating with. You might have to tweak these to suit your integration which might require a little trial and error.

    Thanks again to all the people who post incredibly knowledgeable articles in SO enabling users like me solve problems we come across every now and then.