суббота, 6 декабря 2014 г.

Несколько мыслей о получении IP адреса клиента в ASP.NET

Получение IP-адреса клиента - это, наверное, один из самых интересных и неоднозначных вопросов не только в ASP.NET, но и вообще в веб-разработке. Вариантов, как это сделать несколько, и у каждого из способов есть как сторонники, так и противники. Попробую рассказать вам об этих вариантах, чем они отличаются друг от друга и что лучше использовать.

Итак, самый широко известный в мире ASP.NET способ получения IP-адреса клиента - это использование HttpContext.Current.Request.UserHostAddress, который, по сути является оберткой над HTTP заголовком REMOTE_ADDR, в которую записывается адрес хоста от которого серверу пришел запрос. А этот заголовок используется практически во всех языках программирования, для целей определения IP клиента. И везде не утихают споры правильно ли использовать именно его. Почему? Потому, что REMOTE_ADDR содержит адрес компьютера, установившего соединение с сервером. Во времена становления интернет и появления протокола HTTP этик компьютером почти наверняка являлся клиент, так что все было хорошо. Сейчас же, в большинстве случаев, в REMOTE_ADDR будет адрес прокси сервера внутренней сети (корпоративной, или домашней - смысла это не меняет) через который прошел запрос клиента.
И еще один пример, с которым, особенно часто сталкиваются Linux программисты, в результате криво настроенного nginx. ;)
Представьте себе ситуацию, что у вас есть криво настроенный балансировщик нагрузки. В таком случае в REMOTE_ADDR будет 10.10.0.2, что, естественно совершенно не клиентский адрес.




В общем-то именно из-за этих двух моментов, касающихся REMOTE_ADDR, многие рекомендуют использовать для целей получения адреса клиента другой заголовок - HTTP_X_FORWARDED_FOR, придуманный в свое время создателями кеширующего прокси-сервера Squid.
Этот заголовок, в идеале, должен содержать всю цепочку IP-адресов от клиента до вашего сервера, перечисленных через запятую. Почему в идеале? Потому, что, во-первых, этот заголовок передается самим клиентом, то есть, в него можно записать все что угодно, например "la-la-la", в результате чего, если между злодейским клиентгм и вашим сервером один прокси,
HTTP_X_FORWARDED_FOR будет содержать что-то типа:

la-la-la, 192.168.0.10

Так что, как вы наверное догадались, использовать этот заголовок так, как часто рекомендуют в интернетах нельзя. Забудьте про этот вариант:

 
    if (HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != null)
    {
        return HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
    }

Во-первых, там может быть что угодно, а не обязательно IP-адрес. А во-вторых, потому, что содержать реальный IP-адрес клиента в чистом виде этот заголовок будет только в случае если между клиентом и вами всего один прокси и клиент имеет нормальный полноценный IP.

В 99.9% случае это будет адрес из приватной подсети 192.168.*.* Так что, если уж вы решили использовать HTTP_X_FORWARDED_FOR, то, как минимум, проверяйте что там вообще IP-адрес и из какого он IP-диапазона.

 
         string ipAddr = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();

         if (Regex.IsMatch(ipAddr,"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"))
         {
             if (!(ipAddr.StartsWith("192.168.")||(ipAddr.StartsWith("10.")))

             {
                return ipAddr;
             }
         }
         return HttpContext.Current.Request.UserHostAddress;

Если же HTTP_X_FORWARDED_FOR cодержит несколько адресов, то рекомендую использовать второй, так как первый адрес практически гарантированно будет из внутренней подсети. Ну и, естественно, не забываем проверять содержимое.
 
    string ipAddr = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
    if (!string.IsNullOrEmpty(ipAddr))
    {
        string[] addresses = ipAddr.Split(',');
        if (addresses.Length == 1)
        {
            ipAddr = addresses[0];
        }
        else if (addresses.Length >1)
        {
                   ipAddr = addresses[1];
        }
        if (Regex.IsMatch(ipAddr,"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"))
        {
            if (!(ipAddr.StartsWith("192.168.")||(ipAddr.StartsWith("10.")))) 
            {
                return ipAddr;
            }
        }
    }
    return HttpContext.Current.Request.UserHostAddress;

Так что, именно вот такой код, по моему мнению, является наиболее правильным вариантом определения IP адреса клиента. Хотя, как мне кажется, в большинстве случаев, все же вполне достаточно Request.UserHostAddress или Request.ServerVariables["REMOTE_ADDR"].

Ну а в случае, если вы получаете IP клиента из соображений безопасности, например для проверки валидности сессии, то самым правильным в таком случае будет самое простое решение - сравнивать все, что есть, например в виде простой строки:
 
    string ipAddrForSecurity = HttpContext.Current.Request.UserHostAddress+HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();


Комментариев нет:

Отправить комментарий