Skip to content

alexfirerain/cloudwork

Repository files navigation

CloudWork – облачное хранилище файлов

Программа CloudWork представляет собой файловый сервер, предназначенный для работы в составе программного комплекса из трёх компонентов:

  • передовое (лицевое) приложение NetologyDiplomFrontend, с которым пользователь работает в своём браузере (предоставлено как-есть в ТЗ),
  • собственно тыловое (серверное) приложение CloudWork, разработанное для согласованной работы с передовым приложением согласно спецификации CloudServiceSpecification,
  • СУБД (в данной реализации MySQL) для персистентного хранения на сервере пользовательских файлов и данных о пользователях.

Приложение написано на Java (v17) с использованием каркаса Spring Boot, взаимодействие с БД (MySQL) строится с помощью ORM Hibernate, в процессе компиляции используется библиотека аннотаций Lombok, а для управления состояниями базы данных Liquibase.

Функционал приложения

Приложение Cloudwork производит операции над файлами, принадлежащими зарегистрированным пользователям. Учётные данные зарегистрированных пользователей и их файлы хранятся в БД. Команды на операции считываются из http-запросов, поступающих от лицевого приложения на оконечные адреса (такназываемые "эндпойнты"). Совокупность допустимых для программы запросов и ожидаемых ответов на них называется АПИ и собрана в спецификацию.

Авторизация

CloudWork осуществляет авторизацию пользователей по логину и паролю. Он принимает неавторизованные запросы на эндпойнт "/login", служащий для входа в систему, и авторизованные запросы на остальные эндпойнты. В ответ на корректные логин и пароль приложение генерирует и отсылает авторизационный токен. Затем авторизованный запрос должен содержать этот токен в определённом заголовке.
Запрос к эндпойнту "/logout" соответствует выходу пользователя из системы.

эндпойнт метод принимает возвращает
/login POST json-объект с логином и паролем json-объект с токеном
/logout POST - -

Управление пользователями (помимо назначения им токенов доступа) не входит в функционал программного комплекса, приложение имеет дело с теми учётными записями, которые предоставляются БД. Для демонстрации работы программы используется тестовая база данных с двумя пользователями:

"почта" "пароль"
user 0000
who_user 1111

Контроль состояния БД в данном проекте поручен мигратору Liquibase.

Вместе с тем, в приложении присутствует дополнительный компонент для преддобавления в БД какого-то количества определённых в коде юзеров при запуске: UserPreloader, он включается в настройках application: user-preloader: enabled.

Работа с файлами

CloudWork работает с файлами пользователя, имя которого узнаёт по авторизационному токену из запроса. Поддерживаются запросы загрузить файл, скачать файл, переименовать файл или удалить его, а также запрос списка загруженных файлов (ограниченной длины), о каждом из которых сообщается имя и размер.

эндпойнт метод принимает возвращает
/list GET количество файлов в списке в параметре массив json-объектов с именем и размером файлов
/file POST имя файла в параметре,
сам файл в теле запроса
-
/file GET имя файла в параметре сам файл
/file PUT имя файла в параметре,
json-объект с новым именем в теле запроса
-
/file DELETE имя файла в параметре -

Фактическая реализация фронт-приложения всегда запрашивает список длиной в три файла.

Фактическая реализация переименования файла во фронт-приложении присылает в качестве нового имени случайное трёхзначное десятичное число.

Хотя фронт-приложение отображает сортировку файлов по дате, а CloudWork сохраняет дату загрузки и изменения файла, эти данные не передаются от сервера и лишь симулируются клиентом.

Запуск приложения

CloudWork это не самодостаточное приложение, а сервис, предназначенный для работы в составе программного комплекса.
Сервис выполняет собственно операции с файлами, нуждаясь при этом в СУБД, где хранятся учётные записи пользователей и их материалы, и в лицевом приложении, через которое с комплексом взаимодействует пользователь. В принципе каждая программа может быть запущена любым возможным способом, даже на разных ЭВМ, лишь бы они могли найти друг друга благодаря корректным настройкам.

