Wednesday, May 12, 2010

Interceptors in Spring MVC

These are different ways to configure Interceptors for Spring MVC based controllers.

1. If you have the URL / Controller mapping in xml you can define the interceptors as

<bean id="myService" class="com.test.services.MyService"/>

<bean id="myInterceptorOne" class="com.test.interceptors.FirstInterceptor">
<property name="myService" ref="myService" />
</bean>

<bean id="myInterceptorTwo" class="com.test.interceptors.SecondInterceptor" />

<bean id="defaultHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

<bean name="/home.action" class="com.test.controller.FirstController"/>

<bean id="someId" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="myInterceptorOne"/>
<ref bean="myInterceptorTwo"/>
</list>
</property>
<property name="urlMap">
<map>
<entry key="/index.action" value-ref="index"/>
</map>
</property>
</bean>

<bean id="index" class="com.test.controller.SecondController" />

I have defined 2 controllers. Only /index.action is intercepted by both the interceptors while /home.action is not.

2.Annotation-based controllers – you define the interceptors as –

<bean id="annotationMapper" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<ref bean="myInterceptorOne"/>
<ref bean="myInterceptorTwo"/>
</list>
</property>
</bean>

The interceptors are applied to all the configured controllers.

3. While using annotation based controllers, we can use "mvc:interceptors" tag which can be used to define interceptors and control how they are applied.

Like

<mvc:interceptor>
<mvc:mapping path="/index.action"/>
<mvc:mapping path="/home.action"/>
<bean class="com.test.interceptors.ThirdInterceptor" />
</mvc:interceptor>

Here ThirdInterceptor would be applied to /index.action and /home.action

<mvc:interceptor>
<mvc:mapping path="/index.action"/>
<bean class="com.test.interceptors.ThirdInterceptor" />
</mvc:interceptor>

<mvc:interceptor>
<mvc:mapping path="/index.action"/>
<bean class="com.test.interceptors.FirstInterceptor" />
</mvc:interceptor>

Here ThirdInterceptor and FirstInterceptor (in the defined order) would be applied to /index.action.

Saturday, January 9, 2010

Developing Modular Web Application using Slices on Spring DM 2 RC1

Configurations and Installations
1. Download Spring DM 2 RC1

2. Download Slices bundles from http://www.springsource.com/download/community?project=SpringSource%20Slices&nightly=yes

3. Copy the bundles and the plan file in the dist directory of the zip to dm Server's repository/usr directory.

4. Edit DM Server's config/com.springsource.kernel.userregion.properties file to add slice’s plan under initialArtifacts –
initialArtifacts=repository:plan/com.springsource.kernel.userregion.springdm, repository:plan/com.springsource.server.web, repository:plan/com.springsource.osgi.slices

5. Restart DM server and we are ready to deploy web application using slices.

Sample web application
With Slices, we can develop web application using multiple OSGi bundles; each bundle provides content for a distinct sub-portion of the application's URL space. Slices applications are arranged in a parent/child structure, with each application having one parent called “host” and zero or more children called “slices”.


The advantage using Slices is that any section (slice) of the web application can be hot deployed without restarting the complete web application.


Let us consider a Travel Agent web application that provides reservations for flight and hotel. So, the structure looks like –

The “Flight Reservation” slice can be accessed using URL “/flights” and the “Hotel Reservation” slice can be accessed using URL “/hotels”.

Now let us look into web.xml and MANIFEST.MF for host and slice bundles.

The host web bundle (TravelAgent) has the following web.xml which is similar to any other web application except that com.springsource.osgi.slices.core.SliceHostFilter is configured to route request to its slices.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<filter>
<filter-name>host-filter</filter-name>
<filter-class>com.springsource.osgi.slices.core.SliceHostFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>host-filter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>

<servlet>
<servlet-name>agent</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>agent</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>

</web-app>


Host web bundle (TravelAgent) has the following MANIFEST.MF

Manifest-Version: 1.0
Import-Bundle: org.springframework.context,org.springframework.context.support
Bundle-Version: 1.0.0
Bundle-Name: TravelAgent
Bundle-ManifestVersion: 2
Bundle-SymbolicName: TravelAgent
Web-ContextPath: /
Import-Package: javax.servlet.http;version="[2.5, 3.0)",org.springfram
ework.beans.factory.xml;version="[2.5.6.A,3.1)",org.springframework.w
eb.servlet;version="[2.5.6.A,3.1)",org.springframework.web.servlet.ha
ndler;version="[2.5.6.A,3.1)",org.springframework.web.servlet.mvc;ver
sion="[2.5.6.A,3.1)",org.springframework.web.servlet.view;version="[2
.5.6.A,3.1)",com.springsource.osgi.slices.core,org.springframework.st
ereotype,org.springframework.web.bind.annotation


