Java:没有安全管理器:RMI类加载器已禁用。

32

你好,我有一个RMI应用程序,现在我试图从客户端调用服务器上的一些方法。我有以下代码:

public static void main(final String[] args) {
    try {
        //Setting the security manager

        System.setSecurityManager(new RMISecurityManager());
        IndicatorsService server = (IndicatorsService) Naming
                .lookup("rmi://localhost/" + IndicatorsService.SERVICE_NAME);
        DataProvider provider = new OHLCProvider(server);
        server.registerOHLCProvider(provider);
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (RemoteException e) {
        e.printStackTrace();
    } catch (NotBoundException e) {
        e.printStackTrace();
    }
}

服务器已正确加载,但当我尝试调用 server.registerOHLCProvider(provider); 时,出现以下错误:

     java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
    java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
    java.lang.ClassNotFoundException: sk.xorty.client.providers.OHLCProvider (no security manager: RMI class loader disabled)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:336)
    at sun.rmi.transport.Transport$1.run(Transport.java:159)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)
    at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:255)
    at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:233)
    at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:142)
    at sk.fri.statistics.service.impl.IndicatorsServiceImpl_Stub.registerOHLCProvider(Unknown Source)
    at sk.fri.statistics.service.Client.main(Client.java:61)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
    java.lang.ClassNotFoundException: sk.xorty.client.providers.OHLCProvider (no security manager: RMI class loader disabled)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:296)
    at sun.rmi.transport.Transport$1.run(Transport.java:159)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.ClassNotFoundException: sk.xorty.client.providers.OHLCProvider (no security manager: RMI class loader disabled)
    at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:375)
    at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:165)
    at java.rmi.server.RMIClassLoader$2.loadClass(RMIClassLoader.java:620)
    at java.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:247)
    at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:197)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1574)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
    at sun.rmi.server.UnicastRef.unmarshalValue(UnicastRef.java:306)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:290)
    ... 9 more

我已经将我的策略文件作为VM参数添加了进来,这是它的样子:

grant {
    permission java.security.AllPermission;
}

它一直在说有关禁用类装载的问题,所以我猜问题可能就出在那里了... 谢谢!


如果在设置安全管理器之前嵌入此调用 System.getProperty("java.security.policy"),它会返回什么?不知怎么的,我认为你应该在代码中设置安全策略属性,而不是将其作为命令行参数传递。 - Vineet Reynolds
它正确地打印了策略文件的路径。/home/miso/workspace/IndikatoryClient/security.policy - Xorty
6个回答

30

远程类加载可能会比较棘手。

原始帖子没有包含有关代码库的任何信息。客户端的安全配置可能是正确的,但它无法访问远程代码。客户端直接从“代码库”中加载类,而这些类不是通过RMI连接由服务呈现给客户端的。服务仅仅引用外部源加载这些类。

服务器应该指定系统属性java.rmi.server.codebase。该值必须是一个URL,且客户端可以访问该URL,并且必须能够从该URL中加载必要的类。如果这是一个file: URL,则文件系统必须对客户端可访问。

另一方面,如果服务器应该能够从客户端加载类(就像这里一样),则客户端必须将代码库属性设置为一个对服务器可访问的URL。


1
非常好,先生。我在服务器上指定了代码库,但在客户端没有。那就是问题所在。谢谢您的帮助!我稍后会添加赏金(遵守stackoverflow规则)。 - Xorty

8
每次在RMI动态代理上调用方法时,MarshalInputStream(它扩展了ObjectInputStream来覆盖resolveClassresolveProxyClass)委托给LoaderHandler在3个位置查找要使用的ClassLoader
  1. 被调用代理的ClassLoader(技术上,它使用了一个叫做 latestUserDefinedLoader() 的 hack:它遍历堆栈,查找不是JRE的第一个方法)。
  2. 调用者的Thread-local contextClassLoader
  3. 如果启用了SecurityManager,则为Codebase ClassLoader。
    1. 如果系统属性 java.rmi.server.useCodebaseOnly=false,则代码库ClassLoader使用远程 java.rmi.server.codebase中的URL。请注意,在JDK 7u21中默认值已更改,因此不再使用远程代码库,除非您更改它
    2. 否则,代码库ClassLoader使用本地 java.rmi.server.codebase中的URL。

