Лучшие практики SPA для аутентификации и управления сеансами

309

При создании приложений в стиле SPA с использованием таких фреймворков, как Angular, Ember, React и т. Д., Что люди считают лучшими практиками для аутентификации и управления сеансами? Я могу подумать о нескольких способах решения проблемы.

  1. Относитесь к этому не иначе, как к аутентификации с помощью обычного веб-приложения, предполагая, что API и пользовательский интерфейс имеют одинаковый домен происхождения.

    Скорее всего, это будет связано с наличием файла cookie сеанса, хранилища сеансов на стороне сервера и, возможно, некоторой конечной точки API сеанса, которую может использовать аутентифицированный веб-интерфейс пользователя, чтобы получить информацию о текущем пользователе, чтобы помочь с персонализацией или, возможно, даже определить роли / возможности на стороне клиента. Конечно, сервер будет по-прежнему применять правила, защищающие доступ к данным, пользовательский интерфейс будет просто использовать эту информацию для настройки взаимодействия.

  2. Относитесь к нему как к любому стороннему клиенту, использующему общедоступный API, и выполняйте аутентификацию с помощью некоторой системы токенов, аналогичной OAuth. Этот механизм токенов использовался бы пользовательским интерфейсом клиента для аутентификации каждого запроса к API сервера.

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

Крис Никола
источник
Я предпочитаю этот путь, stackoverflow.com/a/19820685/454252
allenhwkim

Ответы:

478

Этот вопрос был рассмотрен, в несколько иной форме, подробно, здесь:

RESTful аутентификация

Но это обращается к нему со стороны сервера. Давайте посмотрим на это со стороны клиента. Прежде чем мы сделаем это, есть важная прелюдия:

Javascript Crypto безнадежен

Статья Матасано об этом известна, но содержащиеся в ней уроки довольно важны:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

Подвести итоги:

  • Атака «человек посередине» может тривиально заменить ваш криптографический код <script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script>
  • Атака «человек посередине» тривиальна в отношении страницы, которая обслуживает любой ресурс через соединение без SSL.
  • Если у вас есть SSL, вы все равно используете настоящее крипто.

И добавить собственное следствие:

  • Успешная атака XSS может привести к тому, что злоумышленник выполнит код в браузере вашего клиента, даже если вы используете SSL - так что даже если у вас разбит каждый хэтч, криптография вашего браузера все равно может дать сбой, если злоумышленник найдет способ выполнить любой код JavaScript в чужом браузере.

Это делает многие схемы аутентификации RESTful невозможными или глупыми, если вы собираетесь использовать клиент JavaScript. Давайте смотреть!

HTTP Basic Auth

Прежде всего, HTTP Basic Auth. Самая простая схема: просто передайте имя и пароль при каждом запросе.

Конечно, для этого абсолютно необходим SSL, потому что вы передаете кодированное Base64 (обратимо) имя и пароль при каждом запросе. Любой, кто слушает линию, может легко извлечь имя пользователя и пароль. Большинство аргументов «Базовая аутентификация небезопасна» происходят из места «Базовая аутентификация через HTTP», что является ужасной идеей.

Браузер обеспечивает встроенную поддержку HTTP Basic Auth, но это ужасно, и вам, вероятно, не стоит использовать его для своего приложения. Однако альтернативой является сохранение имени пользователя и пароля в JavaScript.

Это самое ОТЛИЧНОЕ решение. Сервер не требует никакого знания состояния и проверяет подлинность каждого отдельного взаимодействия с пользователем. Некоторые энтузиасты REST (в основном, соломенные) настаивают на том, что поддержание любого рода состояния является ересью и пойдет на поводу, если вы подумаете о каком-либо другом методе аутентификации. У такого соответствия стандартам есть теоретические преимущества - он поддерживается Apache «из коробки» - вы можете хранить свои объекты в виде файлов в папках, защищенных файлами .htaccess, если хотите.

Проблема ? Вы кэшируете на стороне клиента имя пользователя и пароль. Это дает злому злу лучший выход из ситуации - даже самые элементарные уязвимости XSS могут привести к тому, что клиент отправит свое имя пользователя и пароль на злой сервер. Вы можете попытаться уменьшить этот риск, хэшируя и пересылая пароль, но помните: JavaScript Crypto безнадежен . Вы можете уменьшить этот риск, оставив его на усмотрение поддержки Basic Auth в браузере, но, как упоминалось ранее, это ужасно, как грех.

