Skip to main content

Using Spring Security to enforce authentication and authorization on Spring Remoting Services Invoked from a Java SE client...

Posted by kalali on March 18, 2010 at 6:41 AM PDT

Spring framework is one of the biggest and the most comprehensive frameworks Java Community can utilize to cover most of the  end to end requirement of a software system when it come to implementation.
Spring Security and Spring Remoting are two important parts of the framework which covers security in a descriptive way and let us have remote invocation of a spring bean methods using a local proxy.

In this entry I will show you how we can use spring security to secure a spring bean exposed over HTTP and invoke its secured methods from an standalone client. In our sample we are developing an Spring service which returns the list of roles which are assigned to that currently authenticated user. I will develop a simple web application congaing an secured Spring service then I will develop a simple Java SE client to invoke that secured service.

To develop the service we need to have a service interface which is as follow:

 package springhttp;

public interface SecurityServiceIF {

public String[] getRoles();

}


Then we need to have an implementation of this interface which will do the actual job of extracting the roles for the currently authenticated user.
 

package springhttp;

public class SecurityService implements SecurityServiceIF {



public String[] getRoles() {

Collection<GrantedAuthority> col = SecurityContextHolder.getContext().getAuthentication().getAuthorities();

String[] roles = new String[col.size()];

int i = 0;

for (Iterator<GrantedAuthority> it = col.iterator(); it.hasNext();) {



GrantedAuthority grantedAuthority = it.next();

roles[i] = grantedAuthority.getAuthority();

i++;

}

return roles;

}

}

Now we should define this remote service in a descriptor file. Here we will use remote-servlet.xml file to describe the service. The file can be placed inside the WEB-INF of the web application. The file content is as follow:
 

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

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>



<bean name="/securityService"

class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">

<property name="serviceInterface" value="springhttp.SecurityServiceIF" />

<property name="service" ref="securityService" />

</bean>



</beans>

We are simply using /securityService as the relative URL to expose our SecurityService implementation.

The next configuration file is the Spring application context in which we define all of our beans, beans weaving and configurations. The file name is applicationContext.xml and it is located inside the WEB-INF directory.

 



<?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:p="http://www.springframework.org/schema/p"

xmlns:util="http://www.springframework.org/schema/util"

xmlns:security="http://www.springframework.org/schema/security"

xsi:schemaLocation="http://www.springframework.org/schema/beans      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd />
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd   http://www.springframework.org/schema/security  
http://www.springframework.org/schema/security/spring-security-3.0.xsd ">



<bean id="securityService" class="springhttp.SecurityService">

</bean>



<security:http realm="SecRemoting">

<security:http-basic/>

<security:intercept-url pattern="/securityService" access="ROLE_ADMIN" />

</security:http>



<security:authentication-manager alias="authenticationManager">

<security:authentication-provider>

<security:user-service id="uds">

<security:user name="Jami" password="Jami"

authorities="ROLE_USER, ROLE_MANAGER" />

<security:user name="bob" password="bob"

authorities="ROLE_USER,ROLE_ADMIN" />

</security:user-service>

</security:authentication-provider>

</security:authentication-manager>





<bean id="digestProcessingFilter"

class="org.springframework.security.web.authentication.www.DigestAuthenticationFilter">

<property name="userDetailsService" ref="uds" />

<property name="authenticationEntryPoint"

ref="digestProcessingFilterEntryPoint" />

</bean>



<bean id="digestProcessingFilterEntryPoint"

class="org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">

<property name="realmName" value="ThisIsTheDigestRealm" />

<property name="key" value="acegi" />

<property name="nonceValiditySeconds" value="10" />

</bean>



<bean id="springSecurityFilterChain"

class="org.springframework.security.web.FilterChainProxy">

<security:filter-chain-map path-type="ant">

<security:filter-chain pattern="/**"

filters="httpSessionContextIntegrationFilter,digestProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor" />

</security:filter-chain-map>

</bean>



<bean id="httpSessionContextIntegrationFilter"

class="org.springframework.security.web.context.HttpSessionContextIntegrationFilter" />

<bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">

<property name="decisionVoters">

<list>



<bean class="org.springframework.security.access.vote.RoleVoter" />

<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />

</list>

</property>

</bean>