因此,在调用远程方法时可能会出现ClassNotFoundException的几个可能原因:

  • 如果堆栈包含“没有安全管理器:RMI类加载器已禁用”,则请确保按照其他人所述设置SecurityManager,如果需要远程类加载以使双方都能获得所有远程接口和可序列化类。
  • 如果您正在使用远程类加载,并且在升级到JRE 7u21时停止工作,则将-Djava.rmi.server.useCodebaseOnly=true设置为匹配先前的行为,或者将-Djava.rmi.server.codebase设置为本地和远程端上URL的空格分隔列表。并确保计算机可以访问这些URL。
  • 如果您在本地使用自定义ClassLoader,其父ClassLoader定义了某些远程接口,则请确保调用Thread.setContextClassLoader(ClassLoader),以便RMI将使用该ClassLoader。(这是我的问题:我有一个SwingWorker,恰好被安排在在设置contextClassLoader之前创建的worker线程上)。例如,A和C属于您的自定义ClassLoader,但B属于父ClassLoader,然后当您调用a.getB().getC()时,getB()调用将使用自定义classloader,但getC()调用将无法在latestUserDefinedClassLoader中找到C,必须回退到contextClassLoader。
所有这些都是有关ObjectInputStream糟糕的API设计的警示故事。ObjectInputStream应该要求您传递一个ClassLoader参数,而不是随意使用latestUserDefinedLoader、contextClassLoader和codebase来查找它。

1
在您的第二个要点中:"然后将 -Djava.rmi.server.useCodebaseOnly=true 设置为与先前的行为匹配" 必须读作 -Djava.rmi.server.useCodebaseOnly=false参考资料 - mins

7
我希望添加一些可能对一些人有帮助的内容,特别是初学者。我来到这里并搜索了上述错误的解决方法,但由于我是一个初学者,我不知道如何使用安全策略并指定"java.rmi.server.codebase"属性。修复该错误的最简单方法是确保要通过RMI发送的对象的类在相同的包路径中。通过这种方式,两个应用程序都将类放置在相对于主文件夹的相同位置,并且错误将得到解决。
示例: 如果您想从服务器向客户端发送类型为 MedicationDTO (可序列化)的对象,请确保它位于相同的包路径中。 在我的情况下,在服务器应用程序中,对象位于com.example.springdemo.dto中,在客户端应用程序中,它位于com.example.springdemo.service.dto中。问题是,使用IntelliJ时,因为 service 包中没有任何东西,但另一个包,它们的名称被连接( service.dto ),我无法看到路径不相同。因此,请确保您的类具有相同的包路径。(我的情况的解决方案:必须在 com.example.springdemo.dto 包中的两个应用程序中都具有MedicationDTO 类。)
我知道这不是最好的解决方案,它只是一个“小技巧”,但如果我能找到这个解决方案,我会非常高兴,因为它可以节省我大量浪费时间来解决问题。
我希望这对那些想快速修复该错误的人有所帮助,因为我认为学习使用安全管理器并包含代码库可能会有点棘手,需要时间。

4
你需要在服务器端使用安全管理器,而不仅仅是在客户端使用。
如果没有这个,服务器的RMI引擎会拒绝从客户端加载类,因为它无法保证这些类不会对服务器进行恶意操作。
你是否真的需要RMI类加载?服务器是否已经拥有客户端尝试发送的类?

是的,我确实需要在客户端创建类。那么我应该如何在服务器端创建SecurityManager呢?和客户端一样,只需设置System.setSecurityManager(new RMISecurityManager())即可吗?谢谢。 - Xorty
是的(但您也可以简单地使用new SecurityManager()或自定义安全管理器,因为RMISecurityManager在这里没有做任何特殊的事情)。不过我不确定在生产系统上您是否真正需要AllPermission。 - Paŭlo Ebermann
当然不是这个问题,问题出在服务器没有意识到客户端的类。请参见我的评论@ericksson。 - Xorty
不,这可能是一个额外的问题,但不是您堆栈跟踪指示的问题。 您需要同时传输类。 - Paŭlo Ebermann

0

我知道我的情况非常特殊,但也许可以帮助其他人。我有这样一种情况:在一个服务器上有多个应用程序,当然它们共享同一个注册表但路径不同。无论这个注册表是从哪个应用程序创建的(通常是第一个应用程序),所有后续应用程序的完整类路径都必须在这个第一个应用程序中指定。总结一下:

  1. 确保提供了策略
  2. 确保为所有应用程序、客户端和服务器提供了完整的类路径

-5
我知道为什么会发生这种情况。 例如,您在项目A中启动服务器, 但您使用项目B中的客户端请求此服务器,这是错误的。 因此,您应该将服务器和客户端放在同一个项目中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接