Slices (FlightReservation and HotelReservation) has the following web.xml similar to any other web application

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<servlet>
<servlet-name>servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>servlet</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
</web-app>

Slice web bundle (FlightReservation) has the following MANIFEST.MF

Manifest-Version: 1.0
Bundle-Version: 1.0.0
Bundle-Name: FlightReservation
Bundle-ManifestVersion: 2
Bundle-SymbolicName: FlightReservation
Slice-ContextPath: /flights
Slice-Host: TravelAgent;version="[1.0.0, 2.0.0)"
Import-Bundle: org.springframework.context,org.springframework.context.support
Import-Package: javax.servlet.http;version="[2.5, 3.0)",org.springfram
ework.beans.factory.xml;version="[2.5.6.A,3.1)",org.springframework.w
eb.servlet;version="[2.5.6.A,3.1)",org.springframework.web.servlet.ha
ndler;version="[2.5.6.A,3.1)",org.springframework.web.servlet.mvc;ver
sion="[2.5.6.A,3.1)",org.springframework.web.servlet.view;version="[2
.5.6.A,3.1)",com.springsource.osgi.slices.core,org.springframework.st
ereotype,org.springframework.web.bind.annotation

Slice web bundle (HotelReservation) has the following MANIFEST.MF

Manifest-Version: 1.0
Bundle-Version: 1.0.0
Bundle-Name: HotelReservation
Bundle-ManifestVersion: 2
Bundle-SymbolicName: HotelReservation
Slice-ContextPath: /hotels
Slice-Host: TravelAgent;version="[1.0.0, 2.0.0)"
Import-Bundle: org.springframework.context,org.springframework.context.support
Import-Package: javax.servlet.http;version="[2.5, 3.0)",org.springfram
ework.beans.factory.xml;version="[2.5.6.A,3.1)",org.springframework.w
eb.servlet;version="[2.5.6.A,3.1)",org.springframework.web.servlet.ha
ndler;version="[2.5.6.A,3.1)",org.springframework.web.servlet.mvc;ver
sion="[2.5.6.A,3.1)",org.springframework.web.servlet.view;version="[2
.5.6.A,3.1)",com.springsource.osgi.slices.core,org.springframework.st
ereotype,org.springframework.web.bind.annotation

A slice uses “Slice-Host” manifest header to define host to which it attach to. URL portion that the slice handles is defined using “Slice-ContextPath” manifest header.

Here I have not covered any configurations / code related to Spring MVC, we will cover Spring MVC in later posts.

Sunday, January 3, 2010

Spring Security – Part 3 - Role based Method Invocation

In the previous part we looked how to secure the URL calls. Now let us look into how to secure the method invocation. Let us again have a properties file (methods.properties) that contains the method names and the roles that can invoke the methods. For example,

com.test.service.MyService.delete*=ROLE_USER,ROLE_MANAGER
com.test.service.MyService.createAccount=ROLE_ADMIN

You can also have this mapping defined in database or LDAP server. For simplicity we will have the mapping defined in a properties file.

Let us now define interface (IMyService) and implementation (MyService)

public interface IMyService {
public void deleteUserPreferences (String userName);
public void deleteUserLogs(String userName);
public void createAccount(String userName);
}

Now based on the above configurations ROLE_USER, ROLE_MANAGER would only be able to invoke the methods deleteUserPreferences and deleteUserLogs. ROLE_ADMIN would only be able to invoke method createAccount.

Now you can write MyService implementing the above interface.

Method security in enforced using a MethodSecurityInterceptor, which secures
MethodInvocations. The interceptor uses a MethodDefinitionSource instance to obtain the configuration attributes that apply to a particular method invocation. Since we would be reading the methods names and the corresponding roles from properties file, we would either –

1. Define our own implementation of MethodSecurityMetadataSource and implement all methods.

2. Or we can write a class extending MapBasedMethodSecurityMetadataSource. MapBasedMethodDefinitionSource is used to store configuration attributes keyed by method names (which can be wildcarded) and will be used internally when the attributes are defined in the application context using the <intercept-methods> or <protect-point> elements.

We will go ahead with point 2 as we just need to read the properties file, create Map<String, List<ConfigAttribute>> and call MapBasedMethodDefinitionSource’s constructor passing this map.

