Привязка прокси для удаленного объекта в Java RMI

Я хотел бы реализовать уровень безопасности для Java RMI, с механизмом динамического прокси. У меня есть некоторый класс с удаленным интерфейсом, который связывается в реестре rmi, теперь я кодирую класс SecurityInvocationHandler, код ниже:

    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 является родителем всех классов с интерфейсом "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);      
    }
}

В основной части сервера RMI я прокси-объект и связать его в 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);
        }
    }
}

Привязка это нормально, но на стороне клиента при поиске "AObject" у меня есть эта ошибка:

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

Код клиента:

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");
    }


}

Без прокси механизма поиска нормально, с этими кодами не работать. Может быть, невозможно связать прокси-объект с Java RMI??

Заранее спасибо.

PS извините за мой английский

2 ответа

Решение

Основная проблема здесь в том, что вам нужно экспортировать сам объект прокси, а не обработчик вызова. В противном случае прокси-объект сериализуется в реестр, а не в заглушку, с последствиями, которые мы видим.

Поэтому вам необходимо внести следующие корректировки:

  1. SecureRemoteInvocationHandler не нужно расширять UnicastRemoteObject прямо или косвенно.
  2. Вам нужно добавить Remote proxyStub = UnicastRemoteObject.exportObject(proxy, 0, csf, ssf); до r.bind() в ServerRMI, где csf а также ssf это фабрики розеток. (Я переименовал их в своем коде.)

Есть и другие улучшения, которые вы можете сделать:

public class SecurityInvocationHandler<T extends Remote>

для лучшей безопасности типов и аналогичным образом:

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

Вам нужно сделать переменную, содержащую результат LocateRegistry.createRegistry() статический, поэтому он не получает мусора.

Вам нужно настроить все конструкторы удаленных объектов для вызова super() с номером порта, так что вы получите динамические заглушки.

Вы не продвинетесь намного дальше, пока не разберетесь с тем, что требуется для завершения рукопожатия SSL. Вам нужно определить javax.net.ssl.keyStore/keyStorePassword на сервере и javax.net.ssl.trustStore в клиенте, если вы не используете по умолчанию (например, если сервер имеет самозаверяющий сертификат).

Причина, по которой это не работает, заключается в том, что вы экспортировали SecurityInvocationHandler заменяет себя своей заглушкой во время сериализации, и эта заглушка не является InvocationHandler, так как InvocationHandler не является удаленным интерфейсом, поэтому, когда объект десериализован, его невозможно собрать, так как нет InvocationHandler хранить в динамическом прокси, только эту заглушку, которую динамический прокси не знает от Адама.

Спасибо за совет EJP.

Я попробовал это решение, UnicastRemoteObject.exportObject действительно помогает то, что прокси-код теперь выполняется на стороне сервера, а не на стороне клиента.

UnicastRemoteObject.exportObject(proxy, 0) работает как положено, мне не нужно изменять конструктор удаленного объекта для вызова super(), потому что супер конструктор по умолчанию вызывает UnicastRemoteObject(0)

Я должен обернуть вызов вызова для обработки исключения, как

@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();
    }
}

иначе клиентская сторона получила бы java.lang.reflect.UndeclaredThrowableException вместо правильного.

Другие вопросы по тегам