Рекомендуется запускать программный комплекс с помощью средства Докер-Компоуз. Предлагаемый сценарий запуска гарантирует сборку докер-образов тылового и лицевого приложений из исходников в данном репозитории и их согласованный старт и взаимодействие между собой и с БД "MySQL". Запуск сценария производится командой docker-compose up -d в рабочем каталоге проекта.

Архитектура приложения

Приложение CloudWork построено на каркасе Спринг Бут, и компоненты автоматически инициализируются с помощью стандартных средств. Приложение построено по стандартной слоистой архитектуре со стандартным разделением функциональных компонентов, отражённым в структуре проекта:

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

    • DisableEncodeUrlFilter
    • WebAsyncManagerIntegrationFilter
    • SecurityContextHolderFilter
    • HeaderWriterFilter
    • CorsFilter
    • ExceptionHandlerFilter - обработчик исключений авторизации CloudWork
    • TokenFilter - фильтр, анализирующий входящие запросы на предмет наличия в них токена аутентификации и запускающий процедуру их аутентификации
    • LogoutFilter - фильтр, завершающий сессию пользователя при его выходе из системы, он запускает специально определённый CloudworkLogoutHandler, который аннулирует сессию
    • RequestCacheAwareFilter
    • SecurityContextHolderAwareRequestFilter
    • SessionManagementFilter
    • ExceptionTranslationFilter
    • AuthorizationFilter
  • контроллеры считывают команду и данные из http-запроса, передают их на обработку сервисам и возвращают клиентскому приложению результат выполнения в формате http-ответа; в приложении присутствуют два контроллера:

    • EntranceController - принимает POST-запрос на авторизацию, приходящий на эндпойнт "/login", а также отрабатывает GET-перенаправление на этот адрес после того, как LogoutHandler отработал запрос на "/logout".
    • FileController - обрабатывает все остальные запросы кроме логина и логаута, т.е. все запросы на эндпойнты "/list" и "/file" согласно спецификации.
  • сервисы отражают основной функционал приложения (такназыаемую "бизнес-логику"); присутствует сервис для работы с юзерами, сервис для операций с файлами и авторизационный сервис:

    • CloudworkAuthorizationService - управляет вопросами пользовательских сессий CloudWork, содержит основную логику авторизации.
    • UserManager - решает все вопросы, относящиеся к хранимым в БД данным о пользователях, в том числе управление сопоставленными токенами доступа и предоставление UserDetails для авторизации.
    • FileService - решает все вопросы, относящиеся к хранению файлов в БД.
  • репозитории реализуют взаимодействию с СУБД, т.е. запрашивают данные о файлах и пользователях из хранилища и сохраняют их в нём

Модель предметной области

Программа манипулирует с двумя основными видами сущностей: учётные записи пользователей (владельцев файлов) и собственно хранимые файлы. Данные о них хранятся в двух соответственных таблицах в БД.

О хранимом файле (FileEntity) в базе держится следующая информация:

  • file_id - уникальный идентификатор (назначается СУБД);
  • file_name - имя файла;
  • size - размер файла;
  • owner_user_id - идентификатор владельца файла (внешний ключ);
  • file_type - тип содержимого (если файл может о нём сообщить);
  • body - байтовый массив, составляющий файл как таковой;
  • upload_date - дата загрузки файла;
  • update_date - дата крайнего изменения (переименования) файла.