So, let us first define a bean PropertyHolder that would read the properties file and create Map<String, List<ConfigAttribute>>.

<bean id="propHolder" class="com.test.common.PropertyHolder">
<property name="methodProperties">
<util:properties location="classpath:methods.properties" />
</property>
</bean>

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.util.StringUtils;

public class PropertyHolder {
private Properties methodProperties;

public void setMethodProperties(Properties methodProperties) {
this.methodProperties = methodProperties;
}

public Properties getMethodProperties() {
return methodProperties;
}

public Map<String, List<ConfigAttribute>> getMethodMap() {
Map<String, List<ConfigAttribute>> methodMap = new HashMap<String, List<ConfigAttribute>>();
for (Iterator iter = methodProperties.keySet().iterator(); iter
.hasNext();) {
String name = (String) iter.next();
String value = methodProperties.getProperty(name);

String[] tokens = StringUtils
.commaDelimitedListToStringArray(value);
List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(
tokens.length);

for (String token : tokens) {
attributes.add(new SecurityConfig(token));
}

methodMap.put(name, attributes);
}

return methodMap;
}
}

Now let us configure a MethodSecurityIterceptor in our application context

<bean id="methodSecurityInterceptor" class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource" ref="mySecurityMetadataSource"/>
</bean>

We have already configured authenticationManager and accessDecisionManager in previous parts.

Let us now configure mySecurityMetadataSource.

<bean id=" mySecurityMetadataSource" class="com.test.common.MySecurityMetadataSource">
<constructor-arg name="propHolder" ref="propHolder"/>
</bean>


Now let us write the bean MySecurityMetadataSource that extends MapBasedMethodSecurityMetadataSource.

Import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource;

public class MySecurityMetadataSource extends
MapBasedMethodSecurityMetadataSource {

public MyMethodSecurityFilterNew(PropertyHolder propHolder) {
super(propHolder.getMethodMap());
}
}

Now define the beans MyService in application context that we want to secure and define another bean org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator to create a proxy for MyService object so that an authorization check may be applied for every invocation on the object.

<bean id=”myService” class=”com.test.service.MyService” />

<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>methodSecurityInterceptor</value>
</list>
</property>
<property name="beanNames">
<list>
<value> myService</value>
</list>
</property>
</bean>

Spring Security – Part 2 – Custom securityMetadataSource

Now let us look into filterSecurityInterceptor. This filter authorizes web requests based on URL patterns. Instead of having the URLs configured in application context, we will have URLs in the properties file. We can even have these URLs configured in database or LDAP server. Here let us have properties file having URLs and the corresponding roles. The properties file looks like –

