在服务器端识别客户端的方法

时间:2013-04-16 12:56:54

标签: c++ architecture client-server server-side

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

客户端A使用以下命令登录:

username: user
password: pass123

服务器发现这些数据与id = 3

匹配

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

  1. 我有一个std::map,其中密钥为client ip,值为account id。在身份验证之后,服务器将客户端的ip及其帐户ID存储到此map中,稍后当服务器收到来自客户端的请求时,它会检查它的IP并在map中查找它。

  2. 我有一个std::map,其中key是随机生成的密钥,值为account id。身份验证后,服务器为此特定客户端生成随机密钥,将此密钥发送到客户端,客户端将其保存以供进一步使用,服务器将此密钥和account id存储在map中。

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

3 个答案:

答案 0 :(得分:3)

  

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

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

  

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

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


其他可能的解决方案:

  • 仍在使用std::map您可以将套接字句柄用作密钥:它在服务器上必然是唯一的,因此不会产生混淆,并且它可以帮助您避免检索客户端的IP /端口在每条消息。

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

  • 如果你的服务器使用旧的“每个连接一个进程”模型(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对象,因此它有内存泄漏但我的目的是展示如何将线程与特定内容相关联会话对象,我会留给您管理对象的生命周期,具体取决于您的确切架构和需求。

答案 1 :(得分:2)

我认为使用单个密钥进行后续身份验证并不是很安全。通过中间人攻击可以拦截密钥,或者可以以其他方式复制和使用密钥,而服务器不会注意到发生了什么。如果安全性对您非常重要,请考虑在网络层之上使用正确的身份验证/加密库,以确保安全通信。

答案 2 :(得分:2)

通常,您向客户端发送他们将通过每个连接发送到您的服务器的东西(也称为会话变量)。这是唯一的,用于标识传入的请求。

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

如果连接过程的开始以会话变量生成开始,则可以识别来自同一客户端的连接,甚至在进行身份验证之前,实际上不必知道任何IP信息。天气你在每个新连接上改变它,或者甚至在每次使用新生成的函数调用之后,你都会遇到中间攻击问题。

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