Управление пользователями CloudWork использует технологию Spring Security, поэтому помимо базовой информации для идентификации в базе юзеров (UserEntity) на всякий случай хранится информация об учётной записи, соответствующая дополнительным возможностям интерфейса UserDetails:

  • user_id - уникальный идентификатор (назначается СУБД);
  • username - логин, имя юзера;
  • password - пароль (хранится в закодированном виде);
  • authorities - полномочия пользователя, т.е. набор его ролей (в БД хранится как строка, где названия ролей соединены через запятую, т.е. в формате CSV);
  • files - список файлов данного пользователя (отражение связи с таблицей файлов);
  • account_expired - истёк ли срок действия учётной записи;
  • locked - заблокирована ли учётная запись;
  • credentials_expired - истёк ли срок действия авторизации;
  • enabled - включена ли учётная запись;
  • access_token - токен доступа, означающий сопоставленную пользователю сессию доступа CloudWork.

Система аутентификации CloudWork

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

Реализация авторизации на базе токена обычно означает, что токен несёт в себе всю информацию, достаточную для аутентификации запроса -- без необходимости запроса к БД или структуре в памяти. Однако приложение CloudWork проектировалось, когда разработчик ещё не знал ничего этого, поэтому оно использует самобытную модель авторизации, фактически основанную на сессиях, когда токен используется в качестве метки сессии. Этот CloudWork-токен представляет собой просто строку с именем пользователя и датой генерации и не имеет никаких свойств безопасности, таких как срок действия или секретный ключ. С содержимым этой строки программа также никак не работает, от токена в каждом запросе просто ожидается, что он тождественен тому, который сопоставлен юзеру в рамках данной сессии. Использование кук в логике данной реализации также не предполагается, хотя веб-приложение их посылает, а сервис очищает при закрытии сессии.

Говорится, что "сессия пользователя открыта", когда пользователю сопоставлен токен доступа - они добавляются в мапу в памяти и записывается в БД в строке соответствующего пользователя, закрытие сессии соответствует удалению сопоставления из памяти и БД.

Модель безопасности

Совокупность используемых в приложении средств безопасности называется иногда в документации "моделью CloudWork", которая включает следующие классы:

  • Role, воплощение интерфейса GrantedAuthority ("выданные полномочия") - константный список предусмотренных моделью ролей, состоит из элементов USER, который используется по умолчанию во всех случаях, и SUPERUSER, который предуготовляется для специальных админских процедур и в данной реализации никак не задействуется.
  • UserInfo, воплощение интерфейса UserDetails ("подробности пользователя") - представление данных об учётной записи, соответствующее хранимой в БД сущности, несёт в себе имя пользователя, пароль, набор полномочий и биты, означающие четыре причины, по которым учётная запись может быть не активна: accountExpired, locked, credentialsExpired, enabled (три первых у активного аккаунта имеют значение НЕТ, последнее ДА). При создании объект UserInfo обычно заполняется данными на основе соответствующей UserEntity.
  • CloudworkAuthorization, воплощение интерфейса Authentication ("аутентификация") - представление состояние аутентифицированности для пользователя CloudWork, несёт в себе имя пользователя, пароль, набор полномочий (Role, в данной реализации используется только USER) и бит аутентифицированности, соответствующий тому, что этот пользователь предоставил в запросе валидный токен доступа и получил через то авторизованный доступ к системе (как USER).

Инициализация компонентов

Спринг автоматически инициализирует управляемые объекты (бины), запуск начинается с класса @SpringBootApplication CloudWorkApplication, затем сканирует папку проекта в поиске аннотированных компонентов:

  • @RestController: EntranceController, FileController
  • @ControllerAdvice: ErrorController
  • @Service: CloudworkAuthorizationService, UserManager, FileService
  • @Repository: UserRepository, FileRepository
  • @Component: TokenFilter, ExceptionHandlerFilter, CloudworkLogoutHandler

