В течение нескольких дней я искал механизм безопасной проверки подлинности и управления сеансом для моего одностраничного приложения. Судя по многочисленным учебникам и сообщениям в блогах о SPA-процедурах и аутентификации, хранение JWT в localStorage или обычных куках, по-видимому, является наиболее распространенным подходом, но это просто не хорошая идея использовать JWT для сеансов, поэтому это не вариант для этого приложения.
Требования
- Логины должны быть revokable. Например, если подозрительная активность обнаружена в учетной записи пользователя, она должна немедленно отменить этот доступ пользователя и потребовать новый вход в систему. Аналогично, такие вещи, как сброс пароля, должны аннулировать все существующие логины.
- Аутентификация должна произойти над Ajax. После этого не должно быть перенаправления браузера ( "жесткое" перенаправление). Вся навигация должна произойти внутри SPA.
- Все должно работать в кросс-домене. SPA будет на www.domain1.com, а сервер будет на www.domain2.com.
- JavaScript не должен иметь доступа к конфиденциальным данным, отправляемым сервером, например идентификаторам сеанса. Это необходимо для предотвращения атак XSS, когда вредоносные скрипты могут украсть токены или идентификаторы сеансов из обычных файлов cookie, localStorage или sessionStorage.
Идеальный механизм, по-видимому, является аутентификацией на основе файлов cookie с использованием HttpOnly
файлов cookie, содержащих идентификаторы сеанса. Поток будет работать следующим образом:
- Пользователь приходит на страницу входа и отправляет свое имя пользователя и пароль.
- Сервер аутентифицирует пользователя и отправляет идентификатор сеанса в качестве файла cookie
HttpOnly
. - Затем SPA включает этот файл cookie в последующие запросы XHR, сделанные на сервере. Возможно, это возможно с помощью опции
withCredentials
. - Когда запрос делается для защищенной конечной точки, сервер ищет файл cookie. Если он найден, он перекрестно проверяет этот идентификатор сеанса на таблицу базы данных, чтобы убедиться, что сеанс по-прежнему действителен.
- Когда пользователь выходит из системы, сеанс удаляется из базы данных. В следующий раз, когда пользователь приходит на сайт, SPA получает ответ 401/403 (так как срок действия сессии истек), а затем выводит пользователя на экран входа в систему.
Поскольку cookie имеет флаг HttpOnly
, JavaScript не сможет прочитать его содержимое, поэтому он будет безопасен от атак, пока он передается через HTTPS.
Проблемы
Вот конкретная проблема, с которой я столкнулся. Мой сервер настроен для обработки запросов CORS. После аутентификации пользователя он правильно отправляет файл cookie в ответ:
HTTP/1.1 200 OK
server: Cowboy
date: Wed, 15 Mar 2017 22:35:46 GMT
content-length: 59
set-cookie: _myapp_key=SFMyNTYBbQAAABBn; path=/; HttpOnly
content-type: application/json; charset=utf-8
cache-control: max-age=0, private, must-revalidate
x-request-id: qi2q2rtt7mpi9u9c703tp7idmfg4qs6o
access-control-allow-origin: http://localhost:8080
access-control-expose-headers:
access-control-allow-credentials: true
vary: Origin
Однако браузер не сохраняет файл cookie (когда я проверяю локальные файлы cookie Chrome, он не существует). Поэтому, когда выполняется следующий код:
context.axios.post(LOGIN_URL, creds).then(response => {
context.$router.push("/api/account")
}
Создается страница учетной записи:
created() {
this.axios.get(SERVER_URL + "/api/account/", {withCredentials: true}).then(response => {
//do stuff
}
}
Этот вызов не содержит cookie в заголовке. Поэтому сервер отклоняет его.
GET /api/account/ HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Accept: application/json
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8,tr;q=0.6
Нужно ли делать что-то особенное, чтобы браузер сохранял cookie после получения его в ответе на вход? Различные источники, которые я прочитал, сказали, что браузеры сохраняют файлы ответов cookie только тогда, когда пользователь перенаправляется, но я не хочу никаких "жестких" перенаправлений, поскольку это SPA.
Рассматриваемый SPA-код написан на Vue.js, но, я думаю, это относится ко всем SPA. Мне интересно, как люди справляются с этим сценарием.
Другие материалы, которые я прочитал по этой теме: