Guy Grin
Guy Grin

Reputation: 2032

Authentication Spring SOAP to Workday

I'm very new to handling SOAP requests and I'm trying to consume the Workday's SOAP api listed here. I've used an gradle/ant script for generating the classes from the WSDL based on the Spring tutorial here

Now that, the classes have been generated. I can access the functions I need. The problem is that I don't know how to authenticate my requests.

This is what I have so far:

import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import workday_Staffing.wsdl.GetWorkersRequestType;
import workday_Staffing.wsdl.GetWorkersResponseType;

public class StaffingClient extends WebServiceGatewaySupport {

    public StaffingClient() {
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setContextPath("workday_Staffing.wsdl");
        setMarshaller(jaxb2Marshaller);
        setUnmarshaller(jaxb2Marshaller);
    }

    public void makeWorkdayRequest() {

        // make the request - missing some authentication here
        GetWorkersRequestType request = new GetWorkersRequestType();
        GetWorkersResponseType workersResponseType = (GetWorkersResponseType) getWebServiceTemplate()
            .marshalSendAndReceive(request);
    }
}

The answer here seems like a good lead but I'm unsure how to build the client and add the authentication.

Any help will be greatly appreciated.

Upvotes: 4

Views: 3811

Answers (1)

dbh
dbh

Reputation: 1627

Mainly, it looks like you're missing the authentication pieces via a SOAPHandler for the WS-Security Credentials. FYI, this not Spring, but was working code as of v16 of the API.

Thanks for the trip down memory lane.

Steps

  1. Set the Workday user Name and tenant
  2. Set the Workday password
  3. Set the Web Service client stub (ex HumanResourcesService)
  4. Set the Web Service client Port
  5. Add the WorkdayCredentials handler to the client stub, with a reference to port, workday user@tenant, and workday password.
  6. Get the Request Context
  7. Set the Request context's workday webservice endpoint (see wdEndPoint)
  8. GetWorkersRequestType
  9. Optionally set some parameters for your request
  10. Get the response
  11. Get the response data

Routines

  • GetWorkers
  • WorkdayCredentials

GetWorkers code

package com.workday.demo;

import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Map;
import java.math.BigDecimal;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.ws.BindingProvider;

import com.workday.human_resources.*;

public class GetWorkers {

    public static void main(String[] args) {

        try {

            // Enter user/password and endpoint information for Proof of Concept
            final String wdUser = "user@tenant";
            final String wdPassword = "zzz";

            // final String wdEndpoint =
            // "https://e2-impl-cci.workday.com/ccx/service/exampleTenant/Human_Resources/v16";
            final String wdEndpoint = "https://impl-cc.workday.com/ccx/service/exampleTenant/Human_Resources/v16";

            System.out.println("Starting...");

            // Create the Web Service client stub
            HumanResourcesService service = new HumanResourcesService();
            HumanResourcesPort port = service.getHumanResources();

            // Add the WorkdayCredentials handler to the client stub
            WorkdayCredentials.addWorkdayCredentials((BindingProvider) port, wdUser, wdPassword);

            // Assign the Endpoint URL
            Map<String, Object> requestContext = ((BindingProvider) port).getRequestContext();
            requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,   wdEndpoint);

            // Define the paging defaults
            final int countSize = 200;
            int totalPages = 1;
            int currentPage = 1;

            // Set the current date/time
            GregorianCalendar cal = new GregorianCalendar();
            XMLGregorianCalendar xmlCal = DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);

            // Loop over all of the pages in the web service response
            while (totalPages >= currentPage) {
                // Create a "request" object
                GetWorkersRequestType request = new GetWorkersRequestType();

                // Set the WWS version desired
                request.setVersion("v10");

                // Set the date/time & page parameters in the request
                ResponseFilterType responseFilter = new ResponseFilterType();
                responseFilter.setAsOfEntryDateTime(xmlCal);
                responseFilter.setAsOfEffectiveDate(xmlCal);
                responseFilter.setPage(BigDecimal.valueOf(currentPage));
                responseFilter.setCount(BigDecimal.valueOf(countSize));
                request.setResponseFilter(responseFilter);

                // Set the desired response group(s) to return
                WorkerResponseGroupType responseGroup = new WorkerResponseGroupType();
                responseGroup.setIncludeReference(true);
                request.setResponseGroup(responseGroup);

                // Submit the request creating the "response" object
                GetWorkersResponseType response = port.getWorkers(request);

                // Display all Workers
                Iterator<WorkerType> i = response.getResponseData().getWorker()
                        .iterator();
                while (i.hasNext()) {
                    WorkerType worker = i.next();
                    {
                        System.out.println(worker.getWorkerReference()
                                .getDescriptor());
                    }
                }

                // Update page number
                if (totalPages == 1) {
                    totalPages = response.getResponseResults().getTotalPages()
                            .intValue();
                }
                currentPage++;
            }

        } catch (ProcessingFaultMsg e) {
            e.printStackTrace();
        } catch (ValidationFaultMsg e) {
            e.printStackTrace();
        } catch (DatatypeConfigurationException e) {
            e.printStackTrace();
        }
    }
}

WorkdayCredentials code

package com.workday.demo;

