Привязка прокси для удаленного объекта в 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 ответа
Основная проблема здесь в том, что вам нужно экспортировать сам объект прокси, а не обработчик вызова. В противном случае прокси-объект сериализуется в реестр, а не в заглушку, с последствиями, которые мы видим.
Поэтому вам необходимо внести следующие корректировки:
SecureRemoteInvocationHandler
не нужно расширятьUnicastRemoteObject
прямо или косвенно.- Вам нужно добавить
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
вместо правильного.