【问题标题】:How to organize RMI Client-Server architecture如何组织 RMI 客户端-服务器架构
【发布时间】:2025-11-22 03:05:02
【问题描述】:

我正在 RMI 中开发一个安全的银行服务,带有一个用于服务器和客户端的 GUI。

服务器必须能够记录每个操作(新用户、已删除用户、取款、提交...) 客户端将执行这些操作。 由于一切都是安全的,客户首先必须在 GUI 中创建一个具有名称和密码的帐户。之后,GUI 将 Bank UserList(arrayList) 中的用户添加为新客户,用户可以执行多项操作。 乍一看似乎很简单,但我认为我的概念不正确。

通过RMI发送整个银行是否正确?因为起初我认为银行会成为服务器,但我找不到其他方法来做到这一点。 目前,客户端 GUI 要求输入登录名和密码,并通过 RMI 接收银行。用户的特征是名称和密码的哈希值。

private String name;
private byte[] passwordDigest;

事实上,GUI 正在执行所有安全检查,我不知道它是否相关。当您输入 login//password 时,它将在银行中搜索登录名并比较密码的哈希值。 事实上,我的印象是客户知道太多信息,因为当您拥有银行时,您就拥有了一切......

这看起来是正确的还是我需要改变我的实现?

【问题讨论】:

  • 你的 Bank 接口是实现 Serializable 还是 Remote?
  • 如果您实际上是在开发“真正的”银行服务,而不是测试项目或任务或其他东西,请出于对所有神圣事物的热爱,不要使用 RMI...我爱Java 但能够使用其他语言的接口很有用:)
  • 任务什么的

标签: java rmi


【解决方案1】:

您需要两个远程对象类。

第一个是通过Naming.lookup()获得的;它是一个单例;它包含一个login() 方法。

此方法返回第二个远程对象,它不是单例,未在注册表中注册,并且为每个返回值重新创建。该对象包含所有银行方法以及一个 logout() 方法,该方法将其取消导出;它可能还实现了Unreferenced 接口,因此它可以检测到死客户端,并自行取消导出。因为它每个客户端存在一次,它可以保持客户端状态,并且因为它只能通过成功的登录步骤获得,所以它解决了您的安全问题。

public interface Login extends Remote
{
    Session login(String username, char[] password /* or whatever */)
        throws LoginException, RemoteException;
}

public interface Session extends Remote
{
    void logout() throws RemoteException;
    void deposit(...) throws RemoteException;
    void withdraw(...) throws RemoteException;
}

public class LoginImpl extends UnicastRemoteObject implements Login
{
    public Session login(String username, char[] password)
        throws LoginException, RemoteException
    {
        // username/password check; if it fails throw a LoginException
        return new SessionImpl(username); // or whatever
    }
}

public class SessionImpl extends UnicastRemoteObject implements Session, Unreferenced
{
    public void logout() throws RemoteException
    {
        unexportObject(this, true);
    }

    public void unreferenced()
    {
        unexportObject(this, true); // needs to be in a try/catch block of course
    }

    // etc
}

我在 2001 年的书中将其描述为远程会话模式。

当然你需要传输层安全性:见javax.rmi.ssl.

【讨论】:

  • 感谢您的回答。但是我不明白银行在这一切中的位置。 Session 正在做 Bank 应该做的所有事情?因为在我的实现中,银行包含一个客户列表,每个客户都有一个帐户列表。
  • @xenom 这种架构没有意义。任何给定的客户都应该只能操作自己的帐户。我根本看不出银行是从哪里来的。
  • 是的,我正在改变一切。一切都将留在服务器端,只有你给我的两个远程对象会通过 RMI。
  • 嗯,另一个精度。现在所有操作都在 Session 中调用。我可以在 Session 构造函数中传递 Bank 实例吗?因为存款/取款实际上是在银行中实现的。例如,构造函数是SessionImpl(String login,Bank bank),例如withdraw 会是this.bank.withdraw(login,amount) 我对吗?
【解决方案2】:

您误解了远程接口的工作原理。客户没有拥有银行,它只有一个对银行的远程实例的引用。客户端在 Bank 接口上进行的所有方法调用实际上都变成了对 Bank 的 remote 实例(在服务器中运行)的 remote 调用。

据推测,安全检查是在内部银行方法在每个方法调用中完成的。

【讨论】:

  • 所以银行参考没有问题。关于安全检查。正如我所说,银行有一个用户列表,用户有一个名称和构造函数中给出的密码的哈希值。在 GUI 中输入登录名/密码时,您的意思是最好让银行的每种方法都询问密码属性?并进行检查本身而不是 GUI?
  • @xenom - 如果您真的希望您的银行应用程序安全,则必须对每个呼叫进行身份验证。这可以通过每次检查信用,或通过一些安全的令牌交换(在每次调用时提供令牌)。检查一次信用然后拨打一堆电话是安全的。