Basic Spring Web Service Tutorial From Contract to Security
February 25, 2010 at 4:37 am | Posted in Web Services | 23 CommentsTags: security, Spring, Web Services
.
- Introduction
- About the Example
- Web Service Operations
- Data Contract
- Server Project Setup
- Implementing the Enpdoint
- Endpoint Mapping
- Invoking the Web Service
- Client Project Setup
- Writing the Web Service Client
- Securing the Web Service – Server
- Securing the Web Service – Client
- Download PDF and Source
I have developed web services using the so called easy way, which is the code first approach. In this kind of technique, the xml schema and the wsdl are generated by the framework. The ones I’ve used were Metro web services and Apache CXF. Both of them uses JAX-WS annotations. It was so easy to use that you simply use annotations, deploy it to a web server, then … poom! There goes the xml schema and the wsdl generated on the fly. I didn’t care about it as long as it works! It too works very well with a client generated by wsimport from the command line.When I got a break, I checked out Spring web services which uses the conract first approach. Contract first web service approach is an industry standard best practice when developing web services.
Honestly speaking, spring web services was a lot harder to learn that JAX-WS based frameworks. It requires knowledge of SOAP, xml schema, xml frameworks, and marshallers. Despite the learning curve, the reward is so rewarding. Once you get used to programming spring web services, it will be as easy as coding a JAX-WS based framework. I’m gonna check out RESTful services soon.
After enjoying soap web service development with Spring, I decided to make my own article that will get up and running spring web services from contract to security.
I assume that you have already gone through Part 1 of spring web services documentation, especially the Chapter 3 of the reference. The example in this article is a CRUD like application using SOAP Spring web services. It will show you how to create a web service from contract to security. It uses JAXB2 un/marshalling and WSS4j for security.
The example uses the following libraries: Spring 2.5.6 Spring WS 1.5.9 jdk6 runtime The ide used was Eclipse 3.5.1 with springide pluggin. You can use any of your favorite IDES. Here’s the scenario. Our target domain is Person. The domain can be processed with methods like, AddPerson, GetPerson, GetAllPersons, UpdatePerson, and DeletePerson. The web service can only be accessed by authorized users. Unauthorized users cannot perfrom any of the service operation. To keep things simple, we’ll use an in-memory Map datasource instead of a database.
Web service uses xml to send and receive data. Some operations have both request and response. Some only have request. A soap fault becomes a response when somethings wrong with the operation such as missing Id, person not found from the server. The following operations are self explanatory.
| Operation | Request XML | Response XML |
| AddPerson |
<AddPersonRequest>
<Person Id="1">
<FirstName>Clark</FirstName>
<LastName>Kent</LastName>
</Person>
</AddPersonRequest>
|
|
| GetPerson |
<GetPersonRequest Id="1" /> |
<GetPersonResponse>
<Person Id="1">
<FirstName>Clark</FirstName>
<LastName>Kent</LastName>
</Person>
</GetPersonResponse>
|
| GetAllPersons |
<GetAllPersons /> |
<GetAllPersonResponse>
<Person Id="1">
<FirstName>Clark</FirstName>
<LastName>Kent</LastName>
</Person>
<Person Id="2">
<FirstName>Bruce</FirstName>
<LastName>Wayne</LastName>
</Person>
<Person Id="3">
<FirstName>Harold</FirstName>
<LastName>Jordan</LastName>
</Person>
</GetAllPersonsResponse>
|
| UpdatePerson |
<UpdatePersonRequest>
<Person Id="1">
<FirstName>Clark Joseph</FirstName>
<LastName>Kent</LastName>
</Person>
</UpdatePersonRequest>
|
|
| DeletePerson |
<DeletePersonRequest Id="1" /> |
The next section shows the xml schema for the xml messages.
Here’s where we start our contract first approach, the data contract. The wsdl contract can be happily generated by Spring’s DefaultWsdl11Definition later. There are many tools out there to generate schema from xml such as Trang command and XMLBeans’s ints2xsd. For our simple example, I personally like doing it by hand. Here’s a good tutorial for writing xml schema by hand http://www.w3schools.com/Schema/default.asp.
The resulting schema can be saved as person.xml. We’ll use this xml file later on the project setup. person.xml
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/person/schema"
xmlns:per="http://www.example.org/person/schema"
elementFormDefault="qualified">
<xs:element name="Person">
<xs:complexType>
<xs:sequence>
<xs:element name="FirstName" type="xs:string" />
<xs:element name="LastName" type="xs:string" />
</xs:sequence>
<xs:attribute ref="per:Id" use="required" />
</xs:complexType>
</xs:element>
<xs:attribute name="Id" type="xs:int" />
<xs:element name="AddPersonRequest">
<xs:complexType>
<xs:sequence>
<xs:element ref="per:Person" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="GetPersonRequest">
<xs:complexType>
<xs:attribute ref="per:Id" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="GetPersonResponse">
<xs:complexType>
<xs:sequence>
<xs:element ref="per:Person" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="GetAllPersonsRequest">
<xs:complexType>
<xs:sequence />
</xs:complexType>
</xs:element>
<xs:element name="GetAllPersonsResponse">
<xs:complexType>
<xs:sequence>
<xs:element ref="per:Person" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="UpdatePersonRequest">
<xs:complexType>
<xs:sequence>
<xs:element ref="per:Person" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="DeletePersonRequest">
<xs:complexType>
<xs:attribute ref="per:Id" use="required" />
</xs:complexType>
</xs:element>
</xs:schema>
Some of the things you should notice are the operations element. They have suffixes “Request” and “Response”. This the default suffix of Spring’s DefaultWsdl11Definition. Later on deploying the web service, the operation elements become the input/output elements. The prefix removed becomes the operation name. For example, the GetPersonElement generates this wsdl element
<wsdl:operation name="GetPerson">
<soap:operation soapAction="" />
<wsdl:input name="GetPersonRequest">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="GetPersonResponse">
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
The next section is the eclipse project setup for the web service server. You can use any IDE you want.
1. Start a new dynamic web project named person.
2. Place person.xsd from the previous section into the WEB-INF folder.
3. Add the following jar files to WEB-INF/lib directory of the project. These jar files are (roughly) the dependencies I simplified from the echo sample. The file can be located under the lib folder of spring-2.5.6 and spring-ws-1.5.9 distribution.
aopalliance.jar
bcprov-jdk14-1.43.jar
commons-logging-1.1.1.jar
log4j-1.2.15.jar
opensaml-1.1.jar
saaj-impl-1.3.2.jar
spring.jar
spring-webmvc.jar
spring-ws-1.5.9-all.jar
wsdl4j-1.6.1.jar
wss4j-1.5.8.jar
xalan-2.7.0.jar
xercesImpl-2.8.1.jar
xml-apis-1.3.04.jar
xmlsec-1.4.3.jar
xmlsec-2.0.jar
xws-security-2.0-FCS.jar
4. Create a new source folder named “generated” under the project folder.
5. Generate java files from person.xsd by using JAXB’s xjc command to
your project directory.
xjc -d generated WEBContent/WEB-INF/person.xsd
You can refresh your project to view the generated files. An easier way to do it is with XJC plugin from this site https://jaxb-workshop.dev.java.net/plugins/eclipse/xjc-plugin.html
6. Create a log4j.properties file under the src folder with the following contents. These are same contents from spring ws samples.
log4j.rootLogger=WARN, stdout log4j.logger.org.springframework.ws=DEBUG log4j.logger.org.springframework.xml=DEBUG log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
7. Create the service classes under the src folder.
example.service.PersonException – This will be a soap fault response
public class PersonException extends Exception {
public PersonException(String message) {
super(message);
}
}
example.service.PersonService – The service interface
import java.util.List;
import org.example.person.schema.Person;
public interface PersonService {
void addPerson(Person person) throws PersonException;
Person getPerson(Integer id) throws PersonException;
List<Person> getAllPersons();
void updatePerson(Person person) throws PersonException;
void deletePerson(Integer id) throws PersonException;
}
example.service.PersonServiceImpl – The service implementation
package example.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.example.person.schema.Person;
public class PersonServiceImpl implements PersonService {
private Map dataSource;
@Override
public void addPerson(Person person) throws PersonException {
if (idExists(person.getId())) {
throw new PersonException("Cannot add person. Id already exists.");
}
dataSource.put(person.getId(), person);
}
@Override
public void deletePerson(Integer id) throws PersonException {
if (!idExists(id)) {
throw new PersonException("Cannot delete person. Id does not exists.");
}
dataSource.remove(id);
}
@Override
public List<Person> getAllPersons() {
return new ArrayList(dataSource.values());
}
@Override
public Person getPerson(Integer id) throws PersonException {
if (!idExists(id)) {
throw new PersonException("Cannot get person. ID does not exist.");
}
return dataSource.get(id);
}
@Override
public void updatePerson(Person person) throws PersonException {
if (!idExists(person.getId())) {
throw new PersonException("Cannot update person. ID does not exist.");
}
dataSource.put(person.getId(), person);
}
public void initialize() {
dataSource = new HashMap();
putPerson(1, "Clark", "Kent");
putPerson(2, "Bruce", "Wayne");
putPerson(3, "Harold", "Jordan");
}
private void putPerson(Integer id, String firstName, String lastName) {
Person person = new Person();
person.setId(id);
person.setFirstName(firstName);
person.setLastName(lastName);
dataSource.put(id, person);
}
private boolean idExists(Integer id) {
return dataSource.containsKey(id);
}
}
8 .Edit web.xml. Notice the servlet name, which is spring-ws. This servlet will for the file spring-ws-servlet.xml.
WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_4.dtd">
<web-app>
<display-name>Person Service</display-name>
<description>Simpe CRUD like Application</description>
<!-- Defines the Spring-WS MessageDispatcherServlet -->
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<init-param>
<!-- Transform the location attributes in WSDLs -->
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<!-- Map all requests to this servlet -->
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
9. Create spring-ws-servlet.xml under the WEB-INF folder. The bean id determines the name of the wsdl. In this configuration, the bean id of the wsdl is “Person” so we can access the wsdl with this url from http://localhost:8080/person/Person.wsdl
<?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:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd"> <bean id="personService" class="example.service.PersonServiceImpl" init-method="initialize" /> <bean id="Person" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition"> <property name="schema" ref="schema" /> <property name="portTypeName" value="Person" /> <property name="locationUri" value="/services" /> <property name="targetNamespace" value="http://www.example.org/person/schema" /> </bean> <bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema"> <property name="xsd" value="/WEB-INF/person.xsd" /> </bean> </beans>
The resulting directory structure should be similar with this image.
By now you can deploy the project to an application server and view the wsdl from http://localhost:8080/person/Person.wsdl. Make sure that the operations are presents. These operations can’t do anything yet. The following section shows how to make this operations work implementing and mapping the endpoint.
Each wsdl operation may be an input or output. We only need to map the request most of the time and the framework takes care of the response.
Our base class, AbstractPersonEndpoint extends AbstractMarshallingPayloadEndpoint and has its own personService property. The properties: personService, marshaller and unmarshaller will be autowired by spring. Our endpoints will extend our base class.
example.endpoint.AbstractPersonEndpoint
package example.endpoint;
import org.springframework.ws.server.endpoint.AbstractMarshallingPayloadEndpoint;
import example.service.PersonService;
public abstract class AbstractPersonEndpoint extends AbstractMarshallingPayloadEndpoint {
protected PersonService personService;
public void setPersonService(PersonService personService) {
this.personService = personService;
}
protected abstract Object invokeInternal(Object request) throws Exception;
}
example.endpoint.AddPersonEndpoint
package example.endpoint;
import org.example.person.schema.AddPersonRequest;
public class AddPersonEndpoint extends AbstractPersonEndpoint {
@Override
protected Object invokeInternal(Object request) throws Exception {
AddPersonRequest addPersonRequest = (AddPersonRequest) request;
personService.addPerson(addPersonRequest.getPerson());
return null;
}
}
example.endpoint.DeletePersonEndpoint
package example.endpoint;
import org.example.person.schema.DeletePersonRequest;
public class DeletePersonEndpoint extends AbstractPersonEndpoint {
@Override
protected Object invokeInternal(Object request) throws Exception {
DeletePersonRequest deletePersonRequest = (DeletePersonRequest) request;
personService.deletePerson(deletePersonRequest.getId());
return null;
}
}
example.endpoint.GetAllPersonsEndpoint
package example.endpoint;
import org.example.person.schema.GetAllPersonsResponse;
import org.example.person.schema.Person;
public class GetAllPersonsEndpoint extends AbstractPersonEndpoint {
@Override
protected Object invokeInternal(Object request) throws Exception {
GetAllPersonsResponse getAllPersonsResponse = new GetAllPersonsResponse();
for (Person person : personService.getAllPersons()) {
getAllPersonsResponse.getPerson().add(person);
}
return getAllPersonsResponse;
}
}
example.endpoint.GetPersonEndpoint
package example.endpoint;
import org.example.person.schema.GetPersonRequest;
import org.example.person.schema.GetPersonResponse;
public class GetPersonEndpoint extends AbstractPersonEndpoint {
@Override
protected Object invokeInternal(Object request) throws Exception {
GetPersonRequest getPersonRequest = (GetPersonRequest) request;
GetPersonResponse getPersonResponse = new GetPersonResponse();
getPersonResponse.setPerson(personService.getPerson(getPersonRequest.getId()));
return getPersonResponse;
}
}
example.endpoint.UpdatePersonEndpoint
package example.endpoint;
import org.example.person.schema.UpdatePersonRequest;
public class UpdatePersonEndpoint extends AbstractPersonEndpoint {
@Override
protected Object invokeInternal(Object request) throws Exception {
UpdatePersonRequest updatePersonRequest = (UpdatePersonRequest) request;
personService.updatePerson(updatePersonRequest.getPerson());
return null;
}
}
One thing you’ll notice is the method signature of invokeInternal method. The argument is usually the request object and the return is the response object. The request and response objects are those generated by JAXB. Yes, those with -Request and -Response suffixes. How these classes were mapped will be shown the next section.
Mapping the endpoints is like mapping the input operation to spring bean end point. The endpoints will have the common properties: personService, marshaller, and unmarshaller. There are different ways to declare jaxb un/marshallers, see http://static.springsource.org/spring-ws/sites/1.5/reference/html/oxm.html#oxm-jaxb2.
JAXB2 marshaller and unmarshaller are declared with the following bean definition. It uses xml-schema based configuration. The context path is the package name of JAXB generated classes.
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.example.person.schema" /> <oxm:jaxb2-marshaller id="unmarshaller" contextPath="org.example.person.schema" />
Each of the endpoints are declared with the following bean definition and properties autowired by name.
<bean id="addPersonEndpoint" class="example.endpoint.AddPersonEndpoint" autowire="byName"/> <bean id="deletePersonEndpoint" class="example.endpoint.DeletePersonEndpoint" autowire="byName"/> <bean id="getAllPersonsEndpoint" class="example.endpoint.GetAllPersonsEndpoint" autowire="byName"/> <bean id="getPersonEndpoint" class="example.endpoint.GetPersonEndpoint" autowire="byName"/> <bean id="updatePersonEndpoint" class="example.endpoint.UpdatePersonEndpoint" autowire="byName"/>
Interceptors
In addtion to just mapping the endpoints, we declare interceptors for validating schema , logging the xml messages, and mapping the server exceptions.
The validatingInterceptor validates rquest and response xml against the schema.
<bean id="validatingInterceptor" class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="xsdSchema" ref="schema" />
<property name="validateRequest" value="true" />
<property name="validateResponse" value="true" />
</bean>
The loggingIinterceptor logs xml messages to the console.
<bean id="loggingInterceptor" class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor" />
Mapping request to beans
Now, the putting the endpoints, interceptors and mapping altogether. The key is the {namespace}request and the value is the spring bean endpoint that processes the request.
<bean name="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="interceptors">
<list>
<ref local="loggingInterceptor" />
<ref local="validatingInterceptor" />
</list>
</property>
<property name="mappings">
<props>
<prop key="{http://www.example.org/person/schema}AddPersonRequest">addPersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}DeletePersonRequest">deletePersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}GetAllPersonsRequest">getAllPersonsEndpoint</prop>
<prop key="{http://www.example.org/person/schema}GetPersonRequest">getPersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}UpdatePersonRequest">updatePersonEndpoint</prop>
</props>
</property>
</bean>
Exception Resolver
Well resolve exceptions as soap faults. We don’t want a stack of stack traces to be sent to the client when an exception occurs. We just send the error code and the error message.
<bean id="exceptionResolver" class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<property name="defaultFault" value="SERVER" />
<property name="exceptionMappings">
<props>
<prop key="org.springframework.oxm.ValidationFailureException">CLIENT,Invalid request</prop>
<prop key="example.service.PersonException">SERVER</prop>
</props>
</property>
</bean>
The updated spring-ws-servlet.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:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd">
<bean id="personService" class="example.service.PersonServiceImpl" init-method="initialize" />
<bean id="Person" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema" ref="schema" />
<property name="portTypeName" value="Person" />
<property name="locationUri" value="/services" />
<property name="targetNamespace" value="http://www.example.org/person/schema" />
</bean>
<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
<property name="xsd" value="/WEB-INF/person.xsd" />
</bean>
<bean id="validatingInterceptor" class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="xsdSchema" ref="schema" />
<property name="validateRequest" value="true" />
<property name="validateResponse" value="true" />
</bean>
<bean id="loggingInterceptor" class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor" />
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.example.person.schema" />
<oxm:jaxb2-marshaller id="unmarshaller" contextPath="org.example.person.schema" />
<bean id="addPersonEndpoint" class="example.endpoint.AddPersonEndpoint" autowire="byName" />
<bean id="deletePersonEndpoint" class="example.endpoint.DeletePersonEndpoint" autowire="byName" />
<bean id="getAllPersonsEndpoint" class="example.endpoint.GetAllPersonsEndpoint" autowire="byName" />
<bean id="getPersonEndpoint" class="example.endpoint.GetPersonEndpoint" autowire="byName" />
<bean id="updatePersonEndpoint" class="example.endpoint.UpdatePersonEndpoint" autowire="byName" />
<bean name="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="interceptors">
<list>
<ref local="loggingInterceptor" />
<ref local="validatingInterceptor" />
</list>
</property>
<property name="mappings">
<props>
<prop key="{http://www.example.org/person/schema}AddPersonRequest">addPersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}DeletePersonRequest">deletePersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}GetAllPersonsRequest">getAllPersonsEndpoint</prop>
<prop key="{http://www.example.org/person/schema}GetPersonRequest">getPersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}UpdatePersonRequest">updatePersonEndpoint</prop>
</props>
</property>
</bean>
<bean id="exceptionResolver" class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<property name="defaultFault" value="SERVER" />
<property name="exceptionMappings">
<props>
<prop key="org.springframework.oxm.ValidationFailureException">CLIENT,Invalid request</prop>
<prop key="example.service.PersonException">SERVER</prop>
</props>
</property>
</bean>
</beans>
The web service now has functional endpoints. We can deploy this project to a web server.
The next section shows how to invoke the web service using SOAP UI and spring ws client.
SOAP UI
The following screen shots the web service invoked with SOAP UI. There is a free version from their site www.soapui.org.
GetPerson successfully invoked.
Spring WS client
The next section shows how to make a client using spring ws client framework.
We’ll use spring-ws to code the client. The client also uses JAXB2 marshalling and unmarshalling.
1. Create a new java project.
2. Create a new source folder named generated under the project folder.
3. Copy the generated classes from the server project to the generated folder. Results are the same if you reexecute xjc against the schema.
4. Create a log4j.properties under the default package of the source folder with the following properties. We turned off the ws logs so it will not clutter the console.
log4j.rootLogger=WARN, stdout log4j.logger.org.springframework.ws=OFF log4j.logger.org.springframework.xml=DEBUG log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
5. Add the following dependencies to the project
commons-httpclient-3.1.jar commons-logging-1.1.1.jar
log4j-1.2.15.jar spring.jar
spring-ws-1.5.9-all.jar wss4j-1.5.8.jar
xmlsec-1.4.3.jar
6. Create package example.client under the src folder. Create applicationContext.xml
The resulting directory structure should be similar with this screen shot.
Writing the Web Service client
example.client.PersonClient – The client interface
package example.client;
import java.util.List;
import org.example.person.schema.Person;
public interface PersonClient {
void addPerson(Person person);
Person getPerson(Integer id);
List<Person> getAllPersons();
void updatePerson(Person person);
void deletePerson(Integer id);
}
example.client.PersonClientImpl -Implementing class. This class extends WebServiceGatewaySupport which has properties like webserviceTemplate, marshaller, unmarshaller, and interceptors which will be injected by spring.
package example.client;
import java.util.List;
import org.example.person.schema.AddPersonRequest;
import org.example.person.schema.DeletePersonRequest;
import org.example.person.schema.GetAllPersonsRequest;
import org.example.person.schema.GetAllPersonsResponse;
import org.example.person.schema.GetPersonRequest;
import org.example.person.schema.GetPersonResponse;
import org.example.person.schema.Person;
import org.example.person.schema.UpdatePersonRequest;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
public class PersonClientImpl extends WebServiceGatewaySupport implements PersonClient {
@Override
public void addPerson(Person person) {
AddPersonRequest request = new AddPersonRequest();
request.setPerson(person);
getWebServiceTemplate().marshalSendAndReceive(request);
}
@Override
public void deletePerson(Integer id) {
DeletePersonRequest request = new DeletePersonRequest();
request.setId(id);
getWebServiceTemplate().marshalSendAndReceive(request);
}
@Override
public List<Person> getAllPersons() {
GetAllPersonsRequest request = new GetAllPersonsRequest();
GetAllPersonsResponse response = (GetAllPersonsResponse) getWebServiceTemplate()
.marshalSendAndReceive(request);
return response.getPerson();
}
@Override
public Person getPerson(Integer id) {
GetPersonRequest request = new GetPersonRequest();
request.setId(id);
GetPersonResponse response = (GetPersonResponse) getWebServiceTemplate()
.marshalSendAndReceive(request);
return response.getPerson();
}
@Override
public void updatePerson(Person person) {
UpdatePersonRequest request = new UpdatePersonRequest();
request.setPerson(person);
getWebServiceTemplate().marshalSendAndReceive(request);
}
}
applicationContext.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:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd">
<bean name="webserviceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="defaultUri" value="http://localhost:8080/person" />
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="unmarshaller" />
</bean>
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.example.person.schema" />
<oxm:jaxb2-marshaller id="unmarshaller" contextPath="org.example.person.schema" />
<bean id="client" class="example.client.PersonClientImpl">
<property name="webServiceTemplate" ref="webserviceTemplate" />
</bean>
</beans>
example.client.Main – This will test our client
package example.client;
import java.util.List;
import org.example.person.schema.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.ws.soap.client.SoapFaultClientException;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
PersonClient client = (PersonClient) ctx.getBean("client");
Person person = null;
// Add person
try {
person = createPerson(5, "Lex", "Luthor");
System.out.println("Add this person " + makeString(person));
client.addPerson(person);
} catch (SoapFaultClientException se) {
System.out.println("\t" + se.getMessage());
}
// Get person
try {
int id = 1;
System.out.println("Get person with id=" + id + "...");
person = client.getPerson(id);
System.out.println("\tPerson Response " + makeString(person));
} catch (SoapFaultClientException se) {
System.out.println("\t" + se.getMessage());
}
// Update the person
try {
System.out.println("Update person's first name");
person.setFirstName("Clark Joseph");
client.updatePerson(person);
System.out.println("\tsucessfully invoked update.");
} catch (SoapFaultClientException se) {
System.out.println("\t" + se.getMessage());
}
// Delete person
try {
int id = 9999;
System.out.println("Delete person with id=" + id);
client.deletePerson(id);
} catch (SoapFaultClientException se) {
System.out.println("\t" + se.getMessage());
}
// Get all persons
System.out.println("Get all persons...");
List<Person> persons = client.getAllPersons();
for (Person p : persons) {
System.out.println("\t" + makeString(p));
}
}
private static Person createPerson(int id, String firstName, String lastName) {
Person person = new Person();
person.setId(id);
person.setFirstName(firstName);
person.setLastName(lastName);
return person;
}
private static String makeString(Person p) {
return "[id=" + p.getId() + ", firstName=" + p.getFirstName() + ", lastName="
+ p.getLastName() + "]";
}
}
Running the client
Execute the example.client.Main. The console prints text similar to these.
Get person with id=1... Person Response [id=1, firstName=Clark, lastName=Kent] Update person's first name sucessfully invoked update. Delete person with id=9999 Cannot delete person. Id does not exists. Get all persons... [id=1, firstName=Clark Joseph, lastName=Kent] [id=2, firstName=Bruce, lastName=Wayne] [id=3, firstName=Harold, lastName=Jordan] [id=5, firstName=Lex, lastName=Luthor]
By now, the web service and the client works well. The next sections adds security the web service wherein it requires the client to include a username token.
.
Securing the Web Service
In our example we’ll use WSS4J security. More information on how use use WSS4j security can be found at spring ws documetation.
Add the these bean definitions in the server’s spring-ws-servlet.xml.
<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="UsernameToken" />
<property name="validationCallbackHandler" ref="callbackHandler" />
</bean>
<bean id="callbackHandler" class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="Bert">Ernie</prop>
<prop key="Mickey">Mouse</prop>
</props>
</property>
</bean>
The configured the wsSecurityInterceptor with the following properties.
validationActions = UserNameToken. This requires the request to have username tokens in the header.
validationCallbackHandler calls a reference to the bean, callbackHandler.
The callbackHandler bean is SimplePasswordCallBackHandlerwhich is configured to handle an in-memory collection of users and passwords in clear text.
Add wsSecurityInterceptor to the list of interceptors of the endpoint mapping
<bean name="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="interceptors">
<list>
<ref local="loggingInterceptor" />
<ref local="validatingInterceptor" />
<ref local="wsSecurityInterceptor" />
</list>
</property>
...
</bean>
That’s it. Security is just a matter of configuration.
The updated spring-ws-servlet.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:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd">
<bean id="personService" class="example.service.PersonServiceImpl" init-method="initialize" />
<bean id="Person" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema" ref="schema" />
<property name="portTypeName" value="Person" />
<property name="locationUri" value="/services" />
<property name="targetNamespace" value="http://www.example.org/person/schema" />
</bean>
<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
<property name="xsd" value="/WEB-INF/person.xsd" />
</bean>
<bean id="validatingInterceptor" class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="xsdSchema" ref="schema" />
<property name="validateRequest" value="true" />
<property name="validateResponse" value="true" />
</bean>
<bean id="loggingInterceptor" class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor" />
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.example.person.schema" />
<oxm:jaxb2-marshaller id="unmarshaller" contextPath="org.example.person.schema" />
<bean id="addPersonEndpoint" class="example.endpoint.AddPersonEndpoint" autowire="byName" />
<bean id="deletePersonEndpoint" class="example.endpoint.DeletePersonEndpoint" autowire="byName" />
<bean id="getAllPersonsEndpoint" class="example.endpoint.GetAllPersonsEndpoint" autowire="byName" />
<bean id="getPersonEndpoint" class="example.endpoint.GetPersonEndpoint" autowire="byName" />
<bean id="updatePersonEndpoint" class="example.endpoint.UpdatePersonEndpoint" autowire="byName" />
<bean name="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="interceptors">
<list>
<ref local="loggingInterceptor" />
<ref local="validatingInterceptor" />
<ref local="wsSecurityInterceptor" />
</list>
</property>
<property name="mappings">
<props>
<prop key="{http://www.example.org/person/schema}AddPersonRequest">addPersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}DeletePersonRequest">deletePersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}GetAllPersonsRequest">getAllPersonsEndpoint</prop>
<prop key="{http://www.example.org/person/schema}GetPersonRequest">getPersonEndpoint</prop>
<prop key="{http://www.example.org/person/schema}UpdatePersonRequest">updatePersonEndpoint</prop>
</props>
</property>
</bean>
<bean id="exceptionResolver" class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<property name="defaultFault" value="SERVER" />
<property name="exceptionMappings">
<props>
<prop key="org.springframework.oxm.ValidationFailureException">CLIENT,Invalid request</prop>
<prop key="example.service.PersonException">SERVER</prop>
</props>
</property>
</bean>
<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="UsernameToken" />
<property name="validationCallbackHandler" ref="callbackHandler" />
</bean>
<bean id="callbackHandler" class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="Bert">Ernie</prop>
<prop key="Mickey">Mouse</prop>
</props>
</property>
</bean>
</beans>
If you try to run the previous client, you’ll get an output like this.
Add this person [id=5, firstName=Lex, lastName=Luthor] No WS-Security header found Get person with id=1... No WS-Security header found Update person's first name No WS-Security header found Delete person with id=9999 No WS-Security header found Get all persons... Exception in thread "main" org.springframework.ws.soap.client.SoapFaultClientException: No WS-Security header found
The next section shows how to add security access to the client using wss4j as well.
Client security is mostly configuration of the application context.
Add this bean to the client’s applicationContext.xml.
<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="UsernameToken"/>
</bean>
Only the bean’s securement action is defined. Although we can hard code the securementUsername and the securementPassword, well do this on the runtime when the program has to switch usernames to test against the server’s list of usernames.
Add wsSecrityInterceptor to the webserviceTemplate’s list of interceptor.
<bean name="webserviceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="defaultUri" value="http://localhost:8080/person" />
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="unmarshaller" />
<property name="interceptors">
<list>
<ref local="wsSecurityInterceptor" />
</list>
</property>
</bean>
The updated applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<?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:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd">
<bean name="webserviceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="defaultUri" value="http://localhost:8080/person" />
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="unmarshaller" />
<property name="interceptors">
<list>
<ref local="wsSecurityInterceptor" />
</list>
</property>
</bean>
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.example.person.schema" />
<oxm:jaxb2-marshaller id="unmarshaller" contextPath="org.example.person.schema" />
<bean id="client" class="example.client.PersonClientImpl">
<property name="webServiceTemplate" ref="webserviceTemplate" />
</bean>
<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="UsernameToken"/>
</bean>
</beans>
example.client.Main – This will test the client with security. The main class has a method setUsernameToken that allows the securityInterceptor to change username and password.
package example.client;
import org.example.person.schema.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.ws.soap.client.SoapFaultClientException;
import org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
PersonClient client = (PersonClient) ctx.getBean("client");
Wss4jSecurityInterceptor interceptor = (Wss4jSecurityInterceptor) ctx.getBean("wsSecurityInterceptor");
System.out.println("Bert is authorized");
setUsernameToken(interceptor, "Bert", "Ernie");
doGetPerson(client, 1);
System.out.println("Bert got wrong password");
setUsernameToken(interceptor, "Bert", "Big Bird");
doGetPerson(client, 1);
System.out.println("Mickey is also authorized");
setUsernameToken(interceptor, "Mickey", "Mouse");
doGetPerson(client, 2);
System.out.println("Batman is not authorized");
setUsernameToken(interceptor, "Batman", "Robin");
doGetPerson(client, 2);
}
private static void doGetPerson(PersonClient client, int id) {
try {
Person person = null;
System.out.println("Get person with id=" + id + "...");
person = client.getPerson(id);
System.out.println("\tPerson Response " + makeString(person));
} catch (SoapFaultClientException se) {
System.out.println("\t" + se.getMessage());
}
}
private static String makeString(Person p) {
return "[id=" + p.getId() + ", firstName=" + p.getFirstName() + ", lastName=" + p.getLastName() + "]";
}
private static void setUsernameToken(Wss4jSecurityInterceptor interceptor, String user, String pass) {
interceptor.setSecurementUsername(user);
interceptor.setSecurementPassword(pass);
}
}
If you run the program, the console prints something like this.
Bert is authorized Get person with id=1... Person Response [id=1, firstName=Clark, lastName=Kent] Bert got wrong password Get person with id=1... The security token could not be authenticated or authorized; nested exception is org.apache.ws.security.WSSecurityException: The security token could not be authenticated or authorized Mickey is also authorized Get person with id=2... Person Response [id=2, firstName=Bruce, lastName=Wayne] Batman is not authorized Get person with id=2... The security token could not be authenticated or authorized; nested exception is org.apache.ws.security.WSSecurityException: The security token could not be authenticated or authorized
person-ws pdf – PDF equivalent of this article
person-ws-source.zip - Eclipse project sources. Just get the jar files from the lib folder of spring 2.5.6 and spring-ws 1.5.9
23 Comments »
RSS feed for comments on this post. TrackBack URI
Leave a Reply
Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.




