我有一个程序,程序有两个可运行的文件,一个叫ServerImpl,一个叫ClientImpl。我的目标是能够让 ClientImpl 使用带 SSL 的 RMI 连接到 ServerImpl 并调用一个方法。服务器还应该能够通过回调使用 RMI 和 SSL 调用客户端上的方法。我可以让客户端使用 SSL 连接到服务器并调用该服务器上的方法,但是我无法让服务器使用带 SSL 的 RMI 连接回客户端。
该程序还有两个目录,其中包含两组不同的证书、 keystore 、信任库文件:
resources/client/
client_cert.cer
client_keystore.jks
client_truststore.jks
resources/server/
server_cert.cer
server_keystore.jks
server_truststore.jks
ServerImpl.java 文件:
public class ServerImpl implements ServerInt {
private ClientInt client;
public static void main(String[] args) {
ServerImpl server = new ServerImpl();
server.bind();
}
public void bind() {
System.setProperty("java.rmi.server.hostname", "192.168.0.32");
System.setProperty("javax.net.ssl.keyStore", "./resources/server/server_keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RMIClientSocketFactory rmiClientSocketFactory = new SslRMIClientSocketFactory();
RMIServerSocketFactory rmiServerSocketFactory = new SslRMIServerSocketFactory();
try {
ServerInt si = (ServerInt) UnicastRemoteObject.exportObject(this, 0, rmiClientSocketFactory, rmiServerSocketFactory);
Registry reg = LocateRegistry.createRegistry(1099);
reg.rebind("server", si);
System.out.println("RMIServer is bound in registry");
} catch (RemoteException ex) {
Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void connect(ClientInt ci) throws RemoteException {
System.out.println("client connected");
}
}
ClientImpl.java 文件:
public class ClientImpl implements ClientInt {
private ServerInt server;
public static void main(String[] args) {
ClientImpl client = new ClientImpl();
client.bind();
client.initConnection();
client.connectToServer();
}
public void initConnection() {
System.setProperty("javax.net.ssl.trustStore", "./resources/client/client_truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
try {
Registry reg = LocateRegistry.getRegistry("192.168.0.32", 1099);
server = (ServerInt) reg.lookup("server");
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
} catch (NotBoundException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void bind() {
System.setProperty("java.rmi.server.hostname", "192.168.0.32");
System.setProperty("javax.net.ssl.keyStore", "./resources/client/client_keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RMIClientSocketFactory rmiClientSocketFactory = new SslRMIClientSocketFactory();
RMIServerSocketFactory rmiServerSockeyFactory = new SslRMIServerSocketFactory();
try {
ClientInt ci = (ClientInt) UnicastRemoteObject.exportObject(this, 0, rmiClientSocketFactory, rmiServerSockeyFactory);
Registry reg = LocateRegistry.createRegistry(5001);
reg.rebind("client", ci);
System.out.println("RMIClient is bound in registry");
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void connectToServer() {
try {
server.connect(this);
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void sayHelloToClient(String helloText) throws RemoteException {
System.out.println(helloText);
}
}
然后我运行 ServerImpl.java 文件,没有任何问题,它运行良好。然后我运行 ClientImpl.java 文件,当我调用 connectToServer 方法时出现错误:
java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
任何人都可以阐明这里的问题是什么以及我如何能够解决它吗?如果做不到这一点,任何人都可以指出一个关于让两个 RMI 实体使用 SSL 相互交谈的好教程吗?谢谢。
最佳答案
好的,我已经弄明白了。问题是相应的客户端/服务器 java 文件仍在使用默认的 Java TrustStore,而不是我在原始问题代码中定义的自定义 Truststore 文件。对于正在寻找使用 SSL 的双向 RMI 客户端-服务器连接的简单演示的任何其他人,这里是完整的正确代码。
创建一个空白 Java 项目后,将“resources”文件夹添加到具有两个子目录“client”和“server”的顶级目录中。然后生成两组独立的证书、 keystore 和信任库,并将它们放在各自的子目录中,如下所示:
resources/client/
client_cert.cer
client_keystore.jks
client_truststore.jks
resources/server/
server_cert.cer
server_keystore.jks
server_truststore.jks
然后为名为“ServerInt”的服务器创建一个接口(interface):
public interface ServerInt extends Remote {
public void connect(ClientInt ci) throws RemoteException;
}
客户端的另一个接口(interface)称为“ClientInt”:
public interface ClientInt extends Remote {
public void sayHelloToClient(String helloText) throws RemoteException;
}
现在为名为“ServerImpl”的服务器创建一个新的 java 类:
public class ServerImpl implements ServerInt {
public static void main(String[] args) {
ServerImpl server = new ServerImpl();
server.bind();
}
public void bind() {
// System.setProperty("javax.net.debug", "all");
System.setProperty("javax.net.ssl.trustStore", "./resources/server/server_truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("java.rmi.server.hostname", "192.168.0.32");
System.setProperty("javax.net.ssl.keyStore", "./resources/server/server_keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RMIClientSocketFactory rmiClientSocketFactory = new SslRMIClientSocketFactory();
RMIServerSocketFactory rmiServerSocketFactory = new SslRMIServerSocketFactory();
try {
// Uncomment this line...
//ServerInt si = (ServerInt) UnicastRemoteObject.exportObject(this, 0);
// and then comment out this line to turn off SSL (do the same in the ClientImpl.java file)
ServerInt si = (ServerInt) UnicastRemoteObject.exportObject(this, 0, rmiClientSocketFactory, rmiServerSocketFactory);
Registry reg = LocateRegistry.createRegistry(1099);
reg.rebind("server", si);
System.out.println("Server is bound in registry");
} catch (RemoteException ex) {
Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void connect(ClientInt ci) throws RemoteException {
System.out.println("Client is connected");
// Generate a really big block of text to send to the client, that way it will be easy to see in a packet
// capture tool like wireshark and verify that it is in fact encrypted.
String helloText = "";
for (int i = 0; i < 10000; i++) {
helloText += "A";
}
ci.sayHelloToClient(helloText);
}
}
最后,我们需要一个名为“ClientImpl”的客户端类:
public class ClientImpl implements ClientInt {
private ServerInt server;
public static void main(String[] args) {
ClientImpl client = new ClientImpl();
client.bind();
client.initConnection();
client.connectToServer();
}
public void initConnection() {
try {
Registry reg = LocateRegistry.getRegistry("192.168.0.32", 1099);
server = (ServerInt) reg.lookup("server");
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
} catch (NotBoundException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void bind() {
// System.setProperty("javax.net.debug", "all");
System.setProperty("javax.net.ssl.trustStore", "./resources/client/client_truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("java.rmi.server.hostname", "192.168.0.32");
System.setProperty("javax.net.ssl.keyStore", "./resources/client/client_keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RMIClientSocketFactory rmiClientSocketFactory = new SslRMIClientSocketFactory();
RMIServerSocketFactory rmiServerSockeyFactory = new SslRMIServerSocketFactory();
try {
// Uncomment this line...
// ClientInt ci = (ClientInt) UnicastRemoteObject.exportObject(this, 0);
// and comment out this line to turn off SSL (do the same in the ServerImpl.java file)
ClientInt ci = (ClientInt) UnicastRemoteObject.exportObject(this, 0, rmiClientSocketFactory, rmiServerSockeyFactory);
Registry reg = LocateRegistry.createRegistry(5001);
reg.rebind("client", ci);
System.out.println("Client is bound in registry");
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void connectToServer() {
try {
server.connect(this);
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void sayHelloToClient(String helloText) throws RemoteException {
System.out.println(helloText);
}
}
这就是它的全部。首先运行“ServerImpl”文件,这将创建 RMI 服务器。然后运行“ClientImpl”文件,这将创建它自己的 RMI 注册表,然后在 connectToServer 方法中将其自身发送到服务器。服务器将通过客户端 RMI 对象接收此消息,然后使用客户端 RMI 对象的实例来调用客户端方法。全部使用 SSL。
为了验证它是否正在使用 SSL,服务器会生成一个非常长的文本字符串并将其发送回客户端。通过使用像 Wireshark 这样的数据包捕获工具,您可以轻松地看到此消息已加密。我在代码中添加了注释,可以轻松关闭 SSL,这样您就可以在不加密的情况下查看此文本。
我花了比我愿意承认的时间更长的时间来弄清楚这一切,同时我找不到关于这个主题的任何好的教程。所以希望如果其他人遇到这个问题,这会有所帮助。
关于java - 简单的双向 RMI SSL 连接,PKIX 路径构建失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23966107/