In the following code sample, a SOAP message is parsed to extract the username and password credentials, these values are applied to the MessageContext class for the invocation (and in the case of WebSphere, a login is performed), and control is returned to the superclass. Also a locale is specified for the call by setting property locale in the MessageContext object. The code sample is followed by an example of a SOAP message which would be processed by the code.
package webservice;
import curam.util.connectors.webservice.CuramEJBMethodProvider;
import curam.util.resources.Configuration;
import curam.util.resources.EnvironmentConstants;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.PrivilegedAction;
import java.util.Iterator;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import org.apache.axis.AxisFault;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.providers.java.EJBProvider;
/**
* A web services hook which extends the Axis EJB provider to
* enable the developer to access the SOAP message. In this case
* it takes the username and password from the SOAP header and
* sets them in the method call.
*
*/
public class TestmodelProvider extends CuramEJBMethodProvider {
/** The name of an XML attribute in a multi ref element. */
private static final String kNameOfIdAttribute = "id";
/** The name of an XML element in the SOAP body. */
private static final String kMultiRefElementName = "multiRef";
/**
* The name of the attribute containing a `href` to another
* element.
*/
private static final String kHrefAttributeName = "href";
/**
* The name of the header element as defined in the WSDL file.
*/
private static final String kNameOfHeaderElement = "inHeader";
/** The name of the element containing the user name field. */
private static final String kUsernameFieldName = "userName";
/** The name of the element containing the password field. */
private static final String kPasswordFieldName = "password";
/** Cached Do As Method instance. */
private static Method stSubjectDoAsMethod;
/**
* Hook which gets the credentials from the header of the soap
* message and sets them in the message context for the call
* before delegating back to the superclass method.
*
* @param msgContext The message context for the call.
*
* @throws AxisFault Generic Axis exception.
*/
public void invoke(final MessageContext msgContext)
throws AxisFault {
final Message requestMessage = msgContext.getRequestMessage();
final SOAPEnvelope envelope =
requestMessage.getSOAPEnvelope();
final SOAPElement element =
getSoapElement(envelope, kNameOfHeaderElement);
String userName = null;
String password = null;
try {
// Get parameters from the SOAP header and set them in the
// message context for the call.
userName = getSubElementValue(element, envelope.createName(
kUsernameFieldName));
password = getSubElementValue(element, envelope.createName(
kPasswordFieldName));
// Check the soundness of our SOAP header processing before
// we attempt to use the data for real. Otherwise bad or
// missing data in these variables will simply manifest
// itself misleadingly as a security configuration problem.
if ((userName == null) || (userName.length() == 0)
|| (password == null) || (password.length() == 0)) {
final AxisFault e = new AxisFault(
"Bad username/password in SOAP" + " header '" + userName
+ "'/'" + password + "'");
throw e;
}
msgContext.setUsername(userName);
msgContext.setPassword(password);
// Specify an absolute locale for the invocation:
final String localeFrenchCanada = "fr_CA";
msgContext.setProperty("locale", localeFrenchCanada);
} catch (SOAPException e) {
throw new AxisFault(e.getMessage(), e);
}
if (isRunningInWebSphere()) {
// A WebSphere limitation means that it will not
// automatically see the credentials we have just set, we
// must also perform our login here.
try {
final Method doAsMethod = getDoAsMethod();
final LoginContext loginContext =
getLoginContext(userName, password);
loginContext.login();
final Subject subject = loginContext.getSubject();
// Create a privileged action class which includes all the
// information about this call.
final PrivilegedAction action =
new ProviderPrivilegedAction(this, msgContext);
final Object[] parameterValues = {subject, action};
// invoke the rest of the call under the new credentials.
final Object axisFault =
doAsMethod.invoke(null, parameterValues);
// Exceptions cannot be thrown from the above invocation,
// they are returned instead. If one was returned then
// throw it now.
if (axisFault != null) {
throw new AxisFault("" + axisFault,
(Exception) axisFault);
}
} catch (Exception e) {
throw new AxisFault(e.getMessage(), e);
}
} else {
// Not in WebSphere. Simply delegate straight through.
super.invoke(msgContext);
}
}
/**
* An accessor for the invoke method in the superclass. Required
* because it must be invoked by another class - the inner class
* within this one.
*
* @param msgContext The message context object for this call.
*
* @throws AxisFault Generic Axis fault handler.
*/
private void superInvoke(final MessageContext msgContext)
throws AxisFault {
super.invoke(msgContext);
}
/**
* Indicates whether we are running within WebSphere in which
* case we must delegate the rest of the call as a privileged
* action.
*
* @return True if running under WebSphere, false otherwise.
*/
private boolean isRunningInWebSphere() {
final String vendorName = System.getProperty("java.vendor");
return vendorName.startsWith("IBM");
}
/**
* Gets a named element from the SOAP message, searching both
* the body and header.
*
* @param envelope The SOAP envelope.
* @param elementNameString The name of the element to get.
*
* @return The required element or null if it was not found.
*/
private SOAPElement getSoapElement(final SOAPEnvelope envelope,
final String elementNameString) {
SOAPElement result = null;
try {
final Name elementName =
envelope.createName(elementNameString);
final Name hrefAttributeName =
envelope.createName(kHrefAttributeName);
final SOAPHeader sh = envelope.getHeader();
final SOAPBody sb = envelope.getBody();
// first search the header.
SOAPElement candidateElement = null;
final Iterator headerIterator =
sh.getChildElements(elementName);
if (headerIterator.hasNext()) {
candidateElement = (SOAPElement) headerIterator.next();
}
// search the body, if necessary.
if (candidateElement == null) {
final Iterator bodyIterator =
sb.getChildElements();
if (bodyIterator.hasNext()) {
candidateElement = (SOAPElement) bodyIterator.next();
}
}
// Now we need to check if this is literal or encoded
// element. A literal one is embedded directly, an encoded
// one means that this element is simply a pointer to an
// element elsewhere in the message.
if (candidateElement != null) {
final String hrefValue =
candidateElement.getAttributeValue(hrefAttributeName);
if ((hrefValue != null) && (hrefValue.length() > 0)) {
// it points to a multi ref, so get this instead.
result = getMultiRefElement(envelope, hrefValue);
} else {
// It's literal so return it directly.
result = candidateElement;
}
}
} catch (SOAPException e) {
e.printStackTrace();
}
return result;
}
/**
* Gets a multi ref element from a SOAP message.
*
* @param envelope The SOAP envelope.
* @param idStringWithPrefix The identifier of the multi ref
* element.
*
* @return The matching element, or null if it was not found.
*
* @throws SOAPException If any SOAP error occurs.
*/
private SOAPElement getMultiRefElement(
final SOAPEnvelope envelope, final String idStringWithPrefix)
throws SOAPException {
SOAPElement result = null;
// Remove the hash character:
final String idString = idStringWithPrefix.substring(1);
final Name idName = envelope.createName(kNameOfIdAttribute);
final SOAPBody body = envelope.getBody();
final Iterator multiRefIterator = body.getChildElements(
envelope.createName(kMultiRefElementName));
while (multiRefIterator.hasNext()) {
final Object o = multiRefIterator.next();
final SOAPElement currentElement = (SOAPElement) o;
final String currentId =
currentElement.getAttributeValue(idName);
if (currentId.equals(idString)) {
result = currentElement;
break;
}
}
return result;
}
/**
* Gets the value of a specified element within the given
* element. If multiple occurrences are present, the first one
* is returned.
*
* @param element The element containing the required one.
* @param elementName The name of the required element.
*
* @return The string value of the element, or null if the
* specified sub element does not exist.
*/
private String getSubElementValue(final SOAPElement element,
final Name elementName) {
String result = null;
final Iterator elementIterator =
element.getChildElements(elementName);
if (elementIterator.hasNext()) {
final SOAPElement subElement =
(SOAPElement) elementIterator.next();
result = subElement.getValue();
}
return result;
}
/**
* Gets the hidden implementation class for the Login Context.
*
* @param userName The user name to login with.
* @param password The password to login with.
*
* @return class for implementation
*
* @throws Exception if an error occurs getting an instance of
* the LoginContext class
*/
private LoginContext getLoginContext(
final String userName, final String password)
throws Exception {
final LoginContext resultLoginContext;
// Initialize WebSphere specific callback handler. Use
// reflection to avoid a build time dependency on an IBM
// class.
final Class wsCallbackHandlerClass = Class.forName(
EnvironmentConstants.kWSCallbackHandlerImplClassName);
final Class[] parameters = { String.class, String.class };
final Constructor constructor =
wsCallbackHandlerClass.getConstructor(parameters);
final Object[] parameterValues = {userName, password};
// The WebSphere login
resultLoginContext =
new LoginContext(
EnvironmentConstants.kWSLogin,
(CallbackHandler) constructor.newInstance(
parameterValues));
return resultLoginContext;
}
/**
* Gets the cached Do As method, initializing it if necessary.
*
* @return The Do As method for this server.
*
* @throws Exception If the method could not be obtained for
* any reason.
*/
private Method getDoAsMethod() throws Exception {
if (stSubjectDoAsMethod != null) {
return stSubjectDoAsMethod;
}
final Class wsSubjectClass =
Class.forName(EnvironmentConstants.kWSSubjectClassName);
final Class[] moreParameters =
{ Subject.class, PrivilegedAction.class };
stSubjectDoAsMethod =
wsSubjectClass.getDeclaredMethod(
EnvironmentConstants.kDoAsMethodName,
moreParameters);
return stSubjectDoAsMethod;
}
/**
*
*
*/
private class ProviderPrivilegedAction
implements PrivilegedAction {
/** The message context for the call. */
private final MessageContext msgContext;
/** The class whose method we must invoke. */
private final TestmodelProvider ownerObject;
/**
* Constructor which initializes the fields.
*
* @param newOwnerObject The class whose method we will
* invoke.
* @param newMsgContext The message context for the call.
*/
public ProviderPrivilegedAction(
final TestmodelProvider newOwnerObject,
final MessageContext newMsgContext) {
ownerObject = newOwnerObject;
msgContext = newMsgContext;
}
/**
* Runs the privileged action using the fields of this class.
*
* @return The exception resulting from the call, or null if
* none was thrown.
*/
public Object run() {
Object resultFault = null;
try {
ownerObject.superInvoke(msgContext);
} catch (Exception e) {
resultFault = e;
}
return resultFault;
}
}
}
The text below shows an actual SOAP message (with some formatting for readability) which is processed by the Java code above. Note that the SOAP header refers to a parameter named ` inCred ` which contains the username and password credentials. The actual data is not stored literally in the header but in a ` multiRef ` element in the message body.
<soapenv:Envelope xmlns:soapenv=
"http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<inCred href="#id0" xmlns=""/>
</soapenv:Header>
<soapenv:Body soapenc:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/">
<opDemo xmlns="http://remote.feature">
<in1 href="#id1" xmlns=""/>
</opDemo>
<multiRef id="id1" soapenc:root="0" soapenv:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="ns-905576305:PersonDetailsWrapper"
xmlns:ns-905576305="http://feature/struct/" xmlns="">
<firstName xsi:type="xsd:string">Jimmy</firstName>
<idNumber xsi:type="xsd:string">0000361i</idNumber>
<surname xsi:type="xsd:string">Client</surname>
</multiRef>
<multiRef id="id0" soapenc:root="0" soapenv:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="ns-905576305:CredentialsWrapper"
xmlns:ns-905576305="http://feature/struct/" xmlns="">
<password xsi:type="xsd:string">password</password>
<userName xsi:type="xsd:string">superuser</userName>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>