Excellent tutorial.
However I’m not able to run it succesfully because it seems (see below) that Maven can’t find the dependency that spring ws security has on xws-security.
Any ideas what might be causing this problem ?
Any ideas how to fix it ?
Can you please also publish your project’s pom.xml file(s) ?
Thanks,
EDH
5/06/10 9:47:36 CEST: Downloading http://mirrors.ibiblio.org/pub/mirrors/maven2/org/springframework/ws/spring-ws-security/1.5.9/spring-ws-security-1.5.9-sources.jar
5/06/10 9:47:36 CEST: Downloaded http://mirrors.ibiblio.org/pub/mirrors/maven2/org/springframework/ws/spring-ws-security/1.5.9/spring-ws-security-1.5.9-sources.jar
5/06/10 9:47:37 CEST: Downloaded sources for org.springframework.ws:spring-ws-security:1.5.9
5/06/10 9:47:38 CEST: Maven Builder: AUTO_BUILD
5/06/10 9:51:21 CEST: Refreshing [/codebase/pom.xml, /led/pom.xml, /led-basis-ws/pom.xml]
5/06/10 9:51:22 CEST: Missing artifact com.sun.xml.wss:xws-security:jar:2.0-FCS:compile
5/06/10 9:51:23 CEST: Maven Builder: AUTO_BUILD
5/06/10 9:54:51 CEST: Refreshing [/codebase/pom.xml, /led/pom.xml, /led-basis-ws/pom.xml]
5/06/10 9:54:52 CEST: [WARN] Missing POM for com.sun.xml.wss:xws-security:jar:2.0-FCS
5/06/10 9:54:52 CEST: Missing artifact com.sun.xml.wss:xws-security:jar:2.0-FCS:compile
Comment by Edwin Dhondt— June 5, 2010 #
The artifact is missing from http://mvnrepository.com/artifact/com.sun.xml.wss
There’s another one from this site http://download.java.net/maven/1/com.sun.xml.wss/
Try the pom from the other site.
If it still does not work, you can try to install the jar files manually with mvn install:install-file -DgroupId= …. ….
The last solution was always my last resort.
Comment by jeromebulanadi— June 8, 2010 #
I had a difficult time locating the required jar files but at last it worked.
Very nice tutorial on spring web service. Your instructor-like approach got me starting with spring web service.
Thanks
Comment by jan— June 22, 2010 #
Very excellent tutorial! Thank you very much, you saved the day!
Comment by Ahmet Yildirim— August 28, 2010 #
excellent tutorial, thanks aloot for sharing this one. few observations or I may say changes that I have done to make this tutorial run in my environment are:-
1. autowire attribute in service endpoint was not working for me, so I wrote the specific property which were needs to be injected, such as
–marshaller and unmarshaller. Please note in such case you need to define jaxb2-marshaller as a bean. for example
com.experian.sws.generated
hence the endpoint tag would now look like
2. since I was using JBoss-4.2.3 as container, following JARs need to be put under server->->lib.
-spring.jar
-spring-ws-1.5.9-all.jar
-spring-webmvc.jar
Rohit Anand
Comment by Rohit Anand— August 28, 2010 #
Hi Rohit,
I am trying to learn SpringWS. I am getting the following excaption
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘Person’ defined in ServletContext resource .
Looks like you made a change. Could you please share what you did to make this work.
Thanks in advance.
Comment by ramanan— October 21, 2010 #
Jerome very superb tutorial. It really help me a lot in my new job. Thanks. More power. God bless always.
Comment by Alden Elevado— October 12, 2010 #
Excellent !
Thanks for your tutorial.
Comment by toan do van— December 2, 2010 #
terrific tutorial – Thank you so much!!!
Comment by tony— December 22, 2010 #
Very nice tutorial !
It was nice to find a tutorial without using Maven.
A lot of tutorials are using Maven but doesn’t give exactly the good list of jars/dependencies and it was very difficult to know which jars must be used and why it doesn’t work. I have spent a lot of time to deal with version problems.
Thank you for your tutorial
Comment by Romain— January 2, 2011 #
Excellent Tutorial…
It works fin in my local. But When I am moving Service on server after adding Wss4jSecurityInterceptor its giving me following exception when I am calling from local. If am removing Interceptor then its gives proper response but below error if I add Wss4jSecurityInterceptor on
PayloadRootAnnotationMethodEndpointMapping. Is it because of HTTPS ?
org.springframework.ws.client.WebServiceTransportException: Internal Server Error [500]
at org.springframework.ws.client.core.WebServiceTemplate.handleError(WebServiceTemplate.java:627)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:551)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:502)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:351)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:345)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:337)
Comment by Pradip— January 28, 2011 #
Excellent tutorial! Everything worked exactly as you described. I’d like now instead of username token use SAML token. Please let me know if you have any suggestions. Thank you very much! Alex
Comment by Alex— January 28, 2011 #
Excellent example. Everything worked first time. I have been trying to get examples of spring ws working for days.
I have now been asked to look at calling a third party ws using their wsdl. Since your example generates the wsdl from the schema, how can I use spring ws to generate the classes that I receive to call the third party web service.
Thanks for all your help.
Comment by Richard— March 21, 2011 #
you can still use xjc from jdk 6.
This example generates classes from Amazon e-commerce
xjc -verbose -wsdl http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsdlComment by jeromebulanadi— March 24, 2011 #
Great tutorial and examples.. This example is much easier than the examples or tutorials from spring-ws
Comment by Michel— May 11, 2011 #
Hi Jerome,
Nice tutorial…………
Comment by prabu G— November 24, 2011 #
thanks Jerome (merci) very clear and detailed example. I hope that you would post more example
Comment by minh nguyen— November 29, 2011 #
very Good Example
Comment by vipin— December 2, 2011 #
Excellent example with excellent explaination.
Comment by Abhaya— December 15, 2011 #
Superb…..Even we can find many tutorials on WebServices,this is simple,clearly explained and very very informative..Thanks for this nice tutorial…
Comment by div— December 20, 2011 #
Nice tuto. Thanks a lot
Comment by adel— February 24, 2012 #
Very useful and superb example….Thanks a lot for having posted such a useful examples
Comment by John— March 7, 2012 #
thanks. expecting more examples from u
Comment by sankar— March 25, 2012 #