/admin/**=ROLE_ADMIN,ROLE_MANAGER
/admin/userPreference/**=ROLE_MANAGER
/admin/userPreference/updatePreference.action=ROLE_USER,ROLE_ASSOCIATE
/admin/userPreference/deletePreference.action=ROLE_USER

Here ROLE_ADMIN, ROLE_MANAGER are given complete access to all admin URLs, ROLE_MANAGER is given access to all URLs having /admin/userPreference/ and ROLE_USER, ROLE_ASSOCIATE are given page / action level access.

Now let us define filterSecurityInterceptor.

<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource" ref="mySecureResourceFilter"/>
</bean>

We have already configured authenticationManager in the previous part. Let us now define accessDecisionManager.

<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.RoleVoter" />
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
</list>
</property>
</bean>

We will look into it in more detail in next part.

Now let us write custom securityMetadataSource – mySecureResourceFilter

Let us define the bean first -


<bean id="mySecureResourceFilter" class="com.test.common.MySecureResourceFilter">
<property name="urlProperties">
<util:properties location="classpath:urls.properties" />
</property>
</bean>

Given the requested URL, we have to find all the roles authorized to access it. Like if requested URL is /admin/changePreference/deletePreference.action, we will get the roles from the properties file which can access the following URLs –

/admin/changePreference/deletePreference.action
/admin/changePreference/**
/admin/**

All this is implemented in getAttributes (..) method defined below which returns the name of the Authorities (or Roles) that are allowed to access requested URL.

import java.util.Collection;
import java.util.Properties;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;

import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

public class MySecureResourceFilter implements
FilterInvocationSecurityMetadataSource {

private Properties urlProperties;

public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}

public Collection<ConfigAttribute> getAttributes(Object filter)
throws IllegalArgumentException {
FilterInvocation filterInvocation = (FilterInvocation) filter;
String url = filterInvocation.getRequestUrl();

//get the roles for requested page from the property file
String urlPropsValue = urlProperties.getProperty(url);
StringBuilder rolesStringBuilder = new StringBuilder();
if(urlPropsValue != null) {
rolesStringBuilder.append(urlPropsValue).append(",");
}

if(!url.endsWith("/")) {
int lastSlashIndex = url.lastIndexOf("/");
url = url.substring(0, lastSlashIndex + 1);
}


String [] urlParts = url.split("/");

StringBuilder urlBuilder = new StringBuilder();
for (String urlPart : urlParts) {
if(urlPart.trim().length() == 0) {
continue;
}
urlBuilder.append("/").append(urlPart);
urlPropsValue = urlProperties.getProperty(urlBuilder.toString() + "/**");

if(urlPropsValue != null) {
rolesStringBuilder.append(urlPropsValue).append(",");
}
}

if(rolesStringBuilder.toString().endsWith(",")) {
rolesStringBuilder.deleteCharAt(rolesStringBuilder.length()-1);
}


if(rolesStringBuilder.length() == 0) {
return null;
}

return SecurityConfig.createListFromCommaDelimitedString(rolesStringBuilder.toString());
}

public boolean supports(Class<?> arg0) {
return true;
}

public void setUrlProperties(Properties urlProperties) {
this.urlProperties = urlProperties;
}

public Properties getUrlProperties() {
return urlProperties;
}

}

That’s it. In next part we will look into role based Method Invocation.

Saturday, January 2, 2010

Spring Security – Part 1 – Necessary Configuration and Custom UserDetailsService

Here we will look into –

1. The necessary configurations required
2. Write custom userDetailsService reading user name, password and roles from properties file

We will not use default security namespace configuration instead we will define beans and configure our own FilterChainProxy.

Add the schema declaration to the application context file –
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:util="http://www.springframework.org/schema/util">

. . . . . .

</beans>

web.xml configuration
The first thing we need to do is add the following filter declaration to web.xml file:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Now let us define and configure springSecurityFilterChain. It delegates request to a chain of Spring-managed filters. Since we would be customizing the Spring Security’s behavior we would be manually configuring the filters instead of using the default configuration.

For a web application we need to configure the following filters in the mentioned order –

1. HttpSessionContextIntegrationFilter – It queries HTTPSession to retrieve SecurityContext and populates SecurityContextHolder for the duration of web request. At the end of the web request, any updates made to the SecurityContextHolder will be persisted back to the HttpSession by this filter.

2. LogoutFilter – It clears SecurityContextHolder when logout is requested.

3. AuthenticationProcessingFilter – It puts Authentication into the SecurityContext on login request.

4. ExceptionTranslationFilter – It converts SpringSecurity exceptions into HTTP response or HTTP redirect.

5. FilterSecurityInterceptor – It authorizes web requests based on URL patterns.

Now let us define springSecurityFilterChain in application context file.
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/**" filters=" httpSessionContextFilter,
logoutFilter,
authenticationProcessingFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
</sec:filter-chain-map>
</bean>


Let us now configure the filters –

1. httpSessionContextFilter
Let us use the default configuration for httpSessionContextFilter.

<bean id="httpSessionContextFilter" class="org.springframework.security.web.context.HttpSessionContextIntegrationFilter"/>

2. exceptionTranslationFilter
It handles any AccessDeniedException and AuthenticationException thrown within the filter chain.
If an AuthenticationException is detected, the filter will launch the authenticationEntryPoint. If an AccessDeniedException is detected, the filter will determine whether or not the user is an anonymous user. If they are an anonymous user, the authenticationEntryPoint will be launched. If they are not an anonymous user, the filter will delegate to the AccessDeniedHandler.
Let us define the beans now. Login form url is set as “/login.jsp” for authenticationEntryPoint and error page is set as “/error/jsp” for access denied handler.

<bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler" />
</bean>


<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint" >
<property name="loginFormUrl" value="/login.jsp" />
</bean>

<bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/error401.jsp"/>
</bean>


3. authenticationProcessingFilter –
User information can be stored in database, in LDAP server or in properties file. Let us have user name, password and roles defined in a properties file (user.properties). We will write custom implementation of Spring Security's UserDetailsService - MyUserDetailService

user.properties has the following entries –
pranav=123123,ROLE_ADMIN
pranjal=321321,ROLE_MANAGER
sudheer=abcabc,ROLE_USER

Now let us write MyUserDetailService that implements UserDetailsService implementing method loadUserByUsername. This would load the user information from the user.properties creating UserDetails.

import java.util.Properties;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.memory.UserAttribute;
import org.springframework.security.core.userdetails.memory.UserAttributeEditor;

public class MyUserDetailService implements UserDetailsService {
private Properties userProperties;

public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {

String userPropsValue = userProperties.getProperty(username);
if (userPropsValue == null) {
throw new UsernameNotFoundException(username
+ "User does not exist");
}

UserAttributeEditor configAttribEd = new UserAttributeEditor();
configAttribEd.setAsText(userPropsValue);

UserAttribute userAttributes = (UserAttribute) configAttribEd
.getValue();

return new User(username, userAttributes.getPassword(), userAttributes
.isEnabled(), true, true, true, userAttributes.getAuthorities());
}

public void setUserProperties(Properties userProperties) {
this.userProperties = userProperties;
}

public Properties getUserProperties() {
return userProperties;
}

}

Now let us define the beans. Post successful authentication user would be shown the requested URL or the default target url “/home.action” set to authenticationSuccessHandler.
<bean id="authenticationProcessingFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler" ref="authenticationSuccessHandler"/>
</bean>

<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref bean="authenticationProvider"/>
</list>
</property>
</bean>

<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailService"/>
</bean>

<bean id="userDetailService" class="com.test.common.MyUserDetailService">
<property name="userProperties">
<util:properties location="classpath:users.properties" />
</property>
</bean>

<bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/home.action"/>
</bean>


We will look into filterSecurityInterceptor and logoutFilter in next part.




Sunday, August 2, 2009

Spring Web Service

Spring-WS supports the contract-first development style that means we start with the WSDL contract and use Java to implement said contract.

We will write a sample code to demonstrate how to write contract-first Web services, that is, developing web services that start with the XML Schema/WSDL contract first followed by the Java code second.

Let us take an example where any service provider maintain user database and provides services for updating user information, providing user information (user profile) and authenticating user. Let say user profile has login id, first name, last name, email and address. We want to provide services like AuthenticateUser, UpdateUserProfile, GetUserProfile.

Let us follow the following steps -

1. So, first step would be to create XSD (let us name it as user.xsd). Go through the below schema, it is self-explanatory

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.mysite.com/user"
xmlns:tns="http://www.mysite.com/user"
elementFormDefault="qualified">

<element name="AuthenticateUser">
<complexType>
<sequence>
<element name="LoginId" type="string" />
<element name="Password" type="string" />
</sequence>
</complexType>
</element>


<element name="UpdateUserProfile">
<complexType>
<all>
<element name="Profile" type="tns:UserProfile" />
<element name="LoginId" type="string" />
</all>
</complexType>
</element>

<element name="GetUserProfile">
<complexType>
<sequence>
<element name="LoginId" type="string" />
</sequence>
</complexType>
</element>

<complexType name="UserProfile">
<sequence>
<element name="loginId" type="string" />
<element name="firstName" type="string" />
<element name="lastName" type="string" />
<element name="emailId" type="string" />
<element name="address" type="string" />
</sequence>
</complexType>

<element name="GetUserProfileResponse" type="tns:UserProfile"/>

<element name="AuthenticateUserResponse" type="int" />

<element name="UpdateProfileResponse" type="int" />

</schema>

2. Now that the schema is created, we will generate supporting classes. We will use JAXB to generate the classes. Download JAXB and unzip it. Use the following command to generate the classes -

%JAXB_HOME%\bin\xjc.bat -d . -p test.schema ./config/user.xsd

The target package is test.schema and schema is under config directory. This would generate the classes based on above user schema.

OR we can use the following build.xml using "ant generate"

<?xml version="1.0"?>
<project name="spring-ws-user-ws-server" basedir="." default="default">
<path id="classpath">
<fileset dir="C:\jaxb-ri-20090206\lib">
<include name="**/*.jar"/>
</fileset>
</path>


