Использование куков для хранения токенов #
Документация #
Концепция по реализации сессии #
Для реализации сессий используем следующие компоненты:
- возможностью сохранять данные в куки;
- возможностью обрабатывать запрос на уровне фильтров;
- возможностью прикреплять дополнительные данные к контексту запроса.
Процесс аутентификации с помощью пароля будет выглядеть следующим образом:
- Пользователь открывает страницу входа в систему.
- Пользователь вводит имя пользователя и пароль, отправляет их веб-приложению.
- Обработчик HTTP-запроса проверяет переданные данные: для указанного пользователя проверяет корректность введённого пароля. Если пароль корректный, то система:
- Формирует JWT-токен с идентификатором пользователя.
- Устанавливает токен в куки ответа, чтобы веб-браузер их сохранил.
- Перенаправляет пользователя на главную страницу.
- Веб-браузер запоминает переданный токен в куках и передаёт их при каждом следующем запросе в систему.
Процесс аутентификации с помощью токена будет выглядеть следующим образом:
- Веб-браузер посылает запрос серверу.
- Сервер на уровне фильтра проверяет наличие токена в куках:
- Проверяет наличие токена в куках.
- Проверяет корректность токена в куках.
- По идентификатору токена формирует описание объекта внутри системы.
- Сохраняет описание пользователя в рамках контекста запроса.
- Затем внутри обработчика запроса из контекста извлекается информация о пользователе, например для отображения его имени на странице. Извлечённое имя передаётся шаблонизатору для отображения.
Использование контекста запросов #
Библиотека http4k
предоставляет классы для сохранения дополнительных данных к конкретным запросам. Это реализуется через классы RequestContext и RequestContexts, а также через линзы RequestContextKey и RequestContextLens. С помощью этих классов можно добавить дополнительные объекты к запросу и использовать их в других частях приложения.
Для реализации этой возможности внутри приложения заводится глобальный объект, которых содержит в себе необходимые данные. Данные внутри объекта привязаны к конкретному запросу. Обычно состояние объекта изменяется на уровне фильтров, где происходит запись нужных данных. Затем к этим данным происходит обращение в других фильтрах и в завершающих обработчиках HTTP-запросов.
Для работой над данными http4k предоставляет два варианта:
- Использование строк для описания ключа хранения объекта.
- Использование линз для модификации данного объекта.
Первый подход может казаться проще, но он не обеспечивает безопасность по типу сохраняемых данных, а также предотвращает случайную перезапись данных по строковому ключу.
Вне зависимости от используемого типа доступа к хранилищу, его необходимо инициализировать с помощью фильтра ServerFilters.InitialiseRequestContext. Инициализировать его необходимо один раз для всего приложения, перед фильтрами и http-обработчиками, использующими данное хранилище. Данный фильтр ответственен также за очистку данных, связанных с конкретным запросом, когда его обработка завершается.
package guide.howto.attach_context_to_a_request
import org.http4k.core.Filter
import org.http4k.core.HttpHandler
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.RequestContexts
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.then
import org.http4k.core.with
import org.http4k.filter.ServerFilters
import org.http4k.lens.RequestContextKey
import org.http4k.lens.RequestContextLens
fun main() {
// Класс, описывающие данные контекста
data class SharedState(val message: String)
// Фильтр для сохранения данных в контекст запроса
fun addStateFilter(key: RequestContextLens<SharedState>) = Filter { next ->
{ request ->
// С помощью линзы key сохраняем состояние в хранилище
next(request.with(key of SharedState("hello there")))
}
}
// Обработчик HTTP-запроса, использующий данные из контекста
fun printStateHandler(key: RequestContextLens<SharedState>): HttpHandler = { request ->
// Для получения значения применяем линзу к объекту запроса
println(key(request))
Response(OK)
}
// Создаём хранилище для всех объектов, описывающих контекст запроса
val contexts = RequestContexts()
// Описываем линзу для установки и считывания данных из контекста запроса.
// Линзы могут быть обязательными (required), опциональными (optional) или
// со значением по умолчанию (defaulted), как и любые другие линзы
val key = RequestContextKey.required<SharedState>(contexts)
// Первый фильтр инициализирует и управляет хранилищем контекста.
// Второй фильтр записывает состояние в хранилище.
// Обработчик выводит информацию.
val app = ServerFilters.InitialiseRequestContext(contexts)
.then(AddState(key))
.then(PrintState(key))
app(Request(GET, "/hello"))
}
Задание. Реализация аутентификации #
Реализуйте процесс аутентификации на основе сессий в приложение по управлению треугольниками.
Приложение должно позволять пользователю ввести имя пользователя и пароль. Если данные введены неправильно, то приложение должно возвращаться назад к форме. Если данные были введены верно:
- В навигационной панели должно отображаться имя текущего пользователя.
- При выходе из приложения имя пользователя не отображается.
- При создании нового треугольника владельцем данного треугольника становится данный пользователь.
Подход к реализации страницы авторизации #
- Добавьте шаблон страницы авторизации,
login.peb
. На данной странице должна располагаться форма для ввода имени пользователя и пароля. Данные с формы должны отправляться POST-запросом. - Добавьте HTTP-обработчик для отображения формы входа в систему. Данный обработчик должен показывать страницу авторизации.
- Свяжите HTTP-обработчик отображения формы с маршрутом
/login
. Убедитесь, что по данному маршруту отображается форма. - Добавьте HTTP-обработчик для обработки данных с формы. Данный обработчик должен обрабатывать переданные данные.
- Необходимо проверить, что данные с формы, были введены: имя пользователя и пароль не могут быть пустыми. Если форма не заполнена, то необходимо отобразить нужную информацию.
- Необходимо использовать запрос к хранилищу, который проверяет корректность имени пользователя и пароля.
- Если запрос вернул ошибку, то на странице необходимо отобразить сообщение о том что имя пользователя или пароль были введены неверно.
- Если запрос был указан корректно, то необходимо сформировать JWT-токен, содержащий идентификатор пользователя внутри системы. Данный JWT-токен необходимо установить в куки ответа в качестве идентификатора куки можно использовать имя
auth_token
. Пользователя необходимо перенаправить на страницу входа.
Реализация отображения имени текущего пользователя #
- В функции по созданию веб-приложения создайте объект для сохранения контекста, тип
RequestContexts
. - В данной функции также определите линзу для взаимодействия с хранилищем, которая позволит сохранять и считывать структуру, описывающую пользователя. В данной структуре необходимо иметь возможность сохранить идентификатор пользователя и его имя. К сожалению линза не может предоставить значение надёжно, поэтому она должна быть опциональной.
- Создайте операцию по формированию структуры по идентификатору пользователя. Данная операция должна принимать в качестве аргумента идентификатор пользователя и возвращать структуру, заполненную необходимой информацией или
null
-значение, если пользователя с заданным идентификатором не существует. - Создайте фильтр аутентификации. Данный фильтр будет зависеть от созданной на предыдущем шаге линзы, метода по извлечению идентификатора пользователя из JWT-токена и запросу на формирование структуры, описывающей пользователя.
- Фильтр должен проверять наличие в куках ключа
auth_token
. - Из значения данного ключа необходимо извлечь значение, JWT-токен.
- Из JWT-токена извлечь идентификатор пользователя.
- С помощью запроса по идентификатору необходимо получить структуру пользователя.
- Полученную структуру необходимо записать в контекст запроса с помощью переданной линзы. Если какое-то из этих действий сделать невозможно, то не надо производить запись
null
-значения в качестве значения.
- Фильтр должен проверять наличие в куках ключа
- В функции по созданию веб-приложения сформируйте цепочку фильтров.
- Первым фильтром должен быть фильтр по инициализации хранилища контекстной информацией.
- Следующим фильтром цепочки должен быть созданный фильтр аутентификации.
- Затем расположите существующие фильтры вашего приложения или маршрутизатор.
- Модифицируйте базовую раскладку страниц,
layout.peb
, так чтобы в рамках навигационной панели справа располагался блок аутентификации.- Если в рамках модели не передаётся структура описания пользователя, то необходимо показывать ссылку на страницу аутентификации. Для проверки на пустоту логично использовать проверку
empty
шаблонизатора. - В противном случае необходимо выводить имя текущего пользователя.
- Если в рамках модели не передаётся структура описания пользователя, то необходимо показывать ссылку на страницу аутентификации. Для проверки на пустоту логично использовать проверку
- Модифицируйте обработку стартовой страницы внутри приложения. HTTP-обработчику необходимо передать линзу для получения структуры пользователя из контекста запроса. Он должен использовать её при обработке запроса и записывать полученный идентификатор в поле модели.
- Проверьте, что после выполнения успешной аутентификации при показе стартовой страницы в навигационной панели показывается имя пользователя.
Реализация функции выхода из приложения #
- Добавьте новы HTTP-обработчик запроса на выход из приложения.
- При получении запроса он формирует ответ-перенаправление на стартовую страницу приложения. В ответе с помощью метода
removeCookie
выполняется удаление кукиauth_token
. - Свяжите данный обработчик в маршрутизаторе с маршрутом
/logout
и GET-запросом. - Исправьте раскладку
layout.peb
таким образом, что при наличии информации об активном пользователе, показывалось не только имя пользователя, но и ссылка на выход из приложения. - Проверьте, что после входа в приложение показывается ссылка на выход, а после нажатия на соответствующую ссылку очищается сессионный JWT-токен.
Учёт текущего пользователя при создании треугольника #
Модифицируйте процедуру добавление нового треугольника в приложении.
- Уберите поля для ввода имени пользователя и пароля шаблона страницы.
- Уберите зависимость от запроса проверку корректности имени пользователя и пароля из HTTP-обработчика.
- Добавьте HTTP-обработчику доступ к линзе на получение информации о текущем пользователе.
- При показе HTTP-формы необходимо проверить наличие информации о пользователе, т.е. проверить наличие аутентификации. В случае отсутствия информации о пользователе необходимо на HTML-странице вывести соответствующее сообщение.
- При обработке HTTP-запроса на добавление треугольника необходимо использовать линзу для получения информации о текущем пользователе. Полученный идентификатор пользователя необходимо использовать при создании нового объекта-треугольника.