Monday, June 15, 2009

Secured Web Service Using Spring Security

At work, I came across a problem of adding basic HTTP authentication to web service. The following configuration for Spring security would work:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:s="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
classpath:/schemas/spring-beans-2.5.xsd
http://www.springframework.org/schema/security
classpath:/schemas/spring-security-2.5.xsd">

<s:http auto-config="false">
<s:form-login />
<s:anonymous />
<s:http-basic />
<s:logout />


<!-- List of web services to intercept for security check -->

<s:intercept-url pattern="/services/OrderService"
method="POST" access="ROLE_APPL_ORDER_SYSTEM" />

<!--
Essentially no security for all other URLs since everything is
granted to Anonymous
-->
<s:intercept-url pattern="/**"
access="IS_AUTHENTICATED_ANONYMOUSLY" />

</s:http>


<!-- LDAP authentication provider configuration -->
<s:ldap-server url="ldap://ldap.myserver.org:389" />
<s:ldap-authentication-provider
user-search-base="OU=People,O=javaidiot.org" user-search-filter="uid={0}"
group-search-base="OU=Groups,O=javaidiot.org" group-search-filter="uniquemember={0}"
group-role-attribute="CN" />

</beans>

Note: Authentication and Authorization is being done above, the user account has to be existed in the LDAP server with the role APPL_ORDER_SYSTEM. In the configuration, pay attention to the "ROLE_" prepended to the actual role because I think it is the convention required.

In your Web Service client, you can do this:

((BindingProvider) port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "username");
((BindingProvider) port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "password");

Here is the remaining context setup for Spring webservice:

<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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
classpath:/schemas/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
classpath:/schemas/spring-aop-2.5.xsd
http://jax-ws.dev.java.net/spring/core
classpath:/schemas/core.xsd
http://jax-ws.dev.java.net/spring/servlet
classpath:/schemas/servlet.xsd">

<!-- OrderService -->
<wss:binding url="/services/OrderService">
<wss:service>
<ws:service bean="#orderServiceImpl">
</ws:service>
</wss:service>
</wss:binding>

<bean id="orderServiceImpl"
class="org.javaidiot.impl.OrderServiceImpl" />

</beans>

Here is the web.xml:

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>
<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>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

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


<!-- OrderService -->
<servlet>
<servlet-name>OrderService</servlet-name>
<display-name>OrderService</display-name>
<description>Order Service</description>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>OrderService</servlet-name>
<url-pattern>/services/OrderService</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

</web-app>


To enable SSL on Tomcat, you will need a key-pairs. You can either buy, get, or use Java keytool to get a self-generated one. Then go to the server.xml to adapt the following line:

<Connector SSLEnabled="true" clientAuth="false" keystoreFile="C:/my_identity.jks"
keystorePass="changeit" maxThreads="150" port="8443" protocol="HTTP/1.1" scheme="https" secure="true"
sslProtocol="TLS" truststoreFile="C:/my_client_trust_to_other_server.jks" truststorePass="changeit"/>

Notice your webservice client now will need the following VM arguments to start:

-Djavax.net.ssl.trustStore="C:\my_client_trust.jks"
-Djavax.net.ssl.trustStorePassword=changeit

Friday, June 12, 2009

A Custom Distributed Lock for Spring Application

I will try to demonstrate how two Spring's applications that can share the same lock.

For example:

When Application 1 and 2 call the method lock("mycheese", 20000L), one of the applications will be able to get the lock, the other will not. The example lock would expire in 20000 milliseconds or can be removed earlier by calling the method unlock("mycheese").

To begin, I will show the Application 1 & 2 contexts:

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


<bean id="contextApplicationContextProvider" class="org.javaidot.lock.ApplicationContextProvider"></bean>

<bean id="lockManager" class="org.javaidot.lock.LockManager">
<property name="memberId" value="1" />
</bean>

<bean id="scheduledTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<!-- wait 1 seconds before starting repeated execution -->
<property name="delay" value="1000"/>
<!-- run every 2 seconds -->
<property name="period" value="2000"/>
<property name="timerTask" ref="lockManager"/>
</bean>

<bean id="timeFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<ref bean="scheduledTask"/>
</list>
</property>
<property name="daemon" value="false"/>
</bean>

<bean id="lockServiceImpl" class="org.javaidot.lock.LockServiceImpl"/>

<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="LockService"/>
<property name="service" ref="lockServiceImpl"/>
<property name="serviceInterface" value="org.javaidot.lock.LockService"/>
<property name="registryPort" value="1236"/>
</bean>

<bean id="remoteLockServiceImpl" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:1235/LockService"/>
<property name="serviceInterface" value="org.javaidot.lock.LockService"/>
<property name="refreshStubOnConnectFailure" value="true"/>
<property name="lookupStubOnStartup" value="false"/>
<!-- <property name="cacheStub" value="true"/>-->
</bean>
</beans>



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


