Pages

Friday, June 5, 2009

Passing WS-Security credentials in Oracle ESB 10.1.3

In Soa Suite 10.1.3 you can use OWSM to add WS-Security credentials to your outgoing SOAP calls. If you don't have OWSM installed or don't want to use it, then you can also do this in the XSLT of a ESB Routing Service. We can use the SOAP header XSLT function to achieve this. See this blog of Dharmendra Dubey for a description. This is only works in Soa Suite 10.1.3.3, when you have 10.1.3.4 installed then you need to an extra /Header element to xpath expression like this
<xsl:variable name="setUsername"
select="ehdr:setOutboundHeader('/Header/wsse:Security/wsse:UsernameToken/wsse:Username',$userName,'wsse=http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd;')"/>
My solution is a bit different, I made a custom XSLT function which can add WS-Security credentials and add a Timestamp to the SOAP Header. You only have to provide the username, password , digest ( or plaintext ) and the time ( in ms ) this SOAP message is valid.
Just add this function to the XSLT of the Routing Service <xsl:variable name="securityHeader" select="customESBFunctions:setUsernameToken('weblogic','weblogic',false,100)"/>
This is how my XSLT looks like.

<xsl:stylesheet version="1.0"
xmlns:bpws="http://schemas.xmlsoap.org/ws/2003/03/business-process/"
xmlns:customESBFunctions="http://www.oracle.com/XSL/Transform/java/nl.whitehorses.esb.xslt.functions.headers.ESBCustomFunctions"
xmlns:tns="http://saml.ws.whitehorses.nl/"
xmlns:s0="http://schemas.xmlsoap.org/wsdl/"
xmlns:ehdr="http://www.oracle.com/XSL/Transform/java/oracle.tip.esb.server.headers.ESBHeaderFunctions"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:hwf="http://xmlns.oracle.com/bpel/workflow/xpath"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
xmlns:xref="http://www.oracle.com/XSL/Transform/java/oracle.tip.xref.xpath.XRefXPathFunctions"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ora="http://schemas.oracle.com/xpath/extension"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:ids="http://xmlns.oracle.com/bpel/services/IdentityService/xpath"
xmlns:orcl="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.ExtFunc"
xmlns:wsse1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
exclude-result-prefixes="xsl tns s0 xsd soap wsp bpws customESBFunctions ehdr hwf xp20 xref ora ids orcl">

<xsl:variable name="securityHeader" select="customESBFunctions:setUsernameToken('weblogic','weblogic',false,100)"/>

<xsl:template match="/">
<tns:sayHello>
<xsl:value-of select="/tns:sayHello"/>
</tns:sayHello>
</xsl:template>
</xsl:stylesheet>

And with this as result.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
soapenv:mustUnderstand="1">
<wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="wsTime">
<wsu:Created>2009-06-04T20:21:19Z</wsu:Created>
<wsu:Expires>2009-06-04T20:21:19Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken>
<wsse:Username>weblogic</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">65szVNxFtj2EVicUi3ePTD1UgxE=</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soapenv:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="body">
<saml:sayHello xmlns:saml="http://saml.ws.whitehorses.nl/"></saml:sayHello>
</soapenv:Body>
</soapenv:Envelope>

This is my java source for the xslt function.

package nl.whitehorses.esb.xslt.functions.headers;


import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;

import java.security.MessageDigest;

import java.text.SimpleDateFormat;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import oracle.tip.esb.server.headers.ESBHeaderContext;

import org.w3c.dom.Element;
import org.w3c.dom.Document;
import oracle.xml.parser.v2.XMLDocument;
import org.xml.sax.SAXException;


public class ESBCustomFunctions {

public static String getHeader() throws IOException {
Element requestHeader = ESBHeaderContext.getRequestHeader();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
((XMLDocument)requestHeader.getOwnerDocument()).print(pw);
return sw.toString();
}

public static String setUsernameToken(String user,String password,Boolean digest, Double valid)
throws IOException, SAXException, ParserConfigurationException {
long currentTime = System.currentTimeMillis();
SimpleDateFormat calendarFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
TimeZone utc = TimeZone.getTimeZone("UTC");
Calendar calendar = new GregorianCalendar(utc);
calendarFormatter.setTimeZone(utc);

calendar.setTimeInMillis(currentTime);
String createTime = calendarFormatter.format(calendar.getTime());
if ( valid != null ) calendar.setTimeInMillis(currentTime + valid.longValue());
String expiresTime = calendarFormatter.format(calendar.getTime());

String securityHeader =
"<Header>"+
"<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"" +
" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" soapenv:mustUnderstand=\"1\">" +
" <wsu:Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"" +
" wsu:Id=\"wsTime\">" +
" <wsu:Created>"+createTime+"</wsu:Created>" +
" <wsu:Expires>"+expiresTime+"</wsu:Expires>" +
" </wsu:Timestamp>" +
" <wsse:UsernameToken>" +
" <wsse:Username>"+user+"</wsse:Username>" ;
if ( digest ) {
securityHeader = securityHeader+ " <wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">"+ passwordDigest( password)+"</wsse:Password>";
} else {
securityHeader = securityHeader+ " <wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">"+password+"</wsse:Password>" ;
}
securityHeader = securityHeader +
" </wsse:UsernameToken>" +
"</wsse:Security>"+
"</Header>";


Element outboundHeader = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().parse(new ByteArrayInputStream(securityHeader.getBytes()));
outboundHeader = (Element)doc.getFirstChild();
ESBHeaderContext.setOutboundHeader(outboundHeader);
return securityHeader;
}


private static String passwordDigest(String password) {
String utf8String = (new StringBuilder()).append(password).toString();
byte utf8Bytes[] = null;
try {
utf8Bytes = utf8String.getBytes("utf-8");
}
catch(UnsupportedEncodingException uee) {
uee.printStackTrace();
}
byte bytesToHash[] = utf8Bytes;
byte hash[] =null;
try {
MessageDigest sha = MessageDigest.getInstance("SHA-1");
hash = sha.digest(bytesToHash);
}
catch(Exception e) {
e.printStackTrace();
}
return Base64.encode(hash);
}

public static void main(String[] args) throws Exception {
System.out.println( setUsernameToken("weblogic","weblogic",true, null));
}

}

And custom extension definitions for JDeveloper with the new XSLT function.

<?xml version="1.0" encoding="UTF-8"?>
<extension-functions>
<functions xmlns:customESBFunctions="http://www.oracle.com/XSL/Transform/java/nl.whitehorses.esb.xslt.functions.headers.ESBCustomFunctions">

<function name="customESBFunctions:getHeader" as="string">
</function>

<function name="customESBFunctions:setUsernameToken" as="string">
<param name="user" as="string"/>
<param name="password" as="string"/>
<param name="digest" as="boolean"/>
<param name="valid" as="number"/>
</function>

</functions>
</extension-functions>

See my previous blogpost for the deployment details

No comments:

Post a Comment