Using Spring Security to Enforce Authentication and Authorization on Spring Remoting Services Invoked From a Java SE Client
Join the DZone community and get the full member experience.
Join For FreeSpring 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 SecurityService 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
Opinions expressed by DZone contributors are their own.
Comments