<target name="generate">

<taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask" classpathref="classpath" />

<xjc destdir="./" package="test.schema" >
<schema dir="./config" includes="user.xsd"/>
</xjc>

</target>

</project>


3. Implementing the Endpoint - In Spring-WS, we implement Endpoints to handle the incoming XML messages. There are two flavors of endpoints: message endpoints and payload endpoints. Message endpoints give access to the entire XML message, including SOAP headers. Typically, the endpoint will only be interested in the payload of the message that is the contents of the SOAP body. In that case, creating a payload endpoint makes more sense.

To handle XML we can use various marshalling techniques like JAXB, Castor,XStream etc apart from JDOM, DOM, dom4j, XOM, SAX,and StAX. We will use JAXB marshalling technique.

package test.ws;

import javax.xml.bind.JAXBElement;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;

import test.schema.AuthenticateUser;
import test.schema.GetUserProfile;
import test.schema.ObjectFactory;
import test.schema.UpdateUserProfile;
import test.schema.UserProfile;
import test.service.IUserService;

@Endpoint
public class UserEndPoint {

IUserService userService;

private ObjectFactory objectFactory = new ObjectFactory();

public UserEndPoint(IUserService userService) {
this.userService = userService;
}

@PayloadRoot(localPart = "AuthenticateUser", namespace = "http://www.mysite.com/user")
public JAXBElement<Integer> authenticateUser(AuthenticateUser request) {
System.out.println(request.getLoginId() + " " + request.getPassword());
int result = userService.authenticateUser(request.getLoginId(), request
.getPassword());
return objectFactory.createAuthenticateUserResponse(result);
}

@PayloadRoot(localPart = "UpdateUserProfile", namespace = "http://www.mysite.com/user")
public JAXBElement<Integer> updateUserProfile(UpdateUserProfile request) {
int result = userService.updateUserProfile(request.getLoginId(),
request.getProfile());
return objectFactory.createUpdateProfileResponse(result);
}

@PayloadRoot(localPart = "GetUserProfile", namespace = "http://www.mysite.com/user")
public JAXBElement<UserProfile> getUserProfile(GetUserProfile request) {
UserProfile userProfile = userService.getUserProfile(request
.getLoginId());
return objectFactory.createGetUserProfileResponse(userProfile);
}
}