HTTP Digest Auth

Возможна ли дайджест-аутентификация с помощью jQuery?

Более «безопасная» аутентификация - это запрос хэша запроса / ответа. За исключением JavaScript, Crypto безнадежен , поэтому он работает только через SSL, и вам все равно придется кэшировать имя пользователя и пароль на стороне клиента, что делает его более сложным, чем HTTP Basic Auth, но не более безопасным .

Проверка подлинности запроса с дополнительными параметрами подписи.

Еще одна более «безопасная» аутентификация, где вы шифруете свои параметры одноразовыми и временными данными (для защиты от повторных и временных атак) и отправляете. Одним из лучших примеров этого является протокол OAuth 1.0, который, насколько я знаю, довольно ошеломляющий способ реализации аутентификации на REST-сервере.

http://tools.ietf.org/html/rfc5849

О, но нет никаких клиентов OAuth 1.0 для JavaScript. Зачем?

JavaScript Crypto безнадежен , помните. JavaScript не может участвовать в OAuth 1.0 без SSL, и вам все равно нужно хранить имя пользователя и пароль клиента локально - что относит их к той же категории, что и Digest Auth - это сложнее, чем HTTP Basic Auth, но не более безопасно .

знак

Пользователь отправляет имя пользователя и пароль, а взамен получает токен, который можно использовать для аутентификации запросов.

Это немного более безопасно, чем HTTP Basic Auth, потому что, как только транзакция с именем пользователя / паролем будет завершена, вы можете отбросить конфиденциальные данные. Он также менее RESTful, поскольку токены составляют «состояние» и усложняют реализацию сервера.

SSL Still

Проблема, однако, в том, что вам все еще нужно отправить это начальное имя пользователя и пароль, чтобы получить токен. Конфиденциальная информация по-прежнему касается вашего компромиссного JavaScript.

Чтобы защитить учетные данные вашего пользователя, вам все равно нужно держать злоумышленников вне вашего JavaScript, и вам все равно нужно отправить имя пользователя и пароль по сети. Требуется SSL.

Срок действия токена

Распространено применение политик токенов, например «эй, когда этот токен был слишком долгим, откажитесь от него и заставьте пользователя снова аутентифицироваться». или «Я уверен, что единственный IP-адрес, разрешенный для использования этого токена XXX.XXX.XXX.XXX». Многие из этих политик являются довольно хорошими идеями.

Firesheeping

Однако использование токена без SSL по-прежнему уязвимо для атаки, называемой «подделка»: http://codebutler.github.io/firesheep/

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

tl; dr: отправка незашифрованных токенов по проводам означает, что злоумышленники могут легко получить эти токены и выдать себя за пользователя. FireSheep - это программа, которая делает это очень просто.

Отдельная, более безопасная зона

Чем больше приложение, которое вы запускаете, тем труднее абсолютно гарантировать, что они не смогут внедрить некоторый код, который изменяет способ обработки конфиденциальных данных. Вы абсолютно доверяете своему CDN? Ваши рекламодатели? Ваша собственная кодовая база?

Общий для деталей кредитной карты и менее общий для имени пользователя и пароля - некоторые разработчики хранят «ввод конфиденциальных данных» на отдельной странице от остальной части своего приложения, страницу, которой можно жестко управлять и как можно лучше ее заблокировать, желательно такую, которая трудно фишинг пользователей.

Cookie (просто означает токен)

Можно (и часто) поместить маркер аутентификации в куки. Это не меняет никаких свойств auth с токеном, это скорее удобство. Все предыдущие аргументы все еще применяются.

Сессия (все еще означает просто токен)

Session Auth - это просто аутентификация токена, но с некоторыми отличиями, которые делают его немного другим:

  • Пользователи начинают с токена без аутентификации.
  • Бэкэнд поддерживает объект «состояние», связанный с токеном пользователя.
  • Токен предоставляется в файле cookie.
  • Среда приложения абстрагирует детали от вас.

Кроме того, на самом деле он ничем не отличается от Token Auth.

Это еще дальше от реализации RESTful - с объектами состояния вы идете все дальше и дальше по пути простого старого RPC на сервере с состоянием.

