Страницы

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

Показаны сообщения с ярлыком backend. Показать все сообщения
Показаны сообщения с ярлыком backend. Показать все сообщения

среда, 29 января 2020 г.

Асинхронный map [дубликат]

#nodejs #async_await #backend


        
             
                
                    
                        
                            На этот вопрос уже даны ответы здесь:
                            
                        
                    
                
                        
                            Как выполнить последовательно несколько асинхронных фунций
в Javascript без коллбэков?
                                
                                    (3 ответа)
                                
                        
                                Закрыт 1 год назад.
            
                    
Пишу бекенд на node (v 8.11.4)

Сервер общается с vk api. У vk есть ограничение - 5 запросов с секунду.

Чтобы не уперется в ограничение было решено сделать функцию sleep:

export default (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms));
};


Далее есть мой метод (туда импортирую функцию sleep):

app.post(path, (req, res) => {
    [1,2,3].map(async item => {
        await sleep(1000);
        console.log(item);
    })
})


Здесь я ожидаю что console.log будет срабатывать раз в секунду. Но этого не происходит.
Проходит 1 секунда и console.log выводится 3 раза.
    


Ответы

Ответ 1



Все дело в том что этот код: [1,2,3].map(async item => { await sleep(1000); console.log(item); }) Тоже самое что и этот let foo = async item => { await sleep(1000); console.log(item); } foo(); foo(); foo(); Все три функции вызываются сразу. Каждая ждет по секунде и затем одна за другой выводят item в консоль. Так как начинают ждать они одновременно, то и заканчивают тоже, поэтому вы видите что сообщения выводятся сразу все. Можно сделать так: const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const foo = async (item, i) => { setTimeout(() => { return console.log(item); }, 1000 * i); // 1000 - это ваша задержка между вызовами } numbers.map(foo); Можно ставить устанавливать задержку после каждого вызова: const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const delayLoop = (array, delay) => { let i = 0; const loop = () => { setTimeout(() => { console.log(numbers[i]); if (++i < numbers.length) { loop(); } }, delay); }; loop(); } delayLoop(numbers, 1000); Можно сделать пример выше более универсальным, добавив callback: const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const delayLoop = (array, delay, handler) => { let i = 0; const loop = () => { setTimeout(() => { handler(numbers[i]); if (++i < numbers.length) { loop(); } }, delay); }; loop(); } const count = (value) => { console.log(`counter: ${value}`); } delayLoop(numbers, 1000, count);

Ответ 2



Если я правильно понял инужно выводитьв консоль значения из массива с заданным интервалом, то можно сделать так - const sleep = (ms) => { return new Promise(resolve => setTimeout(resolve, ms)); }; const array = [1, 2, 3]; array.reduce(async(result, current) => result.then(() => sleep(1000).then(() => console.log(current))), Promise.resolve());

Ответ 3



Возможно, такие циклы проще делать с помощью for..of циклов: app.post(path, async (req, res) => { for (const item of [1,2,3]) { await sleep(1000); console.log(item); }) })

пятница, 24 января 2020 г.

Реализация механизма выдачи уведомлений в Web-приложении

#javascript #angular2 #frontend #backend


Вопрос - есть веб-приложение на angular 2, каким образом можно реализовать постоянное
получение уведомления о каких-либо действиях с сервера (backend на Google Cloud Platform
- App engine). Мне приходил вариант постоянно опрашивать в вечном цикле сервер, однако
это не выход, или по крайней мере реализация будет затратной. Какие есть варианты механизмы
live-уведомлений, а именно их отдачи с сервера на фронт?
    


Ответы

Ответ 1



Если я правильно понял вопрос то возможно WebSocket и event-driven архитектура поможет Вам решить Вашу проблему. Изменения на сервере будут отправлять action на фронт, где уже слушатели будут реагировать на это должным образом.

четверг, 12 декабря 2019 г.

Постобработка данных: фронтент или бэкэнд. За и Против

#http #архитектура #frontend #backend #highload


По сети передаются данные, которые представляют собой полигоны  (для простоты можно
считать, что это массивы float). В таком случае возникает вопрос, где лучше формировать
окончательный вид этих данных в виде json: на сервере и передавать json или на фронте.
Важное замечание. На фронте нужны именно json.

Кажется, что это лучше делать на фронте, чтобы не таскать по сети строки, а передавать
массивы float. Но я не уверен в производительности JS. Насколько эффективно раскладывать
всё по строкам?



