To explain the entire solution, I'll be using Netbeans IDE and it's sample web services.
To Create a Web Service:
Step 1: Create a Sample Calculator Web Service
Click on create new project and select
Samples->WebServices->Calculator
Step 2:
Click next and then finish.
Step 3:
Select Calculator application and start it. It
will open next screenCreate Client:
Step 1:
Create a Java application and then create a new web service
client as in below screen
Step 2:
Select WSDL URL in above screen and then enter Calculator service WSDL url. Enter a package name then click finish. It will automatically generate Web Services Reference
Step 3:
Drag Newly created web service reference by expanding Web Services References tree named add into a newly created
java class TestServices. It will automatically create service call.
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.test;
/**
*
* @author shashi.ranjan
*/
public class TestService {
public static void main(String[] args) {
int result = add(4, 5);
System.out.println(" Adding two numbers : " + result);
}
private static int add(int i, int j) {
com.test.CalculatorWSService service = new com.test.CalculatorWSService();
com.test.CalculatorWS port = service.getCalculatorWSPort();
return port.add(i, j);
}
}
Step 4:
Create a SecurityHandler class into you client application. Below is the code.
package com.test;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.components.crypto.CryptoFactory;
import org.apache.ws.security.message.WSSecEncrypt;
import org.apache.ws.security.message.WSSecEncryptedKey;
import org.apache.ws.security.message.WSSecHeader;
import org.apache.ws.security.message.WSSecSignature;
import org.w3c.dom.Document;
/**
*
* @author shashi.ranjan
*/
public class SecurityHandler implements SOAPHandler<SOAPMessageContext> {
@Override
public boolean handleMessage(SOAPMessageContext messageContext) {
try {
/**
* Getting document from soap context
*/
SOAPMessage message = messageContext.getMessage();
SOAPEnvelope envelop = message.getSOAPPart().getEnvelope();
Document doc = toDocument(message);
/**
* Creating crypto Object.
*/
Crypto crypto = CryptoFactory.getInstance("crypto.properties");
/**
* Using WSS4J API to create encryptor key
*/
WSSecEncryptedKey encrKey = new WSSecEncryptedKey();
encrKey.setKeyIdentifierType(WSConstants.SKI_KEY_IDENTIFIER);
encrKey.setUserInfo("selfsigned", "demo");
encrKey.prepare(doc, crypto);
/**
* Creating encrypter out of encrypt key
*/
WSSecEncrypt builder = new WSSecEncrypt();
builder.setUserInfo("selfsigned", "demo");
builder.setEncKeyId(encrKey.getId());
builder.setEphemeralKey(encrKey.getEphemeralKey());
builder.setEncryptSymmKey(false);
builder.setEncryptedKeyElement(encrKey.getEncryptedKeyElement());
builder.setKeyIdentifierType(WSConstants.SKI_KEY_IDENTIFIER);
builder.setUseKeyIdentifier(true);
/**
* Preparing to sign the document
*/
WSSecSignature sign = new WSSecSignature();
sign.setCustomTokenId(encrKey.getId());
sign.setSignatureAlgorithm(SignatureMethod.RSA_SHA1);
sign.setKeyIdentifierType(WSConstants.SKI_KEY_IDENTIFIER);
sign.setUserInfo("selfsigned", "demo");
/**
* Creating Security Header to insert into document
*/
WSSecHeader secHeader = new WSSecHeader();
secHeader.insertSecurityHeader(doc);
/**
* Encrypting document with crypto and security header
*/
Document signedDoc = sign.build(doc, crypto, secHeader);
builder.build(signedDoc, crypto, secHeader);
/**
* Getting encrypted document
*/
Document encryptedDoc = doc;
/**
* Updating message with encrypted document
*/
DOMSource domSource = new DOMSource(encryptedDoc);
message.getSOAPPart().setContent(domSource);
String outputString = org.apache.ws.security.util.XMLUtils.PrettyDocumentToString(encryptedDoc);
System.out.println("Encrypted Document : " + outputString);
/**
* Logging encrypted soap into file
*/
Boolean outboundProperty = (Boolean) messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty) {
logToSystemOut(message);
}
/**
* Updating message before sending to service.
*/
message.saveChanges();
} catch (SOAPException | WSSecurityException | TransformerException ex) {
Logger.getLogger(SecurityHandler.class.getName()).log(Level.SEVERE, null, ex);
} catch (Exception ex) {
Logger.getLogger(SecurityHandler.class.getName()).log(Level.SEVERE, null, ex);
}
return true;
}
@Override
public Set<QName> getHeaders() {
return Collections.EMPTY_SET;
}
@Override
public boolean handleFault(SOAPMessageContext messageContext) {
return true;
}
@Override
public void close(MessageContext context) {
}
public static org.w3c.dom.Document toDocument(SOAPMessage soapMsg)
throws SOAPException, TransformerException {
Source src = soapMsg.getSOAPPart().getContent();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMResult result = new DOMResult();
transformer.transform(src, result);
return (Document) result.getNode();
}
private void logToSystemOut(SOAPMessage message) throws SOAPException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmddhhmm");
Date date = new Date();
String dateString = dateFormat.format(date);
System.out.println("\nOutbound message:");
File file = null;
PrintStream out = null;
try {
String fileName = "request_invoicedetails_" + dateString + ".xml";
file = new File("D:\\keystore\\" + fileName);
if (!file.exists()) {
file.createNewFile();
}
out = new PrintStream(file);
message.writeTo(out);
System.out.println(out.toString());
} catch (IOException | SOAPException e) {
System.out.println("Exception in handler: " + e);
} finally {
out.close();
}
}
}
The above code is responsible for signing and encrypting the SOAP message. It also dumps SOAP request into a file. Please change path of the file as per your convenience.
Step 5:
Add this SecurityHandler to the webservice by right click on service referece and select add handler.
Run TestService will give you SOAPFaultException because add service is not secure. The same security handler has worked for me in real world scenario where it was a secure SAP service.
Libraries Used:
Its important to note that, whenever I changed versions of jars, I was getting error and exceptions.
Setting up crypto properties:
In order to run the above program, the key is hidden into setting up crypto.properties. I'll explain the process of creating and setting up crypto.properties.
Step 1:
Create a file crypto.properties using notepad and add below entries into it.
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=pkcs12
org.apache.ws.security.crypto.merlin.keystore.password=demo
org.apache.ws.security.crypto.merlin.keystore.alias=selfsigned
org.apache.ws.security.crypto.merlin.alias.password=demo
org.apache.ws.security.crypto.merlin.file=d:\\keystore\\mykeystore.pkcs12
The above propeties values are self explanatory. Replace values of your keystore, type and password as per your keystore values.
Step 2:
Add this crypto.properties into wss4j.1.5.9.jar by executing below command and update library.Once you add this property file to your jar file, you'll be able to create crypto object easily by calling
Crypto crypto = CryptoFactory.getInstance("crypto.properties");
jar -uvf wss4j.1.5.9.jar crypto.properties
Steps to create Sample keystore
Keystores can be created using either keytool or openssl tool. I'll explain both ways.
Using Keytool:
keytool -genkey -keyalg RSA
-alias selfsigned -keystore d:\keystore\mycert.jks -storepass shashi
You can change values as per actual you want to use.
To give public certificate to someone else you can create .cer from this keystore and share.
keytool -export -keystore d:\keystore\mycert.jks -alias selfsigned -file mycert.cer
Using openssl:
Step1: Create Key and Certificate using openssl command
openssl req -new -x509 -extensions v3_ca -keyout private/MyKey.pem -out MyKeycert.pem -days 3650 -config ./openssl.conf
Content of openssl.conf:
[ req ]
default_bits = 1024 # Size of keys
default_keyfile = key.pem # name of generated keys
default_md = md5 # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
# Variable name Prompt string
#---------------------- ----------------------------------
0.organizationName = Organization Name (company)
organizationalUnitName = Organizational Unit Name (department, division)
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64
# Default values for the above, for consistency and less typing.
default_keyfile = key.pem # name of generated keys
default_md = md5 # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
# Variable name Prompt string
#---------------------- ----------------------------------
0.organizationName = Organization Name (company)
organizationalUnitName = Organizational Unit Name (department, division)
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64
# Default values for the above, for consistency and less typing.
# Variable name Value
#------------------------------ ------------------------------
0.organizationName_default = The Sample Company
localityName_default = Metropolis
stateOrProvinceName_default = New York
countryName_default = US
[ v3_ca ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
Step 2:
Export certificate in .pkcs12 format
cat private/MyKey.pem MyKeycert.pem > MyKeyCertificate.pem
openssl pkcs12 -export -in MyKeyCertificate.pem -out MyKeyCertificate.pkcs12 -name selfsigned -noiter -nomaciter
choose password as per actual.
If you have to give public certificate after extracting from MyKeyCertificate.pkcs12, then use below command to do so.
keytool -keystore MyKeyCertificate.pkcs12 -storetype pkcs12 -exportcert -alias selfsigned -file MyKeyCertificate.cer