OAuth 2.0

OAuth 2.0 рассматривает проблему «Как Программное обеспечение A предоставляет Программному обеспечению B доступ к данным Пользователя X без программного обеспечения B, имеющего доступ к учетным данным пользователя X».

Реализация в значительной степени является просто стандартным способом для пользователя получить токен, а затем для стороннего сервиса перейти «да, этот пользователь и этот токен совпадают, и вы можете получить некоторые их данные от нас сейчас».

По сути, OAuth 2.0 - это просто протокол токенов. Он обладает теми же свойствами, что и другие протоколы токенов - вам все еще нужен SSL для защиты этих токенов - он просто меняет способ генерации этих токенов.

OAuth 2.0 может помочь вам двумя способами:

  • Предоставление аутентификации / информации другим
  • Получение аутентификации / информации от других

Но когда дело доходит до этого, вы просто ... используете токены.

Вернуться к вашему вопросу

Итак, вопрос, который вы задаете, заключается в следующем: «Должен ли я хранить свой токен в файле cookie, и чтобы автоматическое управление сеансами моей среды заботилось о деталях, или я должен хранить свой токен в Javascript и обрабатывать эти данные самостоятельно?»

И ответ таков: делай все, что делает тебя счастливым .

Суть автоматического управления сессиями в том, что за кулисами происходит много магии. Часто лучше самим контролировать эти детали.

Мне 21, так что SSL это да

Другой ответ: используйте https для всего, иначе разбойники украдут пароли и токены ваших пользователей.

Кертис Лассам
источник
3
Отличный ответ. Я ценю эквивалентность между системами аутентификации токенов и базовой аутентификацией cookie (которая часто встроена в веб-фреймворк). Это то, что я искал. Я благодарен вам за то, что вы затронули так много потенциальных вопросов. Ура!
Крис Никола
11
Я знаю, что это было давно, но мне интересно, следует ли расширить это, чтобы включить JWT? auth0.com/blog/2014/01/07/…
Крис Никола
14
Token It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." (1) REST требует, чтобы сервер не имел состояния. Токен, хранимый на стороне клиента , не представляет состояния для сервера. (2) Более сложный серверный код не имеет ничего общего с RESTfulness.
борщ
10
lol_nope_send_it_to_me_insteadМне понравилось название этой функции: D
Лев
6
Похоже, вы упускаете из виду одну вещь: файлы cookie безопасны при пометке httpOnly XSS и могут быть заблокированы в дальнейшем с помощью безопасных сайтов. И обработка печенья была намного дольше === более жесткой битвы. Опора на JS и локальное хранилище для обеспечения безопасности токенов - игра для дураков.
Мартин Питерс
57

Вы можете повысить безопасность в процессе аутентификации, используя JWT (JSON Web Tokens) и SSL / HTTPS.

Базовый идентификатор аутентификации / сессии может быть украден через:

  • MITM-атака (Man-In-The-Middle) - без SSL / HTTPS
  • Злоумышленник получает доступ к компьютеру пользователя
  • XSS

Используя JWT, вы шифруете данные аутентификации пользователя и сохраняете их в клиенте, а также отправляете их вместе с каждым запросом в API, где сервер / API проверяет токен. Он не может быть расшифрован / прочитан без закрытого ключа (который сервер / API хранит тайно). Read update .

Новый (более безопасный) поток будет:

Авторизоваться

  • Пользователь входит в систему и отправляет учетные данные для входа в API (через SSL / HTTPS)
  • API получает учетные данные для входа
  • Если действительный:
    • Зарегистрировать новый сеанс в базе данных Читать обновление
    • Зашифруйте идентификатор пользователя, идентификатор сеанса, IP-адрес, метку времени и т. Д. В JWT с помощью закрытого ключа.
  • API отправляет токен JWT обратно клиенту (через SSL / HTTPS)
  • Клиент получает токен JWT и сохраняет в localStorage / cookie

Каждый запрос к API

  • Пользователь отправляет HTTP-запрос к API (через SSL / HTTPS) с сохраненным токеном JWT в заголовке HTTP
  • API читает заголовок HTTP и расшифровывает токен JWT своим закрытым ключом
  • API проверяет токен JWT, сопоставляет IP-адрес из HTTP-запроса с адресом в токене JWT и проверяет, истек ли сеанс
  • Если действительный:
    • Вернуть ответ с запрошенным контентом
  • Если недействительно:
    • Исключение броска (403/401)
    • Флаг вторжения в систему
    • Отправить предупреждение пользователю.

Обновлено 30.07.15:

Полезная нагрузка / заявки JWT фактически могут быть прочитаны без закрытого ключа (секретного), и хранить его в localStorage небезопасно. Я сожалею об этих ложных утверждениях. Однако они, похоже, работают над стандартом JWE (JSON Web Encryption) .

Я реализовал это, сохранив утверждения (userID, exp) в JWT, подписав его закрытым ключом (секретным), о котором API / бэкэнд знает только, и сохранил его как безопасный HttpOnly cookie на клиенте. Таким образом, он не может быть прочитан через XSS и не может быть изменен, иначе JWT не пройдет проверку подписи. Кроме того, используя безопасный файл cookie HttpOnly , вы убедитесь, что файл cookie отправляется только через HTTP-запросы (недоступны для сценария) и только через безопасное соединение (HTTPS).

Обновлено 17.07.16:

JWT по своей природе не имеют гражданства. Это означает, что они лишают законной силы / истекают сами. Добавляя SessionID в утверждения токена, вы делаете его состоящим из состояния, поскольку его действительность теперь зависит не только от проверки подписи и даты истечения срока действия, но также от состояния сеанса на сервере. Однако положительным моментом является то, что вы можете легко аннулировать токены / сеансы, чего раньше не было с JWT без сохранения состояния.

GAUI
источник
1
В конце концов, JWT все еще «просто знак» с точки зрения безопасности, я думаю. Сервер все еще может связать идентификатор пользователя, IP-адрес, метку времени и т. Д. С непрозрачным токеном сеанса, и он будет не более или менее безопасным, чем JWT. Тем не менее, JWT без состояния делает реализацию проще.
Джеймс
1
@James JWT имеет преимущество в том, что он поддается проверке и способен нести ключевые детали. Это очень полезно для различных сценариев API, например, когда требуется междоменная аутентификация. Что-то сеанс не будет так хорошо для. Это также определенная (или, по крайней мере, в процессе) спецификация, которая полезна для реализаций. Это не значит, что она лучше, чем любая другая хорошая реализация токенов, но она хорошо определена и удобна.
Крис Никола
1
@ Крис Да, я согласен со всеми вашими пунктами. Однако поток, описанный в ответе выше, по своей сути не является более безопасным потоком, как заявлено из-за использования JWT. Кроме того, JWT не может быть отозван в схеме, описанной выше, если вы не свяжете идентификатор с JWT и не сохраните состояние на сервере. В противном случае вам нужно либо регулярно получать новый JWT, запрашивая имя пользователя / пароль (плохой пользовательский опыт), либо выдавать JWT с очень длительным сроком действия (плохо, если маркер украден).
Джеймс
1
Мой ответ не на 100% правильный, потому что JWT можно дешифровать / прочитать без закрытого ключа (секретного) и хранить его в localStorage небезопасно. Я реализовал это, сохранив утверждения (userID, exp) в JWT, подписав его закрытым ключом (секретным), о котором API / серверная часть знает только, и сохранил его как cookie-файл HttpOnly на клиенте. Таким образом, он не может быть прочитан XSS. Но вы должны использовать HTTPS, потому что токен может быть украден с помощью атаки MITM. Я обновлю свой ответ, чтобы поразмышлять над этим.
Гауи
1
@vsenko Cookie отправляется с каждым запросом от клиента. Вы не получаете доступ к куки из JS, он связан с каждым HTTP-запросом от клиента к API.
Гауи
7

Я бы пошел на второй, система токенов.

Знаете ли вы о ember-auth или ember-simple-auth ? Они оба используют систему на основе токенов, например, ember-simple-auth:

Легкая и ненавязчивая библиотека для реализации аутентификации на основе токенов в приложениях Ember.js. http://ember-simple-auth.simplabs.com

У них есть управление сессиями, и их легко подключить к существующим проектам.

Существует также пример ember-simple-auth в качестве примера Ember App Kit: Рабочий пример использования ember-app-kit с использованием ember-simple-auth для аутентификации OAuth2.

DelphiLynx
источник