А также бины, объявленные в классах конфигурации: @Configuration AuxiliaryComponents: @Bean PasswordEncoder и @Bean CorsConfigurationSource. В классе @Configuration @EnableWebSecurity SecurityConfig определён @Bean SecurityFilterChain, в котором, помимо настроек безопасности и режимов авторизации SpringSecurity, указано, в какое место в цепь фильтров следует вставить TokenFilter и ExceptionHandlerFilter, а также что обработка выхода пользователя со SpringLogout должна поручаться CloudworkLogoutHandler. Значения TOKEN_HEADER и TOKEN_PREFIX считываются из файла настроек и устанавливаются в статические поля токен-фильтра при его инициализации ﹘ чтобы это было возможно, эти константы реализованы как отедльные бины.

Транспортные объекты

При передаче данных между лицевым и тыловым приложениями используются константные DTO-объекты, реализованные как записи java:

  • LoginRequest - запрос на авторизацию к серверу: логин и пароль.
  • LoginResponse - ответ сервера об успешной авторизации: выданный токен.
  • FileInfo - информация о файле на сервере: имя и размер в байтах; имеет фабричный статический метод для порождения экземпляра из пары оъектов, доставленных прямо из БД.
  • RenameRequest - запрос на переименование файла: новое имя.
  • ErrorDto - ответ сервера о возникшем исключении, сообщение и идентификатор; содержит сквозной атомарный счётчик для нумерации ошибок в приложении (обнуляется при каждой инициализации приложения), а также конструктор для создания экземпляра напрямую из перехваченного исключения.

Получение доступа

Для получения авторизации от лицевого приложения на "/login" приходит джейсон-объект с логином и паролем (заявка от клиента на авторизацию). Сервис проверяет, что присланные учётные данные соответствуют активной учётной записи, и генерирует токен доступа, сохраняемый в сопоставление этому пользователю и высылаемый в ответ. Если для данного пользователя уже существует открытая сессия, то переиспользуется старый токен.

Авторизованный доступ

Когда запрос приходит на CloudWork, он анализируется фильтром TokenFilter. Если адрес требует авторизации (т.е. для всех эндпойнтов кроме "/login"), то проверяется токен из заголовка auth-token. Если токена нужного формата не обнаружено, возвращается ответ 401. Если же корректный токен извлечён из заголовка, он отправляется в CloudworkAuthorizationService - тот проверяет по мапе, что такой токен принадлежит к числу активных, и авторизует запрос для пользователя, которому этот токен сопоставлен.

Закрытие сессии

Для закрытия сессии на "/logout" приходит джейсон-объект с токеном. Сервис проверяет, что присланный токен соответствует открытому пользователю, и удаляет его из БД (и мапы быстрого доступа).

Компоненты системы безопасности


  • TokenFilter (воплощает OnePerRequestFilter) - ищет во входящих запросах токен CloudWork. Имя заголовка, несущего токен, и префикс строки токена могут специфицироваться через настройки application: token-header и application: token-prefix'. Запросы на "/login" пропускаются, ибо авторизации не требуют. Найденный токен отправляется на авторизацию в CloudworkAuthorizationService, который или аутентицирует запрос, или, если что-то не так, отреагирует исключением.
  • CloudworkLogoutHandler (воплощает LogoutHandler) - обработчик выхода из системы, запускаемый LogoutFilter-ом согласно стандартной отработке логаута в Спринг Бут. Его работа заключается в том, что он распоряжается CloudworkAuthorizationService-у выполнить процедуру завершения сеанса пользователя.
  • ExceptionHandlerFilter (воплощает OnePerRequestFilter) - обрабатывает должным образом исключения, возникшие в фильтрах, т.е. находящиеся вне зоны ответственности ErrorController-а. Если перехватываемое исключение относится к подмножеству AuthenticationException, то ответ маркируется http-кодом 401, в ином случае - кодом 500.

Сервисы CloudWork также являются частью системы безопасности:

  • UserManager, воплощающий интерфейс UserDetailsManager - используется для всех операций, связанных с управлением пользователями, в том числе для получения учётных данных пользователя (UserDetails) по его имени, как то требуется при стандартной процедуре проверки учётных данных в Спринг Секьюрити.
  • CloudworkAuthorizationService, воплощающий интерфейс AuthenticationManager - сервис, реализующий основную логику системы управления пользователями и сессиями CloudWork. Он работает четырьмя методами:
  1. public LoginResponse initializeSession(LoginRequest loginRequest) - проверяет полученный из запроса логин и пароль и, в случае успеха, возвращает обёрнутый токен доступа, который либо генерируется, либо переиспользуется из уже открытой сессии:
    • принимает запрос на аутентификацию, содержащий переданный от веб-приложения логин и пароль;
    • просит UserManager-а по логину найти данные пользователя; если он не сможет этого сделать, выдаст UsernameNotFoundException;
    • сверяет полученный пароль с паролем из данных пользователя (с учётом зашифрованной кодировки его хранения в БД); если не совпадёт, выдаст BadCredentialsException;
    • интересуется у UserManager-а, не существует ли уже открытая сессия для этого пользователя (т.е. сопоставлен ли ему ненулевой токен), и, если нет, то генерирует новый токен и говорит UserManager-у записать его в БД в строку этого юзера;
    • возвращает, обёрнутым в LoginResponse, токен доступа для прошедшего проверку логин-запроса (новосгенерированный или переиспользуя существующий).
  2. public void authenticateByToken(String token) - аутентицирует текущий запрос по строке токена, взятой из его заголовка:
    • спрашивает у UserManager-а, какому пользователю сопоставлен такой токен; если никакому, то выдаст BadCredentialsExcepption, так как это значит, что сессия, которой этот токен соответствует, видимо уже закончена;
    • ставит в SecurityContextHolder объект CloudworkAuthorization, соответствующий учётным данным найденного по базе пользователя, таким образом аутентицируя текущий поток; если аутентикация невозможна по одной из причин, предусмотренных в методе .authenticate(), бросается соответственное исключение.
  3. public void terminateSession(String username) - завершает текущую сессию пользователя, распоряжаясь UserManager-у установить значение токена для указанного юзернейма в нуль.
  4. public Authentication authenticate(Authentication authentication) - во исполнение должности AuthenticationManager принимает объект аутентификации, находит с помощью UserManager-а соответствующие ему данные пользователя и проверяет, не отключена ли запись, не заблокирована ли, и есть ли у этого пользователя сопоставленный ненулевой токен; если что-то из этого окажется не фактом, выдаст DisabledException, LockedException или BadCredentialsException, а если всё в порядке, то аутентицирует этот объект и вернёт его обратно.
  5. private String generateTokenFor(UserDetails authentication) - внутренний метод создания токена для предложенного пользователя. Модель CloudWork использует очень простую строку из логина и текущей даты, как "%s @ %s".formatted(имя_пользователя, new Date()).

Работа с файлами пользователей

Сервисы CloudWork

Основная логика работы приложения реализована в службах, т.е. сервисах:

  • CloudworkAuthorizationService - управляет вопросами пользовательских сессий CloudWork.
  • UserManager - решает все вопросы, относящиеся к хранимым в БД данным о пользователях, в том числе управление сопоставленными токенами доступа и предоставление UserDetails для авторизации.
  • FileService - решает все вопросы, относящиеся к хранению файлов в БД.

Контроллеры CloudWork

  • EntranceController - принимает POST-запрос на авторизацию, приходящий на эндпойнт "/login", а также отрабатывает GET-перенаправление на этот адрес после того, как LogoutHandler отработал запрос на "/logout".
  • FileController - обрабатывает все остальные запросы кроме логина и логаута, т.е. все запросы на эндпойнты "/list" и "/file" согласно спецификации.

Репозитории CloudWork

  • UserRepository - JpaRepository-интерфейс, производит все необходимые запросы к БД по таблице users.
  • FileRepository - JpaRepository-интерфейс, производящий все необходимые запросы к БД по таблице files.

About

an online file storage backend service

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published