After a decent amount of searching, I couldn't find a complete Java example, but was able to piece together a workable solution. I'm no security expert, just an application developer trying to troubleshoot a problem. So this definitely isn't production-ready code, but if you're in the same boat trying to troubleshoot a connection, hopefully this will be of some use to you and save you some time.
The code here is a SOAPHandler that will add the appropriate headers to your Web service call. I won't go into how to configure a handler here as that is specific to whatever development tool you are using. But once you get this handler configured correctly, it should add the headers so your SOAP message looks something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> | |
<S:Header> | |
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> | |
<wsse:UsernameToken> | |
<wsse:Username> | |
myusername | |
</wsse:Username> | |
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"> | |
5VuKIINJgQwDKgpFeuGOUizeDsU= | |
</wsse:Password> | |
<wsse:Nonce> | |
1DQIP6G1UtFWC7v593CLXw== | |
</wsse:Nonce> | |
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> | |
2010-03-19T20:12:24.531Z | |
</wsu:Created> | |
</wsse:UsernameToken> | |
</wsse:Security> | |
</S:Header> | |
<S:Body> | |
Whatever. | |
</S:Body> | |
</S:Envelope> | |
The main difficulty with the implementation is the password digest. From the spec:
Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) )
In other words, you must concatenate the bytes or a nonce, a created date, and the password; then SHA-1 digest the result; then Base64 encode the digest. Here is the code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package sample; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.security.MessageDigest; | |
import java.security.SecureRandom; | |
import java.text.DateFormat; | |
import java.text.SimpleDateFormat; | |
import java.util.Calendar; | |
import java.util.Collections; | |
import java.util.Set; | |
import java.util.TimeZone; | |
import javax.xml.soap.SOAPElement; | |
import javax.xml.soap.SOAPEnvelope; | |
import javax.xml.soap.SOAPException; | |
import javax.xml.soap.SOAPHeader; | |
import javax.xml.ws.handler.MessageContext; | |
import javax.xml.ws.handler.soap.SOAPHandler; | |
import javax.xml.ws.handler.soap.SOAPMessageContext; | |
import sun.misc.BASE64Encoder; | |
public class SimpleWsAuthHandler implements SOAPHandler<SOAPMessageContext> { | |
public boolean handleMessage(SOAPMessageContext smc) { | |
Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); | |
if (outboundProperty.booleanValue()) { | |
try { | |
String usernameStr = "myusername"; | |
String passwordStr = "myPassword"; | |
//From the spec: Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) ) | |
//Make the nonce | |
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG"); | |
rand.setSeed(System.currentTimeMillis()); | |
byte[] nonceBytes = new byte[16]; | |
rand.nextBytes(nonceBytes); | |
//Make the created date | |
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); | |
df.setTimeZone(TimeZone.getTimeZone("UTC")); | |
String createdDate = df.format(Calendar.getInstance().getTime()); | |
byte[] createdDateBytes = createdDate.getBytes("UTF-8"); | |
//Make the password | |
byte[] passwordBytes = passwordStr.getBytes("UTF-8"); | |
//SHA-1 hash the bunch of it. | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
baos.write(nonceBytes); | |
baos.write(createdDateBytes); | |
baos.write(passwordBytes); | |
MessageDigest md = MessageDigest.getInstance("SHA-1"); | |
byte[] digestedPassword = md.digest(baos.toByteArray()); | |
//Encode the password and nonce for sending | |
String passwordB64 = (new BASE64Encoder()).encode(digestedPassword); | |
String nonceB64 = (new BASE64Encoder()).encode(nonceBytes); | |
//Now create the header with all the appropriate elements | |
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope(); | |
SOAPHeader header = envelope.addHeader(); | |
SOAPElement security = header.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); | |
SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse"); | |
SOAPElement username = usernameToken.addChildElement("Username", "wsse"); | |
username.addTextNode(usernameStr); | |
SOAPElement password = usernameToken.addChildElement("Password", "wsse"); | |
password.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"); | |
password.addTextNode(passwordB64); | |
SOAPElement nonce = usernameToken.addChildElement("Nonce", "wsse"); | |
nonce.addTextNode(nonceB64); | |
SOAPElement created = usernameToken.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); | |
created.addTextNode(createdDate); | |
smc.getMessage().writeTo(System.out); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
return outboundProperty; | |
} | |
public Set getHeaders() { | |
return Collections.emptySet(); | |
} | |
public void close(MessageContext context) { | |
} | |
public boolean handleFault(SOAPMessageContext context) { | |
return false; | |
} | |
} |
No comments:
Post a Comment