<bean id="exceptionTranslationFilter"  class="org.springframework.security.web.access.ExceptionTranslationFilter">

<property name="authenticationEntryPoint"

ref="digestProcessingFilterEntryPoint" />

</bean>

</beans>


Most of the above code is default spring configurations except for the following parts which I am going to explain in more details. The first snippet is defining the bean itself:
 

<bean id="securityService" class="springhttp.SecurityService">

</bean>


The second part is when we specify the security restrictions on the application itself. We are instructing the spring security to only allow an specific role to invoke the securityService
 

<security:http realm="SecRemoting">

<security:http-basic/>

<security:intercept-url pattern="/securityService" access="ROLE_ADMIN" />

</security:http>


The third part is when we define the identity repository where our users and role assignment are stored.
 

<security:authentication-manager alias="authenticationManager">

<security:authentication-provider>

<security:user-service id="uds">

<security:user name="jimi" password="jimi"

authorities="ROLE_USER, ROLE_MANAGER" />

<security:user name="bob" password="bob"

authorities="ROLE_USER,ROLE_ADMIN" />

</security:user-service>

</security:authentication-provider>

</security:authentication-manager>




As you can see we are using the simple in memory user service, you may configure the an LDAP, JDBC, or a custom user service in the production environment. All other parts of the applicationContext.xml are Spring security filter definition. You can find explanation about any of then in Spring Security Documentation or by googling for it. To enforce security restrictions on the SercurityService for any local invocation we can simply change the first disucssed snippet as follow to allow ROLE_ADMIN and ROLE_MANAGER to invoke the service locall.

 <bean id="securityService" class="springhttp.SecurityService">

<security:intercept-methods>

<security:protect

method="springhttp.SecurityService.getRoles" access="ROLE_MANAGER" />

</security:intercept-methods>

</bean>

Now that we have all Spring configuration files in place, we need to add some elements to web.xml in order to let spring framework kick start. Following changes need to be included in the web.xml file.
 

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

/WEB-INF/applicationContext.xml

</param-value>

</context-param>

<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>

<servlet>

<servlet-name>remote</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>2</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>remote</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>






Now that the server part is over, we can develop the client application to invoke this service we developed. The code for the client class is as follow:
 

package client;

public class Main {



public static void main(final String[] arguments) {

final ApplicationContext context =

new ClassPathXmlApplicationContext(

"client/spring-http-client-config.xml");

String user = "bob";

String pw = "bob";

SecurityContextImpl sc = new SecurityContextImpl();

Authentication auth = new UsernamePasswordAuthenticationToken(user,

pw);



sc.setAuthentication(auth);

SecurityContextHolder.setContext(sc);



String[] roles = ((SecurityServiceIF) context.getBean("securityService")).getRoles();

for (int i = 0; i < roles.length; i++) {

System.out.println("Role:" + roles[i]);

}



}

}




As you can see we are initializing the application context using a xml file. We will discuss that XML file in few minutes. After the application context initialization we are creating a SecurityContextImpl,   and a UsernamePasswordAuthenticationToken  then we pass the token to the security context and finally use this security context to pass it on to the server for authentication and further authorization.

Finally we invoke the getRoles() method of our SecurityService which returns the set of roles assigned to the currently authenticated user and print its role to the default output stream.

Now, the spring-http-client-config.xml content:

 

<?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:p="http://www.springframework.org/schema/p"

xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd />
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd"> />


<bean id="securityService"

class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">

<property name="serviceUrl" value="http://localhost:8084/sremoting/securityService" />

<property name="serviceInterface" value="client.SecurityServiceIF" />

<property name="httpInvokerRequestExecutor">

<bean class="org.springframework.security.remoting.httpinvoker.AuthenticationSimpleHttpInvokerRequestExecutor"  />

</property>

</bean>

</beans>

As you can see we are just setting some properties like serviceURL bean name, the httpInvokerRequestExecutor and so on. the only thing which you may need to change to run the sample is the serviceUrl.
You can download the complete sample code form
here. These are two NetBeans project, a web application and a Java SE one. You may need to add Spring libraries to the project prior to any other attempt.

The SecurityService implementation is borrowed from Elvis weblog available at http://elvisfromhell.blogspot.com/2008/06/this-was-hard-one.html

Comments

which version of spring and spring security have you used ...

which version of spring and spring security have you used for this example?