Spring Web Service to Securely Transfer SOAP Request and Response Call
In this post, we explore the way in which you can apply encryption to a SOAP request and response call using a 256-bit key.
Join the DZone community and get the full member experience.
Join For FreeIn this article, I will explain the steps to transfer a SOAP Request and Response call securely with a 256-bit key.
I have used the Spring framework with JAXws and AES algorithms for encryption and decryption.
I hope this article will help you to understand this secure way of transferring SOAP calls using a public key as shown in below figure.
Sample Soap UI output
Web.xml
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Spring Web Service with AES Encryption </display-name>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>wsservice</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>wsservice</servlet-name>
<url-pattern>/AESWS</url-pattern>
</servlet-mapping>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<session-config>
<session-timeout>5</session-timeout>
</session-config>
</web-app>
Dispatcher-servlet.xml
The Config.properties files are configured in the property-placeholder. The AES Key is configured in these properties files.
Below is the AES encrypted Java file to marshal and unmarshal the data.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<context:annotation-config />
<context:component-scan base-package="com.shri" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/views/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<mvc:resources mapping="/resources/**" location="/resources/" />
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="message" />
</bean>
<context:property-placeholder location="classpath:config.properties, classpath:message.properties"
ignore-unresolvable="true"/>
<mvc:annotation-driven />
<!-- AWS Encryption starts -->
<bean id="encrypt" class="com.shri.ws.shared.AESEncrypted" >
<constructor-arg value="${aes_key}" type="String"></constructor-arg>
</bean>
<!-- AWS Encryption end-->
</beans>
application-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ws="http://jax-ws.dev.java.net/spring/core"
xmlns:wss="http://jax-ws.dev.java.net/spring/servlet" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://jax-ws.dev.java.net/spring/core
http://jax-ws.dev.java.net/spring/core.xsd
http://jax-ws.dev.java.net/spring/servlet
http://jax-ws.dev.java.net/spring/servlet.xsd">
<import resource="dispatcher-servlet.xml" />
<!-- Web service binding status -->
<wss:binding url="/AESWS">
<wss:service>
<ws:service bean="#shriAESWS" />
</wss:service>
</wss:binding>
<!-- Web service binding status ends -->
<!-- Web service methods bean Declaration -->
<!-- Service -->
<bean id="shriAESWS" class="com.shri.ws.service.WSAppService">
<property name="enqObjectImpl" ref="enqObjectImpl"></property>
</bean>
<!-- Service Ends -->
<!-- Implementation Object -->
<bean id="enqObjectImpl" class="com.shri.ws.impl.EnqObjectImpl" />
<!-- Implementation Object Ends -->
<!-- Web service methods bean Declaration Ends -->
</beans>
config.properties file
This is the public AES 256-bit key used for Encryption and Decryption.
aes_key= x!A%D*G-KaPdSgVkYp3s6v8y/B?E(H+M
The WSAppService extends SpringBeanAutowiringSupport
.
Include your DAO Process once the request contains a valid file.
Create additional request using the autowired @WebMethod
(operationName="User_DEFINE") in the method.
@WebService(serviceName = "WSAppService", portName = "WSAppServicePort",
targetNamespace = "http://example.org/AESWS")
public class WSAppService extends SpringBeanAutowiringSupport {
@Resource
private WebServiceContext wsctx;
static Logger log = Logger.getLogger(WSAppService.class.getName());
private EnqObjectImpl enqObjectImpl;
@WebMethod(exclude = true)
public void setEnqObjectImpl(EnqObjectImpl enqObjectImpl) {
this.enqObjectImpl = enqObjectImpl;
}
//WebService
@WebMethod(operationName = "WS")
@WebResult(name = "OUTBOUND")
@XmlJavaTypeAdapter(AESEncrypted.class) //Used to encrypt and decrypt
public String startProcess(
@WebParam(name = "INBOUND") @XmlJavaTypeAdapter(AESEncrypted.class) String request ){
log.info(" Request : " + request);
String response; // response
Outbound outbound = new Outbound();
try{
log.info(request);
//Validate the request
if(request.trim().length()>0 && !request.trim().equalsIgnoreCase("?"))
//After unmarsel the header request tag, validate the request by XSD schema
//Step1
XmlToObject xmlToObject = new XmlToObject();
//Step2
//Convert request string to object
Inbound inbound = (Inbound)XmlToObject.convert(Inbound.class, request, outbound);
if (outbound.getStatus() == "failure") {
response = outbound.toString(); // Outbound XML tag
return response;
}else{
//Do your DAO process here
log.info(inbound.getName());
log.info(inbound.getAddress());
//Move to service and start DAO Process
outbound = enqObjectImpl.startProcess(inbound, outbound);
outbound.setStatus("success");
}
}else{
outbound.setStatus("failure");
}
}catch(Exception err){
log.info(err.toString());
outbound.setStatus("failure");
}
response = outbound.toString();
return response;
}
}
XmlToObject
Convert XML code to an Object by using JAXB; validation is also enabled in this process. If we want, we can push through the response with a valid failure message.
public class XmlToObject {
static Logger log = Logger.getLogger(XmlToObject.class.getName());
public static Object convert (Class entityClass,String xml, Outbound outbound) throws SAXException{
log.info("Entity class "+ entityClass.getName());
//XML Validation
ValidationEventCollector vec = new ValidationEventCollector();
Object inbound = null;
try {
//It generate the URL entityClass.getResource() for the folder
URL xsdURL = entityClass.getResource("schema.xsd");
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(xsdURL);
JAXBContext jaxbContext = JAXBContext.newInstance(entityClass);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema(schema);
unmarshaller.setEventHandler(vec);
StringReader reader = new StringReader(xml);
inbound = (Object)unmarshaller.unmarshal(reader);
} catch (JAXBException err) {
if (vec != null && vec.hasEvents()) {
for (ValidationEvent ve : vec.getEvents()) {
String msg = ve.getMessage();
ValidationEventLocator vel = ve.getLocator();
int numLigne = vel.getLineNumber();
int numColonne = vel.getColumnNumber();
log.info("Error validation xml" + "Error : " + numLigne + "." + numColonne + ": " + msg +" : "+ ve.getSeverity());
}
}
outbound.setStatus("failure");
if (err.getLinkedException () == null){
log.debug(err.toString());
//Add This message to your response by adding additional datatype in Outdata file
}else{
log.debug(err.getLinkedException().getMessage());
}
}
return inbound;
}
}
AESEncrypted.java
This file helps to encrypt and decrypt the data and it overwrites it with @Service
org.springframework.stereotype.Service.
I used base64 code with an Apache common codec jar.
@Service
public class AESEncrypted extends XmlAdapter<String, String>{
//Secret Key values set from properties files
private static SecretKeySpec secretKey;
private static Cipher cipher;
static Logger log = Logger.getLogger(AESEncrypted.class.getName());
public AESEncrypted(){}
//Value set from dispatcher Servlet constructor INJECTION
public AESEncrypted(String awsKey){
try {
secretKey = new SecretKeySpec(awsKey.getBytes(), "AES");
cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
} catch (Exception e) {
log.info("Secrate Key Loading issue !! " );
}
}
/**
* Encrypts the value to be placed back in XML
*/
@Override
public String marshal(String plaintext) {
try{
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] cipherText = cipher.doFinal(plaintext.getBytes("UTF8"));
String encryptedString = new String(Base64.encodeBase64(cipherText),"UTF-8");
return encryptedString;
}catch(Exception err){
log.info(err.getMessage());
return "";
}
}
/**
* Decrypts the string value
*/
@Override
public String unmarshal(String encryptedText) {
try{
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] cipherText = Base64.decodeBase64(encryptedText.getBytes("UTF8"));
String decryptedString = new String(cipher.doFinal(cipherText), "UTF-8");
return decryptedString;
}catch(Exception err){
log.info(err.getMessage());
return "";
}
}
}
Inbound.java
Here, we request an XML data file.
I converted the below Java file to an XSD file with the simple steps below.
1. location> schemagen Program.java
package com.shri.ws.vo;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* @author shrisowdhaman
* Nov 15, 2017
*/
@XmlType(name = "REQ",propOrder={"name","address"})
@XmlRootElement(name ="REQ")
public class Inbound {
private String name;
private String address;
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
@XmlElement(name="NAME",required=true, nillable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
@XmlElement(name="ADDRESS",required=true, nillable=false)
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Schema.xsd
Used to validate the request file with a schema. Validation code available in XmlToObject.java
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="REQ" nillable="true" type="xs:anyType"/>
<xs:complexType name="REQ">
<xs:sequence>
<xs:element name="NAME" type="xs:string"/>
<xs:element name="ADDRESS" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
EnqObject.java
public interface EnqObject {
public Outbound startProcess(Inbound inbound,Outbound outbound);
}
EnqObjectImpl.java
@Service
public class EnqObjectImpl implements EnqObject{
static Logger log = Logger.getLogger(EnqObjectImpl.class.getName());
public Outbound startProcess(Inbound inbound, Outbound outbound) {
if (inbound != null) {
try {
//Call DAO Area and save the values in database
log.info("Inside Inbond");
} catch (Exception err) {
outbound.setStatus("failure");
}
} else {
outbound.setStatus("failure");
}
return outbound;
}
}
You can check my Source Code here.
Java, by default, supports 128-bit keys. If you want to use a 256-bit key, follow the below steps.
Update the Java Cryptography Extension (JCE) to Unlimited Strength Jurisdiction Policy Files to support a 256-bit key.
1. Download the java policy files for Java 7 or Java 8.
2. Locate the jre\lib\security directory for the Java instance that the Atom is using.
For example, this location might be:
C:\Program Files\Java\jre1.8.0_144\lib\security.
C:\Program Files\Java\jdk1.8.0_144\jre\lib\security
3.Replace the following .jar files directory: local_policy.jar and US_export_policy.jar.
4.Stop and restart the eclipse/server.
Check AES Encryption code here.
Opinions expressed by DZone contributors are their own.
Comments