<bean id="contextApplicationContextProvider" class="org.javaidot.lock.ApplicationContextProvider"></bean>

<bean id="lockManager" class="org.javaidot.lock.LockManager">
<property name="memberId" value="2" />
</bean>

<bean id="scheduledTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<!-- wait 1 seconds before starting repeated execution -->
<property name="delay" value="1000"/>
<!-- property name="fixedRate" value="true"/-->
<!-- run every 2 seconds -->
<property name="period" value="2000"/>
<property name="timerTask" ref="lockManager"/>
</bean>

<bean id="timeFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<ref bean="scheduledTask"/>
</list>
</property>
<property name="daemon" value="false"/>
</bean>

<bean id="lockServiceImpl" class="org.javaidot.lock.LockServiceImpl"/>

<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="LockService"/>
<property name="service" ref="lockServiceImpl"/>
<property name="serviceInterface" value="org.javaidot.lock.LockService"/>
<property name="registryPort" value="1235"/>
</bean>

<bean id="remoteLockServiceImpl" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:1236/LockService"/>
<property name="serviceInterface" value="org.javaidot.lock.LockService"/>
<property name="refreshStubOnConnectFailure" value="true"/>
<property name="lookupStubOnStartup" value="false"/>
<!-- <property name="cacheStub" value="true"/>-->
</bean>

</beans>


The summary for the contexts above:
  1. ApplicationContextProvider implements Spring's ApplicationConextAware to hold the ApplicationContext. The purpose to hold the original spring context. All the credits here goes to the author Siegried Bolz.
  2. LockManager hold a dictionary of locks to be managed, it also implements Java TimerTask.
  3. ScheduledTimerTask is used to kick of the run method in LockManager to clean up expired locks.
  4. LockServiceImpl implement the lock and unlock method for RMI client.
  5. RmiServiceExporter is the RMI Server for RMI Client
  6. RmiProxyFactoryBean is the RMI client to the other application RMI Server.


Below are the source listings of the entire application, the Main1.java and Main2.java are the test classes:


file: AppContext.java


package org.javaidiot.lock;

import org.springframework.context.ApplicationContext;

/**
* This class provides application-wide access to the Spring ApplicationContext.
* The ApplicationContext is injected by the class "ApplicationContextProvider".
*
* @author Siegfried Bolz
*/

public class AppContext {

private static ApplicationContext ctx;

/**
* Injected from the class "ApplicationContextProvider" which is automatically
* loaded during Spring-Initialization.
*/

public static void setApplicationContext(ApplicationContext applicationContext) {
ctx = applicationContext;
}

/**
* Get access to the Spring ApplicationContext from everywhere in your Application.
*
* @return
*/

public static ApplicationContext getApplicationContext() {
return ctx;
}
}



file: ApplicationContextProvider.java


package org.javaidiot.lock;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* This class provides an application-wide access to the
* Spring ApplicationContext! The ApplicationContext is
* injected in a static method of the class "AppContext".
*
* Use AppContext.getApplicationContext() to get access
* to all Spring Beans.
*
* @author Siegfried Bolz
*/
public class ApplicationContextProvider implements ApplicationContextAware {

public void setApplicationContext(ApplicationContext ctx) throws BeansException {
// Wiring the ApplicationContext into a static method
AppContext.setApplicationContext(ctx);
}
}



file: ILock.java

package org.javaidiot.lock;

public interface ILock {

public abstract String getName();

public abstract void setName(String name);

public abstract Long getExpirationInMs();

public abstract void setExpirationInMs(Long expirationInMs);

public abstract Long getLockTimeInMs();

public abstract void setLockTimeInMs(Long lockTimeInMs);

public abstract Long getRequesterId();

public abstract void setRequesterId(Long requesterId);

}


file: Lock.java


package org.javaidiot.lock;

import java.io.Serializable;

