3

我正在用 C++ 创建客户端 - 服务器应用程序。服务器将接受来自多个客户端的请求。每个客户都有在服务器上创建的个人帐户。身份验证后,我知道客户端使用特定 ip 登录到特定帐户。现在我想确定哪些请求考虑特定帐户,例如:

客户端A登录:

username: user
password: pass123

服务器发现这些数据与id= 3 的帐户匹配。

现在,当这个客户端A发送一些请求时,我希望服务器访问(并最终更改)id= 3 的帐户。到目前为止,我已经提出了这两个想法:

  1. 我有一个std::mapwhere key isclient ip和 value is account id。身份验证后,服务器将客户端的 ip 和它的帐户 id 存储到map其中,稍后当服务器收到来自客户端的请求时,它会检查它的 ip 并在map.

  2. 我有一个std::mapwhere key 是随机生成的 key 而 value 是account id. 认证后,服务器为这个特定的客户端生成随机密钥,将此密钥发送给客户端,客户端保存以供进一步使用,服务器存储此密钥并保存account idmap.

我想知道这些是否是处理此类问题的好方法。哪个更好,还要考虑安全性(这对我来说非常重要)?还是有更好的方法?

4

3 回答 3

3

1)我有一个 std::map ,其中键是客户端 IP,值是帐户 ID。身份验证后,服务器将客户端的 ip 和它的帐户 ID 存储到此映射中,然后当服务器收到来自客户端的请求时,它会检查它的 ip 并在映射中查找它。

IP 本身是不够的:可以有多个不同的客户端从同一个 IP 连接(来自同一台计算机,或者来自 NAT 后面的不同计算机,因此您只能看到 NAT IP)。如果要基于 IP 的唯一密钥,则需要使用客户端的 IP/端口元组。

2)我有一个 std::map ,其中键是随机生成的键,值是帐户 ID。认证后,服务器为这个特定的客户端生成随机密钥,将此密钥发送给客户端,客户端保存以供进一步使用,服务器将此密钥和帐户ID存储在地图中。

这是非常危险的:是什么禁止客户端发送另一个客户端的“会话 ID”而不是他的,从而劫持另一个客户端的会话?这与您可能想要阅读的 HTTP Session Hijacking 完全相同。简而言之:如果可以避免,请不要这样做。


其他可能的解决方案:

  • 仍然使用std::map您可以使用您的套接字句柄作为键:它在服务器上必须是唯一的,因此不会出现混淆,并且它使您免于在每条消息中检索客户端的 IP/端口。

  • 如果您的服务器使用旧的“每个连接一个线程”模型,那么您不必跳过这些圈。只需将您的会话数据与 Thread Local Storage 变量相关联即可。或者,几乎所有线程库都允许您将参数传递给线程,这些参数可用于将特定数据与您的线程相关联(参见下面的示例)。

  • 如果您的服务器使用旧的“每个连接一个进程”模型(fork),那么它会更容易,每个进程都有自己的变量,因此您无需做任何特别的事情。


不幸的是,只要我们不知道您的服务器使用的模型(线程、分叉、选择、aio...?),您的问题就非常开放,因此很难给您一个明确的答案。


如果您使用的是线程模型,这(大致)是我通常的做法(C++11 线程,但任何其他线程库也可以这样做):

class ClientSession {
public:
    ClientSession(int sock)
        : m_sock(sock),
          m_thread(&ClientSession::threadFunction, this)
    {
    }
private:
    int m_sock;
    WhateverType m_someSessionVariable;

    std::thread m_thread; // the thread object should be declared last so that
    // it is initialised last in the constructor, thus avoiding race conditions
    // during the initialisation (you really don't want the thread to access
    // your member variables if they are not yet initialised!)

    static void threadFunction(ClientSession* object) {
      object->threadMethod();
    }

    void threadMethod() {
      // handle your connection
      // the current ClientSession object (this) *is* your session
      // put whatever you want in it, eg. m_someSessionVariable
    }
};

//...

int sock_client = TEMP_FAILURE_RETRY(accept(sock_server, 0, 0));
if (sock_client >= 0)
  new ClientSession(sock_client);

警告:显然这段代码是有缺陷的,它永远不会破坏 ClientSession 对象,所以它有内存泄漏,但我的意思是展示如何将线程与特定会话对象相关联,我将把它留给你来管理你的对象的生命周期取决于您的确切架构和需求。

于 2013-04-16T13:26:09.023 回答
2

通常,您向客户端发送一些他们将在每次连接到您的服务器时发送的内容(也称为会话变量)。这是唯一的,用于识别传入请求。

您概述的第一个想法留下了一个问题,即本地网络上的两台或更多台计算机将与远程服务器共享相同的外部 IP。这意味着如果一个客户端在该 IP 下登录,则内部网络上的每个人都被验证为该用户。

如果您的连接过程的开始是从会话变量生成开始的,您可以识别来自同一客户端的连接,甚至在它经过身份验证之前,实际上不必知道任何 IP 信息。如果您在每个新连接上更改此设置,甚至在每次使用新生成的函数调用后,您总会遇到中间人攻击问题。

在会话创建/恢复过程之前,您需要在客户端和服务器之间的通信上实施一些安全方案。

于 2013-04-16T13:17:03.700 回答
2

我认为使用单个密钥进行以后的身份验证不是很安全。通过中间人攻击,该密钥可能被截获,或者可能被复制和使用,而服务器不会注意到发生了什么。如果安全性对您来说真的很重要,请考虑在网络层之上使用适当的身份验证/加密库以确保安全通信。

于 2013-04-16T13:11:00.277 回答