View Full Version : Distributed applications
jmm52
Sep 6th, 2004, 10:34 AM
Hi,
The application I am working on has the business logic exported as a couple of RMI objects which are used by the web tier and a variety of external applications, and I've used Spring to wire everything up and make the split between JVMs nice and transparent. I'd like to use Acegi Security for authentication and authorisation throughout.
Would the most sensible approach be to centralise the security by customising the Spring RMI abstractions such that they pass the ThreadLocal SecureContext over the wire, or authenticate seperately in each JVM but using the new remote AuthenticationManager code?
Many thanks for any input you would care to give,
James.
Ben Alex
Sep 6th, 2004, 07:36 PM
I'm sorry but I haven't worked with RMI and Acegi Security to date, so my comments below are qualified. :-)
Basically you'll need a way of getting an Authentication into a SecureContext and ContextHolder on each of your servers. You can use any approach you like to do that. eg pass the Authentication and have something detect it in the RMI invocation and populate the ContextHolder for the duration of the invocation only.
Because MethodInvocationInterceptor will re-authenticate the ContextHolder.getContext().getAuthentication() on every request, you'll find your remote servers are actually re-performing authentication anyway.
The new RemoteAuthenticationManager won't be of much help to you. It's good to do a simple check of a password if the remote server is the only service that knows the password. In your case each of your servers will need access to the authentication repository (so their MethodInvocationInterceptor works). If you don't want each server to have this access, I'd suggest deploying CAS (there's a section at the end of the Acegi Security reference guide which discusses CAS using web services).
Hope the above is of some help. Please post back how you get on, for the benefit of others using RMI in this manner.
jmm52
Sep 9th, 2004, 08:09 AM
OK, I've managed to dynamically add/remove the Context as a parameter to the RemoteInvocation which Spring passes over the wire between its RMI proxy and service exporter, so that client credentials are transparently available to the server. I've done this with a custom RemoteInvocationFactory on the client, and a subclassed RmiServiceExporter for the server (code available if anyone is interested).
I would have prefered to use Spring AOP to manipulate the MethodInvocation itself (before it becomes remote), but there doesn't seem to be any way of adding/removing arguments from an invocation chain as an interceptor, unless I am missing something...
In addition to securing the server-side business objects, I want to use Acegi Security to control access on the webapp UI. This is where I had imagined needing the RemoteAuthenticationManager, since my FilterSecurityInterceptor will need somewhere to validate the Authentication object for each request. Does that sound sane? It seems like a shame to have two RMI calls for each page view - can the RemoteAuthenticationMangager be persuaded to cache authentication results do you think?
jmm52
Sep 9th, 2004, 08:23 AM
Actually, I just refactored the server side of the invocation manipulation into a custom RemoteInvocationExecutor which can be used with the standard Spring RmiServiceExporter. Much nicer.
Ben Alex
Sep 9th, 2004, 05:20 PM
Let me see if I understand your environment: each of your servers have direct access to a RDBMS or similar repository containing the authentication information. The servers receive the RMI request and unwrap it, setting up the ContextHolder on the server. The Spring application context houses your business objects including a MethodSecurityInterceptor. The MethodSecurityInterceptor delegates to some AuthenticationManager - probably ultimately a DaoAuthenticationProvider with an AuthenticationDao that looks up the credentials from the RDBMS.
Your remote RMI clients use RemoteAuthenticationManager to conduct the initial validation of the credentials. That way they know that when those credentials are presented via RMI to the server they will be re-validated successfully.
Your question relates to how does this tie into webapp security. If the above is correct, the ultimate processor of all authentication requests is DaoAuthenticationProvider. It has inbuilt caching. Thus as soon as the first request is received (be it from RemoteAuthenticationManager, MethodSecurityInterceptor or FilterSecurityInterceptor) it will cache the correct credentials in the UserCache. Thus the RDBMS will only be hit once.
If I have misunderstood your setup, please elaborate and I'll be happy to assist.
jmm52
Sep 10th, 2004, 05:47 AM
Yep, I think we're on the same page. Great stuff - thanks for the support! (And, for general interest, it dawned on me that a far better way to pass the Context over RMI was to use a custom subclass of the RemoteInvocation itself, so the entire use-Acegi-over-RMI code comes to something like 15 lines of Java, all of it client-side). This seems like a great project and I can't wait to see where you end up by 1.0 :)
Ben Alex
Sep 10th, 2004, 07:28 PM
Pleased you got it working. Any chance of posting your code in the forum, given it's so small and will probably help others searching the archives in the future?
jmm52
Sep 11th, 2004, 08:13 AM
Sure thing. The custom RemoteInvocation turned out to be really simple since the constructor and the invoke() methods are called in different JVMs:
package test;
import java.lang.reflect.InvocationTargetException;
import net.sf.acegisecurity.context.Context;
import net.sf.acegisecurity.context.ContextHolder;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.support.RemoteInvocat ion;
public class AcegiRemoteInvocation extends RemoteInvocation {
private Context context;
public AcegiRemoteInvocation(MethodInvocation methodInvocation) {
super(methodInvocation);
context = ContextHolder.getContext();
}
public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
ContextHolder.setContext(context);
Object result = super.invoke(targetObject);
ContextHolder.setContext(null);
return result;
}
}
And there is a trivial RemoteInvocationFactory used to create instances of them:
package test;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.support.RemoteInvocat ion;
import org.springframework.remoting.support.RemoteInvocat ionFactory;
public class AcegiRemoteInvocationFactory implements RemoteInvocationFactory {
public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
return new AcegiRemoteInvocation(methodInvocation);
}
}
Which gets wired into your remote service thus:
<bean id="test" class="org.springframework.remoting.rmi.RmiProxyFactoryBe an">
<property name="serviceUrl"><value>rmi://localhost/Test</value></property>
<property name="serviceInterface"><value>test.TargetInterface</value></property>
<property name="refreshStubOnConnectFailure"><value>true</value></property>
<property name="remoteInvocationFactory"><ref bean="remoteInvocationFactory"/></property>
</bean>
<bean id="remoteInvocationFactory" class="test.AcegiRemoteInvocationFactory"/>
It doesn't propagate changes made to the Context at the server back to the client, but since the Context is refreshed at each secure method interception, and the same AuthenticationManager is being used throughout, I don't think this is a problem.
I haven't tried using it with the other remoting protocols, but I would have thought that getting them to serialise the invocation shouldn't be too hard.
Ben Alex
Sep 11th, 2004, 05:25 PM
Thanks for that James. Mind if I pop this in the new "sandbox" in Acegi Security CVS, so any improvements etc made in the future can be found in one place?
jmm52
Sep 12th, 2004, 10:55 AM
Not at all - go for it :)
vBulletin® v3.7.3, Copyright ©2000-2008, Jelsoft Enterprises Ltd.