Tuesday, June 17, 2008

Spring Security + Spring Remoting + Digest Authentication

This was a hard one. Documentation on how to use spring security with a rich client is thin or outdated. Using search engines, you'll have a name clash with the "spring rich client". The name change from Acegi to Spring Security and other things like the sample code not being completely distributed with the sources made it kinda hard for me to figure things out.
Nevertheless, here's what I've come up with:

The task was to integrate spring security with a rich client that uses spring remoting (via httpinvoker) so that the client knows the roles the user has and the server does so as well. I didn't want the client to use HTTPS for performance reasons, nor did i want to transfer the password in clear over the wire (or only marginally scrambled via Base64). I chose digest authentication to be the one.
I must admit that my knowledge on the spring security architecture is very shallow, so my solution is probably not the most elegant one, but it works for me.

I don't use the RemoteAuthentication classes that are provided since I couldn't make them work together with digest authentication.

Before posting the config, how does it work?
The client takes the user credentials, they would be manual input in a real app.
The creds are put into the HttpState so the Commons HttpClient library can do digest auth with the server. The problem is that you have the SecurityContext with the GrantedAuthorities available on the server, but not on the client. For that, I've created a SecurityService that just returns the GrantedAuthorities via remote invocation. That way, the client can (for example) hide or show menus or buttons,
depending on the available roles.

Ok, let's go. This is the main application, of course just a stub to show it works.

public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml");
String user = "jimi";
String pw = "jimi";
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(user, pw);

HttpState httpState = (HttpState) ctx.getBean("httpState");
httpState.setCredentials(AuthScope.ANY, creds);

//print the available roles
String[] roles = ((SecurityService) ctx.getBean("securityService")).getRoles();
for (int i = 0; i < roles.length; i++) {
System.out.println("Role:" + roles[i]);
}
}


Now, the client's appconfig:


<?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.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">


<bean id="securityService"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8999/middletierService/securityService" />
<property name="serviceInterface" value="de.yyy.xxx.SecurityService" />
<property name="httpInvokerRequestExecutor" ref="httpInvokerRequestExecutor"/>
</bean>


<bean id="httpInvokerRequestExecutor"
class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor">
<constructor-arg ref="httpClient"/>
</bean>

<bean id="httpClient" class="org.apache.commons.httpclient.HttpClient">
<property name="state" ref="httpState" />
</bean>

<bean id="httpState" class="org.apache.commons.httpclient.HttpState"/>

</beans>


Part 2 will be on the server's configuration.

No comments: