Управление списком пользователей и базовая авторизация

Управление списком пользователей и базовая авторизация #

Документация #

Хранение паролей #

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

Пароль также обладает интересной особенностью: не надо сохранять пароль целиком, достаточно иметь возможность сравнить пароль, который был введён пользователем при регистрации, с паролём, который был введён для авторизации в системе. Для решения этой задачи зачастую прибегают к хеш-функциям, которые формируют одинаковую хеш-строку для одинакового ввода данных.

flowchart LR login["Пароль регистрации"] auth["Пароль авторизации"] hash("Функция хеширования") log_hash["Хеш регистрации"] log_auth["Хеш авторизации"] login --> hash auth --> hash hash --> log_hash hash --> log_auth

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

Для улучшения защиты данных для перебора необходимо выполнить ещё пару улучшений:

  1. Увеличить количество итераций для выполнения алгоритма. Это затруднит прямой перебор.
  2. Добавить секретную соль, которой производится расширение данных в пароле.

В результате алгоритм вычисления хеша будет следующим:

  • взять пароль от пользователя
  • добавить к введённому паролю соль
  • применить к полученной строке хеш-функцию определённое количество итераций

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

Сохранение соли для паролей #

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

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

Использование функций в базе данных H2 #

База данных H2 предоставляет ряд функций, которые можно применять для манипулирования над данными. При этом обработка может осуществляться как на данными, которые передаются внутрь БД, так и с теми, которые возвращаются из БД.

Для выполнения хеширования строк предоставляется функция HASH:

CALL HASH('SHA3-256', '12345', 10)

В качестве первого аргумента передаётся название алгоритма для выполнения шифрования. Для обеспечения качества шифрования лучше всего использовать алгоритмы семейства SHA3: SHA3-224, SHA3-256, SHA3-384 и SHA3-512. Разница между ними заключается в размере формируемой хеш-строки. Чем он больше, тем меньше может быть возможность коллизий, тем надёжнее идентификация строки.

Вызов функций через Ktorm #

Уровень слоя взаимодействия с базой данных путём генерации SQL-выражений из библиотеки Ktorm поддерживает основные операции над данными: извлечение, модификация, подсчёт. Однако расширенные возможности, предоставляемый СУБД, не поддаются обобщению. Для поддержки расширенных возможностей конкретных баз данных предоставляются расширения конкретных диалектов.

Стоит отметить, что даже с этими расширениями поддержать весь спектр возможностей языка SQL Ktorm не в состоянии. Для обеспечения работы с такими ограничениями Ktorm предоставляет возможность выполнения нативного SQL-кода с помощью вызова Database.useConnection. Данный метод принимает в качестве аргумента подключение к базе данных, объект типа Connection.

С его помощью можно выполнить подготовленный заранее подготовленный SQL-запрос, PreparedStatement:

class GetDepartmentNameOperation(
    private val database: Database,
) {
    private val preparedSql = """
        select NAME from EMPLOYEE
        where DEPARTMENT_ID = ?
        order by ID
    """.trimIndent()

    fun getDepartmentName(departmentId: Int): String? {
        database.useConnection { connection ->
            connection.prepareStatement(preparedSql).use { statement ->
                statement.setInt(1, departmentId)
                return statement
                    .executeQuery()
                    .asIterable()
                    .map { row ->
                        row.getString(1)
                    }.firstOrNull()
            }
        }
    }
}

Для выполнения запросов на получение данных используется метод PreparedStatement.ExecuteQuery, для выполнения запросов на изменение данных используется метод PreparedStament.ExecuteUpdate().

Задача #

  1. Реализуйте хранилище для сохранения соли. Сформируйте соль для пароля. Соль должна быть достаточно большой длины, рекомендуется от 100 случайных символов.
  2. Добавьте новую сущность «Ученик». Он описывается следующими полями: имя пользователя, пароль, дата добавления.
    1. Добавьте новую миграцию к приложению, описывающего данную сущность. Длина строки под пароль 64 символа.
    2. Не забудьте добавить уникальный идентификатор.
    3. Создайте описание таблицы с помощью библиотеки Ktorm.
  3. Реализуйте процедуру добавления нового ученика.
    1. Добавьте страницу «управление пользователями», доступную через навигационную панель.
    2. На странице добавьте кнопку «добавить ученика».
    3. При нажатии на кнопку пользователю должна открыться форма с вводом данных. На форме должны присутствовать поля: имя пользователя и два поля для ввода пароля.
    4. При отправке данных с формы на уровне обработки данных формы, необходимо проверить равенство двух паролей, введённых пользователем.
    5. Создайте операцию по изменению данных в базы.
      1. В конструктор операции передайте ссылку на подключение к базе данных.
      2. В конструктор операции передайте строку-соль, полученную из настроек.
      3. Для передачи соли из настроек передайте соль также в конструктор хранилища операций.
      4. В момент выполнения операции получите имя пользователя и пароль.
      5. Перед записью пароля в базу данных объедините её с солью.
      6. При записи данных в базу внутри SQL-выражения проведите преобразование солированного пароля в хеш-значение.
  4. Доработайте процедуру добавления нового треугольника.
    1. На странице добавления добавьте поле для ввода имени пользователя и пароля ученика.
    2. При добавлении треугольника проверьте, что введённый пароль соответствует указанному ученику. Если пароли не совпадают, то необходимо показать страницу добавления треугольника с сообщением об ошибке.
      1. Создайте операцию по проверке пароля пользователя.
      2. Операция должна принимать имя ученика, пароль и соль.
      3. В рамках операции должно проверяться соответствие имени ученика и хеш-суммы от пароля с солью с сохранёнными данными.

© A. M. Васильев, 2022, CC BY-SA 4.0, andrey@crafted.su