import java.util.List;

import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.namespace.QName;
import java.util.Set;

/**
 * This class creates a handler that will add the WS-Security username and
 * password to the to the SOAP request messages for a client side proxy.
 * 
 */
public class WorkdayCredentials implements SOAPHandler<SOAPMessageContext> {

    /** Namespace for the SOAP Envelope. */
    private static String SOAPENVNamespace = "http://schemas.xmlsoap.org/soap/envelope/";

    /** The prefix that will be used for the SOAP Envelope namespace. */
    private static String SOAPENVPrefix = "soapenv";

    /** Namespace for the WS-Security SOAP header elements. */
    private static String WSSENamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";

    /** The prefix that will be used for the WS-Security namespace. */
    private static String WSSEPrefix = "wsse";

    /**
     * The WS-Security URI that specifies that the password will be transmitted
     * as plain text.
     */
    private static String WSSEPasswordText = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";

    /**
     * The user name that will be sent in the WS-Security header on the SOAP
     * request message. This is of the form systemid@tenant.
     */
    private String username;

    /**
     * The password that will be sent in the WS-Security header on the SOAP
     * request message.
     */
    private String password;

    /**
     * This method created an instance of the WorkdayCredentials class and adds
     * it as a handler to the bindingProvider supplied.
     * 
     * @param bindingProvider
     *            The client stub to which the handler will be added. The most
     *            convenient way to obtain the required bindingProvvider is to
     *            call one of the getPort methods on the Service class for the
     *            Web service and then cast the returned object to a
     *            BindingProvider.
     * @param username
     *            The id and tenant name for the user. This is of the form
     *            systemid@tenant.
     * @param password
     *            The password for the system user.
     */
    @SuppressWarnings("unchecked")
    public static void addWorkdayCredentials(BindingProvider bindingProvider,
            String username, String password) {
        List<Handler> handlerChain = bindingProvider.getBinding().getHandlerChain();
        handlerChain.add(new WorkdayCredentials(username, password));
        bindingProvider.getBinding().setHandlerChain(handlerChain);
    }

    /**
     * Creates a WorkdayCredentials handler and initialises the member
     * variables. In most cases, the addWorkdayCredentials static method should
     * be used instead.
     * 
     * @param username
     *            The id and tenant name for the user. This is of the form
     *            systemid@tenant.
     * @param password
     *            The password for the system user.
     */
    public WorkdayCredentials(String username, String password) {
        this.username = username;
        this.password = password;
    }

    /**
     * Returns null as this handler doesn't process any Headers, it just adds
     * one.
     */
    public Set<QName> getHeaders() {
        return null;
    }

    /**
     * Adds WS-Security header to request messages.
     */
    public boolean handleMessage(SOAPMessageContext smc) {
        Boolean outboundProperty = (Boolean) smc
                .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (outboundProperty.booleanValue()) {
            addWSSecurityHeader(smc, username, password);
        }
        return true;
    }

    /**
     * Returns true, no action is taken for faults messages.
     */
    public boolean handleFault(SOAPMessageContext smc) {
        return true;
    }

    public void close(MessageContext messageContext) {
    }

    /**
     * Adds a WS-Security header containing a UsernameToken to a SOAP message.
     * 
     * @param smc
     *            The SOAPMessageContent to which the WS-Security header will be
     *            added.
     * @param username
     *            The WS-Security username.
     * @param password
     *            The WS-Security password.
     * 
     * @throws java.lang.RuntimeException
     *             This exception will be thrown if a SOAPException occurs when
     *             modifying the message.
     */
    private void addWSSecurityHeader(SOAPMessageContext smc, String username,
            String password) throws java.lang.RuntimeException {

        try {
            // Get the SOAP Header
            SOAPMessage message = smc.getMessage();
            SOAPHeader header = message.getSOAPHeader();
            if (header == null) {
                // Create header as it doesn't already exist
                message.getSOAPPart().getEnvelope().addHeader();
                header = message.getSOAPHeader();
            }

            // Add WS-Security SOAP Header
            SOAPElement heSecurity = header.addChildElement("Security",
                    WSSEPrefix, WSSENamespace);
            heSecurity.addAttribute(message.getSOAPPart().getEnvelope()
                    .createName("mustUnderstand", SOAPENVPrefix,
                            SOAPENVNamespace), "1");

            // Add the Usernametoken element to the WS-Security Header
            SOAPElement heUsernameToken = heSecurity.addChildElement(
                    "UsernameToken", WSSEPrefix, WSSENamespace);

            // Add the Username element to the UsernameToken Element
            heUsernameToken.addChildElement("Username", WSSEPrefix,
                    WSSENamespace).addTextNode(username);

            // Add the Password element to the UsernameToken Element
            SOAPElement hePassword = heUsernameToken.addChildElement(
                    "Password", WSSEPrefix, WSSENamespace);
            hePassword.addAttribute(message.getSOAPPart().getEnvelope()
                    .createName("Type"), WSSEPasswordText);
            hePassword.addTextNode(password);

        } catch (SOAPException e) {
            throw new RuntimeException(
                    "Failed to add WS-Security header to request", e);
        }
    }
}

Upvotes: 3

Related Questions