Domanda

I would implement a security layer for java RMI, with dynamic proxy mechanism. I've some class with remote interface that bind in rmi registry, now I'm coding a class SecurityInvocationHandler, code below:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.rmi.RemoteException;
    import java.rmi.server.RMIClientSocketFactory;
    import java.rmi.server.RMIServerSocketFactory;

    /** 
    *
    * @author andrew
    * @param <T>
    */
    public class SecurityInvocationHandler<T> extends SuperRemoteInterface implements InvocationHandler {

    final T remoteInterface;


    public static <T> T newInstance(final T obj, RMIClientSocketFactory rcsf, RMIServerSocketFactory rssf) throws RemoteException {
        return (T) java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), new SecurityInvocationHandler(obj, rcsf, rssf));
    }

    private SecurityInvocationHandler(T remoteInterface, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException {
        super(csf, ssf);
        this.remoteInterface = remoteInterface;

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Invoke method -> " + method.getName());
        //TODO
        return method.invoke(remoteInterface, args);   
    }

}

SuperRemoteInterface is parent of all classes with Interface "Remote":

import java.rmi.RemoteException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;

import Config.SysConfiguration;
import java.rmi.server.UnicastRemoteObject;

public class SuperRemoteInterface extends UnicastRemoteObject {

    protected SysConfiguration conf;

    protected SuperRemoteInterface() throws RemoteException {
        super();
    }

    protected SuperRemoteInterface(RMIClientSocketFactory clientFactory, RMIServerSocketFactory serverFactory) throws RemoteException {
        super(0, clientFactory, serverFactory);      
    }
}

In the main of Server RMI I proxy Object and bind it in rmiregistry:

import /****/
public class ServerRMI extends UnicastRemoteObject {

    public ServerRMI() throws RemoteException {
    }

    /*...*/
    public static void main(String[] args) {

        /*.....*/

        try {
            //Registry r = LocateRegistry.getRegistry();
            Registry r = LocateRegistry.createRegistry(port);

            RMIClientSocketFactory clientFactory = new RMISSLClientSocketFactory();
            RMIServerSocketFactory serverFactory = new RMISSLServerSocketFactory();

            AInterface proxy = (AInterface)SecurityInvocationHandler.newInstance(new AObject(conf), clientFactory, serverFactory);            



            r.bind("AObject", proxy);
            /* ..... */
        } catch (Exception e) {
            //e.printStackTrace();
            System.exit(-1);
        }
    }
}

Binding it's ok, but in the client side when lookup "AObject", I have this error:

java.lang.ClassCastException: cannot assign instance of $Proxy80 to field java.lang.reflect.Proxy.h of type java.lang.reflect.InvocationHandler in instance of $Proxy79
        at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2039)
        at java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.java:1212)
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1952)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1870)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
        at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source)
        at java.rmi.Naming.lookup(Naming.java:84)
        at login_web.GetRemoteInterface.getAInterface(GetRemoteInterface.java:35)
        .....

Client code is:

public class GetRemoteInterface {

    private static final String _port = ":nnnn";
    private String hostAddress;


    public GetRemoteInterface() throws UnknownHostException {
    /*....*/

    public AInterface getAInterface() throws MalformedURLException, RemoteException, NotBoundException{
        return (AInterface) Naming.lookup("//"+hostAddress+_port+"/AObject");
    }


}

Without proxy mechanism lookup ok, with these codes not work. Maybe it isn't possible binding a proxed object with java rmi??

Thanks in advance.

P.S. sorry for my English

È stato utile?

Soluzione

The basic problem here is that you need to export the proxy object itself, not the invocation handler. Otherwise the proxy object gets serialized to the Registry, instead of its stub, with the consequences we see.

So you need to make the following adjustments:

  1. SecureRemoteInvocationHandler doesn't need to extend UnicastRemoteObject either directly or indirectly.
  2. You need to add Remote proxyStub = UnicastRemoteObject.exportObject(proxy, 0, csf, ssf); before r.bind() in ServerRMI, where csf and ssf are the socket factories. (I renamed them in my code.)

There are other improvements you can make:

public class SecurityInvocationHandler<T extends Remote>

for better type-safety, and similarly:

public static <T extends Remote> T newInstance(...)

You need to make the variable containing the result of LocateRegistry.createRegistry() static so it doesn't get garbage-collected.

You need to get adjust all remote object contructors to call super() with a port number, so you get dynamic stubs.

You won't get much further than this until you sort out what is required for the SSL handshake to complete. You need to define javax.net.ssl.keyStore/keyStorePassword in the server, and javax.net.ssl.trustStore in the client if you aren't using the default one (i.e. if the server has a self-signed certificate).

The reason it doesn't work your way is that your exported SecurityInvocationHandler replaces itself with its stub during serialization, and that stub isn't an InvocationHandler, because InvocationHandler isn't a remote interface, so when the object gets deserialized it can't be reassembled, as there is no InvocationHandler to store in the dynamic proxy, just this stub, which the dynamic proxy doesn't know from Adam.

Altri suggerimenti

Thanks for EJP's advice.

I have try this solution, UnicastRemoteObject.exportObject really helps that proxy code is now run in server side but not client side.

UnicastRemoteObject.exportObject(proxy, 0) works as expected, I do not have to modify the remote object constructor to call super() because the default super constructor is calling UnicastRemoteObject(0)

I have to wrap the invoke call to handle the exception carefully like

@Override
public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {
    try {
        return method.invoke(remote, args);
    } catch (InvocationTargetException e) {
        throw e.getCause();
    }
}

or else client side would got a java.lang.reflect.UndeclaredThrowableException instead of the correct one.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top