4. We have injected userService to above end point, the service interface looks like -

package test.service;

import test.schema.UserProfile;

public interface IUserService {
public int authenticateUser(String loginId, String password);

public int updateUserProfile(String loginId, UserProfile profile);

public UserProfile getUserProfile(String loginId);
}

5. Routing the Message to the Endpoint - I have used annotation to configure Endpoint and PayloadRoot. See @Endpoint and @PayloadRoot. let us define applicationContext-ws.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"
xmlns:sws="http://www.springframework.org/schema/web-services"
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
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-1.5.xsd">


<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
<property name="xsd" value="/WEB-INF/user.xsd"/>
</bean>

<bean id="userEndPoint" class="test.ws.UserEndPoint">
<constructor-arg ref="userService"/>
</bean>

<oxm:jaxb2-marshaller id="marshaller" contextPath="test.schema"/>

<bean id="annotationMapping"
class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
</bean>

<sws:marshalling-endpoints/>
</beans>

Also we define applicationContext.xml where we define the userService bean

<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<context:annotation-config/>

<bean id="userService" class="test.service.UserService" />
</beans>


6. Publishing the WSDL - we don't have to write a WSDL; Spring-WS can generate itself. This is how ws-servlet.xml looks like

<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">

<bean id="user" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema" ref="schema"/>
<property name="portTypeName" value="User"/>
<property name="locationUri" value="/userService"/>
</bean>

</beans>

- The bean id determines the URL where the WSDL can be retrieved. Here the bean id is user that means the WSDL can be retrieved as user.wsdl in the servlet context. The full URL will be like http://localhost:8080/UserService/user.wsdl.

- The schema property refers to the user schema defined in point 1. We will place the schema in the WEB-INF directory of the application.

- We will define the WSDL port type to be User

- We set the location where the service can be reached: /UserService/

7. Now let us write web.xml, which looks like

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Spring WS</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-ws.xml
/WEB-INF/applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

<mime-mapping>
<extension>xsd</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

8. Now let us deploy the web application.
- Copy user.xsd, web.xml, applicationContext-ws.xml, applicationContext.xml, ws-servlet.xml under /WEB-INF/
- Copy all required libs under WEB-INF/lib - spring-ws-1.5.6-all.jar, spring-ws-core-1.5.6.jar, spring-webmvc.jar, spring-oxm-1.5.6.jar, spring.jar, commons-logging-1.0.4.jar, wsdl4j-1.6.1.jar, XmlSchema-1.4.3.jar
- Copy the UserEndPoint class, all supporting classes and service classes under WEB-INF/class and restart tomcat


Create WAR and deploy under Tomcat. We have successfully deployed the webservice. Check the WSDL using http://localhost:8080/UserService/user.wsdl

We will next write the dummy client. But after a short break :)