Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

Что именно идентифицирует посетителя сайта?

Дамы и Господа, изучая backend, столкнулся с такой проблемой недопонимания: есть сайт, сервер Nodejs (в принципе, какой угодно, но меня интересует именно Nodejs) и зашел на сайт посетитель. Не важно есть ли регистрация на сайте или нет — что именно идентифицирует посетителя?
Я знаю, что управляется это сессиями. Читал тут, но не понял откуда именно и чего и с каких данных server понимает, что вот сейчас именно тот самый посетитель? Допустим, на сайт зашли два посетителя: один из Москвы другой из Воркуты и на сайте есть чат. Чат простой и банальный - можно без регистрации отправить какой-нибудь пост или поболтать. Надо в чате сделать так, чтобы сообщение пользователя из Воркуты было на синем фоне, а сообщения из Москвы — на зеленом. Тут же на этот сайт зашел новый посетитель тоже из Москвы и с того же IP адреса, но с другого компа который в соседней комнате нашего москвича (допустим это его супруга), вот ее (супруги) сообщение должен иметь фон оранжевый...
В итоге, не прошу никакого кода (если это только не обязательно для наглядного примера). Прошу объяснить саму логику восприятия сервером посетителей. Как определить кто есть кто?


Ответ

Перечислю все известные мне способы идентификации пользователя.
IP-адрес
Указываю этот способ потому, что он единственный, который невозможно подделать. Его можно позаимствовать у других (прокси, VPN, Tor, просто динамический IP), но это обычно сложнее, чем, например, почистить куки. Удалить IP-адрес, аналогично чистке cookies, нельзя: какой-нибудь обязательно будет. В связи с его относительной надёжностью (не всем не лень держать наготове сотни прокси-серверов для смены IP) его часто используют для усиления безопасности: например, ограничивают максимальное число запросов в секунду/минуту/час с одного IP. Однако разных людей, сидящих через один интернет, IP различить не даст, что противоречит условию вопроса, поэтому едем дальше.
Банальные логин и пароль
Суть проста: тупо шлём логин и пароль в каждом запросе. Один из вариантов реализации этого способа уже присутствует в самом протоколе HTTP, через заголовок Authorization, уже реализован во всех основных веб-браузерах и веб-серверах.
В HTTP-варианте суть такова:
при первом посещении сайта у клиента ничего нет и никакой дополнительной информации серверу не шлёт. Сервер отвечает ошибкой 401 Unauthorized и добавляет HTTP-заголовок WWW-Authenticate с информацией о способах входа (для простого логина-пароля это Basic realm="default") клиент получает это всё и просит у пользователя логин и пароль. После чего отправляет свой запрос повторно, но уже с HTTP-заголовком Authorization, в котором содержится логин-пароль в base64: Basic YWRtaW46MTIzNDU2. Если этот пример раскодировать, получим admin:123456 — логин и пароль, разделённые двоеточием сайт это всё проверяет и или отвечает нормально, или опять 401 и запрашиваем логин-пароль на новый Этот Authorization: Basic YWRtaW46MTIzNDU2 шлём каждый раз во всех последующих запросах.
Достоинства:
простота. HTTP-вариант в веб-браузерах и веб-серверах уже сделан, ничего изобретать не надо. Если делать свой вариант, то достаточно реализовать проверку логина-пароля в каждом запросе без дополнительных сложностей.
Проблемы:
без HTTPS безопасность вообще никакая: логин-пароль по сути ходят по интернету в открытом виде. Клиент тоже вынужден помнить у себя пароль в открытом виде; HTTP-вариант в браузерах работает только в пределах текущей сессии; после перезапуска браузера логин-пароль нужно вводить снова.
Справедливости ради отмечу, что HTTP умеет не только голые логин-пароль (возможно полный список спосбов авторизации), но останавливаться на других способах не буду в связи с их низкой распространённостью.
Случайная строка
Самый простой, самый сбалансированный в отношении «безопасность/удобство» и самый популярный способ идентификации. Самая распространённая в мире (наверно) кука PHPSESSID — это именно оно. Суть такова:
при первом посещении сайта у клиента ничего нет. Сайт замечает это, создаёт новую случайную строку (подлиннее, чтоб трудно было подобрать; символов 30 хотя бы) и вместе с обычным ответом на запрос тем или иным образом отправляет эту сгенерированную строку (Set-Cookie, редирект на специальную ссылку или просто в теле ответа, если это например JSON API) клиент вместе с ответом получает эту строку и запоминает её где-нибудь (браузер сам хранит в cookies, SPA может положить её в localStorage и т.п.) при последующих посещениях сайта клиент добавляет эту строку к своему запросу (cookies, HTTP-заголовок Authentication или просто GET-параметр в запрашиваемом адресе) если нужно идентифицировать клиента более конкретно (вход по логину-паролю, например), сайт в своей базе данных после записывает, что такой-то случайной строке соответствует такой-то логин, а потом при последующих запросах считывает эту информацию из базы.
Достоинства:
простота, очевидно; при смене IP-адреса (а на мобильниках это частое явление) идентификация не слетает; реализация кнопочки «Разлогинить меня на всех устройствах» сводится к простому удалению всех записей в базе.
Проблемы:
генератор случайной строки должен быть действительно случайным (или не совсем случайным, но криптостойким, не uniqid()), так как псевдослучайность злоумышленник может попытаться подобрать (например, подбор состояния генератора в PHP или Python, или подбор сессий, созданных черех uniqid(), в Invision Power Board). Ни в коем случае в качестве строки нельзя использовать хэш логина, хэш пароля, текущее время, одну-единственную заранее заготовленную строку и прочие неслучайные вещи, так как это сильно упрощает подбор. Как получить настоящую случайность, читайте в документации к вашему языку программирования; дополнительная нагрузка на сервер. Чтобы узнать, какой именно пользователь прячется за случайной строкой, ему приходится обращаться к базе данных. Не проблема для подавляющего большинства сайтов, но для гигантов типа гугла уже проблема; куки иногда баганутые: например, IE11 добавляет куки к поддоменам, даже когда его не просят (в Edge уже исправлено), что может привести к утечке данных на сторонние CDN, например. Поэтому следите за тем, как браузеры, для которых вы затачиваете сайт, манипулируют с куками. Ну и про HttpOnly не забывайте, чтобы нельзя было угнать куки через XSS (и про Secure, если сайт использует HTTPS).
Неслучайная, но защищённая строка (например, JWT)
Суть такова: нагло нарушаем вышеупомянутый запрет на неслучайные данные и пихаем в строку, например, ID пользователя и, опционально, имеющиеся права доступа (например, админ ли он), срок годности строки и какие-нибудь ещё данные. Но! Дополнительно к этой строке добавляем какой-нибудь хэш, который считается по данным плюс некой секретной строке, которую знает только сайт и никому не отдаёт. При запросе от клиента сайт, соответственно, проверяет, что хэш правильный. Это защищает от подбора и подделок: чтобы подделать данные, нужно пересчитать хэш, а злоумышленник, не зная секретной строки, этого сделать не сможет. (Секретная строка должна быть ОЧЕНЬ длинной, символов сто, чтоб вообще не подобрать, так как на ней вся безопасность.) (В JWT также вместо просто секретной строки можно применять RSA для подписи, что повышает безопасность, но расписывать все детали реализации не буду, и так длинно получилось)
Достоинства:
меньшая нагрузка на сервер. Клиент уже сам прислал все нужные данные, серверу остаётся лишь посчитать хэш от этих данных и секретной строки и проверить, что он совпадает с присланным. В базу данных ходить не надо: секретная строка обычно лежит в какой-нибудь переменной поблизости, так что всё это делается быстро; клиент сам может прочитать JWT и понять, кто он такой (если данные только защищать хэшем, а не шифровать); при смене IP-адреса тоже не слетает.
Проблемы:
реализация усложняется. Если делать всё самому, то можно накосячить и получить дырку в безопасности, поэтому лучше брать готовые реализации вроде того же JWT; кнопочку «Разлогинить меня на всех устройствах» сделать вообще нельзя. Чтобы пользовательская строка с данными стала недействительной, нужно или сменить секретную строку, или запомнить где-то в базе, что именно такая-то строка с такими-то данными стала недействительна. Но это всё довольно проблематично и сводит на нет все преимущества данного способа идентификации. Поэтому такие строки, как правило, делают короткоживущими: например, Google в своих API выдаёт JWT, действительный всего полчаса (информация о сроке годности хранится прямо в JWT, в базу ходить не надо). информация может протухнуть. Например, если записать в JWT, что пользователь является админом, а потом отобрать права админа, то сайт, опираясь на данные JWT, будет продолжать считать клиента админом, пока сам JWT не протухнет целиком. Можно брать информацию из базы, но тогда опять становится проще использовать случайную строку. JWT и аналоги из-за того, что содержат всю необходимую информацию, обычно длинные; при большом количестве данных строка может, например, не влезть в cookies.
Суперкуки и прочий фингерпринтинг
Суть в использовании технологий не по назначению. У каждого браузера и каждой ОС есть свои особенности поведения, и по этим особенностям можно довольно точно идентифицировать, кто именно зашёл. Например, они рисуют текст немного по-разному, и по мелким отличиям в пикселях текста браузеры можно различать. Не буду расписывать всё во всех подробностях, оставлю ссылки для дальнейшего чтения:
Evercookie — самые устойчивые куки Panopticlick 2.0 для фингерпринтинга браузера Супер-куки на основе HSTS отследят вас даже в приватном режиме
Достоинства:
хрен выпилишь. Если захотеть, можно, конечно, но уж очень много мороки. Это уже не просто кнопочку «Очистить cookies» нажать. Устройство клиента будет идентифицировано независимо от того, сменил ли он IP-адрес, почистил ли куки и т.п.
Проблемы:
точность не стопроцентная. Все айфоны довольно одинаковые, и отличить один айфон X от другого айфона X вряд ли получится (хотя это касается только фингерпринтинга, для суперкук попроще); пользователи вас найдут и больно побьют.

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

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