【问题标题】:Binding proxy for remote Object in java RMIjava RMI中远程对象的绑定代理
【发布时间】:2023-10-31 01:56:02
【问题描述】:

我将为 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);      
    }
}

在 Server RMI 的 main 中,我代理 Object 并在 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");
    }


}

没有代理机制查找ok,使用这些代码不起作用。 也许不可能用 java rmi 绑定代理对象??

提前致谢。

附:对不起我的英语

【问题讨论】:

  • 我已经这样做了,所以这当然是可能的。但首先你需要重新考虑你的 API。它没有提供远程对象实现多个远程接口的情况。您可能会发现所需的重新设计很有启发性。
  • 您好@EJP,我阅读了这篇文章linksmart-proxy,然后我修改了SecurityInvocationHandler 类,带有这个签名:public class SecurityInvocationHandler&lt;T&gt; implements InvocationHandler, Serializable { 及其构造函数。现在,客户端查找代理对象,但 InvocationHandler 在客户端运行..
  • 当然可以。它不是一个导出的远程对象,所以它被序列化到客户端,并在那里执行。假设您退后一步,准确告诉我们您要完成的工作是什么?
  • 所以,我会实现一个安全层,进行登录,限制特定客户端的方法调用等。昨天阅读了这篇文档[A Framework for Smart Proxies and Interceptors in RMI](citeseerx.ist.psu.edu/viewdoc/… ) 它解释了我会做什么,但有两个问题,我在下面引用:
  • "方法编组:另一个必须解决的问题是编组方法调用请求。直接使用RMI时,远程对象的存根处理这个任务。服务对象丢失时stub 被动态代理取代。因此,有必要实现一种将方法调用转发到服务器的机制。ServerProxy 是用于此目的的 Remote 对象。它足够通用,因此任何方法调用都可以使用可以通过它发送任意数量的参数。”

标签: java rmi classcastexception dynamic-proxy


【解决方案1】:

这里的基本问题是您需要导出代理对象本身,而不是调用处理程序。否则代理对象会被序列化到注册表,而不是它的存根,结果我们会看到。

所以需要做如下调整:

  1. SecureRemoteInvocationHandler 不需要直接或间接扩展 UnicastRemoteObject
  2. 您需要在ServerRMI, 中的r.bind() 之前添加Remote proxyStub = UnicastRemoteObject.exportObject(proxy, 0, csf, ssf);,其中csfssf 是套接字工厂。 (我在我的代码中重命名了它们。)

您还可以进行其他改进:

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 存储在动态代理中,只有这个存根,动态代理不知道亚当。

【讨论】:

  • 感谢您的建议,所以限制评论长度,所以我在另一个答案中写下缺少的部分(异常必须由代理处理)
【解决方案2】:

感谢 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 而不是正确的。

【讨论】:

    最近更新 更多