Участники, которые собираются участвовать в конкурсе, обратите внимание, конкурс
объявлен для того, чтобы люди поделились своим опытом решения подобного рода задач.
Описание конкурса:

**В ходе беседы с участниками сообщества, выяснилось, что данная тема актуальна и
может быть интересна. В связи с этим, хочется получить в качестве ответа максимально
подробный ответ с описанием деталей задачи, которую Вы решали, а также принятое решение. **

Я, в частности, подразумеваю, что речь идёт о High Load, т.е. о системах, которые
обеспечивают безотказную работоспособность для не менее чем Q запросов и отвечают не
более чем за указанный интервал времени T

Меня интересуют решения T == 1 sek. Q >= 50. Лучше, больше 100
    


Ответы

Ответ 1



Вопрос, на самом деле, весьма сложный. Есть плюсы и минусы обоих решений. Если делать всё на серваке, то все клиенты будут однородные данные получать и не будет дополнительно обработки. Следовательно, шанс, что на клиенте накосячат, меньше. Лишняя нагрузка на сервак. Порой переложить дополнительную обработку на клиент - вполне валидное решение, чтоб снизить нагрузку. Как пример, на текущем месте работы у нас используются карты. Необходима кластеризация точек. И проблема с тем, где это делать (на сервере или клиенте), стоит весьма остро. В базе миллионы пользователей, у каждого свои атрибуты (координаты, город, страна, пол и т.п). Нужно кластеризовать всё это дело с учётом атрибутов. Раньше на серваке захардкожено было, что больше определённого числа юзеров не выдаётся. Встал вопрос о кластеризации. Варианта два: Сервер отдаёт кучу пользователей. А клиентские приложения сами кластеризуют как-то (iOS, Android и т.д. реализуют это на клиенте). Кластеризовать на сервере. Клиентам только зум передавать. Сервак, в принципе, неплохо масштабируется. Решили делать там. У нас бэк на ноде. Используем supercluster, который подтягивает данные из базы и кластеризует всё. В риалтайме вполне справляется с этим делом. Для того, чтоб не проседало ничего, используем Worker Threads. Из нашего проекта не могу скринов отсыпать, но выглядит как-то так: P.S. у нас всё API гоняется в json. Если у вас не слишком частые запросы, то лучше уж json использовать, оверхедом по размеру можно принебречь.

Ответ 2



Лучше фронту всегда отдавать готовые, потребительские данные. Всю остальную логику с данными делать на сервере. Фронт у тебя должен быть "тупой". Получил данные и отобразил. Лучше это в том числе потому что средний пользователь сегодня смотрит сайт не с десктопа, а с телефона. Часто это не топовый iPhone, а какой-то слабый и дешевый андроид. На нём всё медленно. Значит, чем меньше вы делаете ненужной работы на клиенте, тем быстрее работает сайт у пользователей, и тем более удовлетворены работой вашего сайта его гости. Это особенно важно если говорить не о сайтах уровня домашней страницы, а о настоящем Highload: потеря одного процента пользователей из-за того что ваш сайт недостаточно отзывчив может иметь очень ощутимые финансовые последствия.

Ответ 3



Тут вопрос не до конца поставлен... Я считаю что в этом случае решения по архитектуре нужно принимать исходя из количества передаваемых(обновляемых во времени) данных Т.к. не понятно можно все сразу загрузить, сложить в Indexeddb и потом её кошмарить, что весьма простое решение. Или же надо постоянно слать гео запросы на сервер, типа покажи мне что сейчас попало в такой-то bbox, т.к. фич терабайты или они меняются постоянно. И еше не понятно, вы решаете задачу оптимизации, или просто пока рисуете на карте ? Я по работе в основном имею дело с компьютерной графикой и гео-информационными системами. Проекты ориентированы на realtime отображение на глобусе как ракета полетела, куда упали отделяющиеся части, какие измерительные средства какую траекторию померили и все в таком духе. Сырых данных вагон и маленькая тележка, однако то, что прилетает непосредственно ко мне можно сказать песчинка. Бэкэнд у нас сложная штука- зоорпарк разного программного и аппаратного барахла, раскиданного по планете, передачей данных в системе является amqp брокер, и естественно он помимо моих данных обрабатывает еще кучу других потребителей и поставщиков сообщений. При таком распределении нагрузки - я сам восстанавливаю пакеты протобафа в geojson и тому подобные вещи на лету, ибо разным потребителям информация нужна все-равно в разном виде и гонять по сети/спутнику лишние байты тоже не хочется, еще нужно учесть что все это еще и дуплицируется по какой-то кучерявой логике детали которой мне неизвестны =) Add: похоже, в вашем конкретном случае, с учётом указанных вами критериев, вы не можете полагаться на скорость клиентского девайса, соответственно, то и работу на нем лучше не выполнять. Что касается кластеризации и геоданных - вы смотрели в сторону postgis? Это посгрес на стероидах, специально для geospatial данных и запросов