public class Lock implements Serializable, ILock {


private static final long serialVersionUID = 6437744860401621455L;

private String name;
private Long expirationInMs;
private Long lockTimeInMs;
private Long requesterId;

public Lock(String name, Long expirationInMs, Long memberId) {
super();
this.name = name;
this.expirationInMs = expirationInMs;
this.requesterId = memberId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Long getExpirationInMs() {
return expirationInMs;
}

public void setExpirationInMs(Long expirationInMs) {
this.expirationInMs = expirationInMs;
}

public Long getLockTimeInMs() {
return lockTimeInMs;
}

public void setLockTimeInMs(Long lockTimeInMs) {
this.lockTimeInMs = lockTimeInMs;
}

public Long getRequesterId() {
return requesterId;
}

public void setRequesterId(Long requesterId) {
this.requesterId = requesterId;
}

public String toString() {
return "name=" this.name
", lockTimeInMs=" this.lockTimeInMs
", expirationInMs=" this.expirationInMs
", requesterId=" this.requesterId;
}

}

file: LockManager.java


package org.javaidiot.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
import org.springframework.context.ApplicationContext;

public class LockManager extends TimerTask {

private ApplicationContext ctx;
private Long memberId;
private Map <String, Lock> locks = new HashMap <String, Lock> ();

public LockManager() {
ctx = AppContext.getApplicationContext();
}

public Long getMemberId() {
return memberId;
}

public void setMemberId(Long memberId) {
this.memberId = memberId;
}

public boolean lock(String name, Long timeToLiveInMs) {

Boolean bool = false;

Long expirationInMs = timeToLiveInMs System.currentTimeMillis();

Lock lock = new Lock(name, expirationInMs, memberId);

boolean localBool = createLockLocally(lock);

System.out.println("localBool:" localBool);


boolean remoteBool = false;

try {
remoteBool = createLockRemotely(lock);
System.out.println("remoteBool:" remoteBool);
} catch (RuntimeException e) {
System.out.println("Remote Lock service is down??");
}

if ((localBool == true) && (remoteBool == true)) {
bool = true;
}

return bool;
}

public void unlock(String name) {

ILock lock = locks.get(name);

removeLockLocally(lock);

try {
removeLockRemotely(lock);
} catch (Exception e) {
System.out.println("remote lock service is down!");
}
}

public void showLocks() {

System.out.println("\nList of Locks:");
System.out.println("------------------");
Set <String> keys = locks.keySet();
for (String key: keys) {
System.out.println(locks.get(key));
}
}

public boolean createLockRemotely(Lock lock) {
System.out.println("creating lock remotely");

LockService lockService = (LockService) ctx.getBean("remoteLockServiceImpl");
return lockService.createLock(lock);
}

public boolean createLockLocally(Lock lock) {
System.out.println("creating lock locally");

if (locks.containsKey(lock.getName())) {
return false;
}
locks.put(lock.getName(), lock);
System.out.println("created lock locally: " lock);
return true;
}

public void removeLockLocally(ILock lock) {
System.out.println("removing lock locally");

locks.remove(lock.getName());
System.out.println("removed lock locally: " lock.getName());
}

public void removeLockRemotely(ILock lock) {
System.out.println("removing lock remotely");

LockService lockService = (LockService) ctx.getBean("remoteLockServiceImpl");
lockService.removeLock(lock);
}

@Override
public void run() {

showLocks();

Set <String> keys = locks.keySet();
for (String key: keys) {
ILock lock = locks.get(key);
if (System.currentTimeMillis() > lock.getExpirationInMs())
locks.remove(key);
}
}

public Map<String, Lock> getLocks() {
return locks;
}

public void setLocks(Map<String, Lock> locks) {
this.locks = locks;
}

}

file: LockService.java


package org.javaidiot.lock;

public interface LockService {
public boolean createLock(Lock lock);
public void removeLock(ILock lock);
}

file: LockServiceImpl.java


package org.javaidiot.lock;


import org.springframework.context.ApplicationContext;

public class LockServiceImpl implements LockService {

private ApplicationContext ctx;

public LockServiceImpl() {
ctx = AppContext.getApplicationContext();
}
@Override
public boolean createLock(Lock lock) {

boolean ret = false;
LockManager lockManager = (LockManager) ctx.getBean("lockManager");

Long memberId = lockManager.getMemberId();

System.out.println("Processing createLock from requester " lock.getRequesterId());
if (memberId != lock.getRequesterId()) {
ret = lockManager.createLockLocally(lock);
}
return ret;
}
@Override
public void removeLock(ILock lock) {

LockManager lockManager = (LockManager) ctx.getBean("lockManager");
Long memberId = lockManager.getMemberId();

System.out.println("Processing removeLock from requester " lock.getRequesterId());
if (memberId != lock.getRequesterId()) {
lockManager.removeLockLocally(lock);
}
}
}

file: Main1.java


package org.javaidiot.lock;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main1 {

public static void main(String[] args) throws Exception {

ApplicationContext ctx = new ClassPathXmlApplicationContext("context1.xml");

LockManager lockManager = (LockManager) ctx.getBean("lockManager");

boolean bool = lockManager.lock("mycheese", 40000L);

System.out.println("Main1: locked:" bool);

bool = lockManager.lock("mycheese", 40000L);
System.out.println("Main1: locked:" bool);

Thread.sleep(10000);

lockManager.unlock("mycheese");
}
}

file: Main1.java


package org.javaidiot.lock;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main2 {

public static void main(String[] args) throws Exception {

ApplicationContext ctx = new ClassPathXmlApplicationContext("context2.xml");

LockManager lockManager = (LockManager) ctx.getBean("lockManager");

Thread.sleep(20000);

System.out.println("Main2: getLock:" lockManager.lock("mycheese", 20000L));
}

}