Ответ 4



Успешная практика разработки Unix говорит о том, что для связи компонент системы желательно (всюду, где это не является невозможным по тем или иным причинам) использовать текстовые (с возможностью чтения и редактирования человеком) форматы представления данных. Думаю, в вашем случае нужно гонять по сети json (т.е. человекочитаемый текст).

воскресенье, 24 ноября 2019 г.

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


Изучая backend, столкнулся с такой проблемой недопонимания:  есть сайт, сервер Nodej
(в принципе, какой угодно, но меня интересует именно Nodejs) и зашел на сайт посетитель. Не важно есть ли регистрация на сайте или нет — что именно идентифицирует посетителя? 

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

В итоге, не прошу никакого кода (если это только не обязательно для наглядного примера). Прошу объяснить саму логику восприятия сервером посетителей. Как определить кто есть кто?
    


Ответы

Ответ 1



Перечислю все известные мне способы идентификации пользователя. 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-параметр в запрашиваемом адресе) если нужно идентифицировать клиента более конкретно (вход по логину-паролю, например) сайт в своей базе данных после записывает, что такой-то случайной строке соответствует такой-то логин, а потом при последующих запросах считывает эту информацию из базы. Если говорить о PHP, то всё это в него встроено: при вызове функции session_start( создаётся кука PHPSESSID из случайных букв и цифр (или читается существующая, если он уже есть). Данные, которые связаны с этой кукой, хранятся в массиве $_SESSION, и в можете его читать и изменять. Содержимое этого массива по умолчанию сохраняется в какой-то файл; при последующих запросах от пользователя этот файл автоматически прочитается при вызове session_start(), и все данные, которые вы положили в массив $_SESSION при обработке предыдущих запросов, восстановятся. Подробности в документации. Достоинства: простота, очевидно; при смене IP-адреса (а на мобильниках это частое явление) идентификация не слетает; реализация кнопочки «Разлогинить меня на всех устройствах» сводится к простому удалению всех записей в базе. Проблемы: генератор случайной строки должен быть действительно случайным (или не совсем случайным но криптостойким, не uniqid()), так как псевдослучайность злоумышленник может попытатьс подобрать (например, подбор состояния генератора в PHP или Python, или подбор сессий созданных черех uniqid(), в Invision Power Board). Ни в коем случае в качестве строки нельзя использовать хэш логина, хэш пароля, текущее время, одну-единственную заранее заготовленную строку и прочие неслучайные вещи, так как это сильно упрощает подбор. Как получить настоящую случайность, читайте в документации к вашему языку программирования. Или просто используйте готовую реализацию вроде session_start() в PHP; дополнительная нагрузка на сервер. Чтобы узнать, какой именно пользователь прячетс за случайной строкой, ему приходится обращаться к базе данных. Не проблема для подавляющего большинства сайтов, но для гигантов типа гугла уже проблема; куки иногда баганутые: например, 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 вряд ли получится (хотя это касается только фингерпринтинга, для суперкук попроще); пользователи вас найдут и больно побьют.

Ответ 2



В дополнение к сказанному о недостатках кук можно сказать что: Прозрачный серверный кеш запросов становится невозможен если используются куки (буд это Varnish или прозрачное кеширование в nginx). Потому если не ставить куки со сторон сервера, если они не нужны, то страницы сайта смогут использовать серверный кеш и будут открываться быстрее. Попытки кешировать запросы с заголовком Set-Cookie противоречат просто здравому смыслу, значит они тоже проходят мимо кеша. Настройка CDN для статических ресурсов требует внимания если используются куки. Есл ваш сайт открывается без www по адресу, например, test.ru, то, поставив куку на сайте можно будет считать что эта кука будет передаваться в запросах ко всем поддоменам, включая, например, cdn.test.ru. Потому на сайтах, которые традиционно открываются без www, вы можете видеть что для статических ресурсов используется отдельный домен второго уровня, а не поддомен. Например yastatic.net у Яндекса. Последнюю проблему должен был исправить RFC 6265, но на момент написания этого ответ некоторые основные браузеры всё ещё не поддерживают его в полней мере. От этих стары браузеров всё ещё нельзя просто так отмахнуться, так как не во всём мире использование старых браузеров одинаково низко: например, в Японии на март 2018 года IE используется для 16% всех запросов. А это страна в которой живёт 127 миллионов человек. Если вы делаете глобальный сервис, то так и так вам придётся действовать будто RFC 6265 ещё нет. Кроме JWT можно вспомнить другие "длинные" куки вроде ViewState из ASP.NET, которы свойственны те же проблемы с кешированием и CDN, что и со обычными сессионными случайным куками, только хуже. Такие и подобные куки могут быть очень большие, запросто по десятк килобайт, а если они будут передаваться с каждым запросом к любой картинке или статическому файлу на вашем сайте, то это будет определённо сказываться на скорости работы сайта во всех режимах. Майкрософт прямо рекомендуют не пользоваться ими если вам важна скорость сайта. Что ещё? Вместо кук можно использовать всевозможные заголовки, которые могут попасть в браузерны кеш. Например, если вы однажды послали с какой-то картинкой или файлом заголовок ETag то при следующем обращении браузер снова перешлёт значение этого заголовка. Пользователю никак не видно что вы его так отслеживаете, то есть знаете, что это вот тот человек зашел ещё раз, потому такие приёмы не приветствуются. Итого Если вы можете не идентифицировать ваших пользователей, то ваш сайт сможет работать быстрее.

Ответ 3



Как правило используются Cookies - определенная строка данных, которая хранится пользователя в браузере. Алгоритм их генерации вы можете сделать сами, либо использовать родной движка, например PHPSESSID в PHP (смотри функцию session_start()). Можно так же идентифицировать пользователя без них, но в данном случае необходимо будет использовать другие параметры, к которым есть доступ. Это, в первую очередь User-agent (браузер пользователя) и его IP адрес. В случа PHP эти переменные хранятся в $_SERVER: 1. $_SERVER['HTTP_USER_AGENT'] 2. $_SERVER['REMOTE_ADDR'] Соответственно, второй вариант обладает меньшей точностью, но будет работать, есл у пользователя отключено хранение Cookie. Пример ошибки во втором случае - два пользователя находятся за NAT и используют одинаковый браузер.

воскресенье, 7 июля 2019 г.

Кроссдоменный AJAX

Чем плохо то, что сервер разрешит всем производить кроссдоменный ajax? Будем обрабатывать эти запросы так же, как и от обычных клиентов и все дела. Что я упускаю?


Ответ

Тем, что подобная "всеразрешающая" политика:
значительно снизит уровень безопасности в вебе; против интересов людей, которые в сети создают что-то помимо мусора.

Предположим, Вася пользуется популярным сайтом А, где у него есть учетная запись и некоторые персональные/чувствительные данные. И тут Вася открывает в другой вкладке браузера сайт Б, сделанный Петей. Петя - сцуко хитрый. Он поместил на странице своего сайта скрипт, который обращается к сайту А. Если посетитель петиного сайта там авторизован, Петя легко ворует чужие данные, а также может изменять и удалять их. В реальном мире, что мешает таким Петям делать подобное? Политика same-origin!
Другой пример. Иван запилил на своем сайте страницу, где регулярно выкладывает свои работы, интересные огромному числу посетителей. Сайт Ивана предоставляет оригинальный контент и работает быстро, посетители довольны. И вдруг, Иван замечает что его сервер не справляется, посетители жалуются. Что еще хуже, в поисковике появился сайт Пети с копиями контента Ивана - и не просто с присвоением авторства, а еще и в окружении рекламы типа "порно-с-осликами-скачать-сейчас". Посмотрев логи, Иван видит что его сервер завален запросами от посетителей сайта Пети (потому что тот наговнокодил на jQuery парсер с интервалом выполнения 500мс). В реальном мире, что помогает защищать контент от тупых школьников с jQuery и спасает от лишней нагрузки? Политика same-origin!

понедельник, 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 вряд ли получится (хотя это касается только фингерпринтинга, для суперкук попроще); пользователи вас найдут и больно побьют.

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

Дамы и Господа, изучая 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 вряд ли получится (хотя это касается только фингерпринтинга, для суперкук попроще); пользователи вас найдут и больно побьют.