Программа для авторизации на java
В данной статье мы рассмотрим способ авторизации пользователей в веб приложениях на Spring Boot по протоколу OAuth2 с использованием внешнего сервера авторизации (на примере Bitbucket).
Что мы хотим получить
Допустим, мы разрабатываем защищенное веб приложение, имеющее доступ к ресурсам пользователя на внешнем сервере, например, систему непрерывной интеграции, а в качестве внешнего сервера выступает Bitbucket, и мы не хотели бы хранить у себя логин и пароль пользователя от внешней системы. То есть нам необходимо авторизовать пользователя через Bitbucket, чтобы получить доступ к его учетной записи и ресурсам, дополнительно проверить, что он является пользователем нашего приложения и сделать это так, чтобы пользователь не раскрывал нам свои учетные данные от Bitbucket. Интересно как это сделать, — добро пожаловать под кат.
OAuth2
OAuth2 — это протокол авторизации, который позволяет предоставить третьей стороне ограниченный доступ к защищенным ресурсам пользователя без необходимости передавать ей (третьей стороне) логин и пароль.
OAuth2 определяет 4 роли:
- Владелец ресурса
- Ресурсный сервер
- Сервер авторизации
- Клиент (приложение)
На ресурсном сервере размещаются защищенные учетные записи пользователей.
Сервер авторизации проверяет подлинность владельца ресурса и выдает маркеры доступа. Сервер авторизации может одновременно являться и ресурсным сервером.
Клиентское приложение — это приложение, которое хочет получить доступ к учетной записи и ресурсам пользователя.
В нашем случае клиентское приложение — это приложение, которое мы разрабатываем, а Bitbucket будет как сервером авторизации так и ресурсным сервером.
OAuth2 поддерживает четыре типа авторизации: Authorization Code, Implicit, Resource Owner Password Credentials и Client Credentials. Мы не будем рассматривать их все, нас интересует тип Authorization Code. Тип Authorization Code оптимизирован для серверных приложений, в которых исходный код не является общедоступным и может сохраняться конфиденциальность кода доступа (Client Secret). Этот тип работает на основе перенаправления, то есть пользователь будет перенаправлен на сервер авторизации для того, чтобы подтвердить свою личность и разрешить приложению использовать его учетную запись.
Процесс авторизации посредством Authorization Code состоит из последовательности двух запросов:
- Запрос авторизации (Authorization Request)
- Запрос кода доступа (Access Token Request)
- response_type — значение должно быть равно code
- client_id — значение, полученное при регистрации клиента в OAuth2 провайдере
- redirect_uri — URL, куда будет перенаправлен пользователь после авторизации
- scope — опциональный параметр, указывающий, какой уровень доступа запрашивается
- state — случайно сгенерированная строка для верификации ответа
В случае, если пользователь подтвердит свою личность и разрешит приложению доступ к своим ресурсам, пользовательский агент будет перенаправлен на URL обратного вызова, заданный при регистрации клиента с дополнительным параметром code, содержащим код авторизации и параметром state со значением переданным в запросе.
Пример ответного вызова:
Запрос кода доступа используется, чтобы обменять полученный код авторизации на код доступа к ресурсам пользователя. Этот запрос представляет собой POST запрос со следующими параметрами:
- grant_type — значение должно быть authorization_code
- code — код авторизации, полученный на предыдущем шаге
- redirect_uri — должен совпадать с URL, указанным на предыдущем шаге
- client_id — значение, полученное при регистрации клиента в OAuth2 провайдере
- client_secret — значение, полученное при регистрации клиента в OAuth2 провайдере
Ответ сервера содержит код доступа и время его жизни:
Весь этот процесс уже автоматизирован в Spring Security и нам нет необходимости заботиться о его реализации.
Первым делом зарегистрируем наше приложение в качестве клиента в Bitbucket, чтобы получить ключ (Key) и код доступа (Client Secret).
Вводим название клиента и URL обратного вызова. Затем отмечаем, что будет доступно данному клиенту.
Полученные значения Key и ClientSecret сохраняем в application.properties:
Настройка Spring Security
Кастомизация UserService
Spring Security не только полностью реализует процесс авторизации, но также предоставляет возможности его кастомизации. Например, возможность кастомизации запроса авторизации, запроса кода доступа, а также возможность собственной пост обработки ответа на запрос кода доступа. После успешной авторизации Spring Security использует UserInfo Endpoint, чтобы получить атрибуты пользователя с сервера авторизации. В частности, для этого используется реализация интерфейса OAuth2UserService.
Мы собираемся создать собственную реализацию этого сервиса, чтобы после авторизации пользователя на сервере авторизации дополнительно проверить является ли он пользователем нашего приложения, либо зарегистрировать его, если регистрация открыта всем. По умолчанию Spring Security использует реализацию DefaultOAuth2UserService. Он и ляжет в основу нашей реализации.
Тестовый endpoint
Самое время создать endpoint для проверки работоспособности того, что мы только что сделали. Наш endpoint будет состоять всего из одного запроса, который будет приветствовать текущего пользователя.
Проверка работоспособности
Теперь нам надо разрешить нашему приложению доступ к своей учетной записи и ресурсам.
Приветствие пользователя содержит как атрибуты с сервера авторизации, так и ID пользователя в нашем приложении.
В этой статье будет рассмотрено создание простого веб приложения с использованием Spring Boot и Spring Security. В приложении будет реализована регистрация новых пользователей и авторизация, ограничение доступа к страницам сайта в зависимости от роли пользователя.
Главная цель статьи показать как можно ограничить доступ к различным страницам сайта для пользователей с разными ролями.
Что будет представлять из себя приложение
Сайт со следующими страницам:
Что будем использовать
- JDK 8+;
- Intellij Idea;
- Spring (Spring Boot, Spring MVC, Spring Security);
- Hibernate;
- JSP;
- PostgreSQL.
Содержание
- Описание основных используемых аннотаций.
- Создание нового проекта в IDE.
- Создание структуры проекта (пакетов).
- Добавление сущностей, контроллеров, сервисов, репозиториев и представлений.
- Запуск приложения.
1. Описание основных используемых аннотаций
Немного информации о Spring Security
Самым фундаментальным объектом является SecurityContextHolder. В нем хранится информация о текущем контексте безопасности приложения, который включает в себя подробную информацию о пользователе (принципале), работающим с приложением. Spring Security использует объект Authentication, пользователя авторизованной сессии.
«Пользователь» – это просто Object. В большинстве случаев он может быть
приведен к классу UserDetails. UserDetails можно представить, как адаптер между БД пользователей и тем что требуется Spring Security внутри SecurityContextHolder.
Для создания UserDetails используется интерфейс UserDetailsService, с единственным методом:
2. Создание нового проекта в IDE
Мы будем использовать систему сборки Maven.
Под GroupId подразумевается уникальный идентификатор компании (или ваше личное доменное имя), которая выпускает проект. ArtefactId – это просто название нашего проекта.
После завершения создания проекта отроется файл pom.xml, Idea предложит включить автоимпорт – не отказывайтесь. В этом файле будут содержаться все зависимости (библиотеки), используемые в проекте.
3. Создание структуры проекта (пакетов)
Сразу перейдем к созданию пакетов. Структура проекта, которая должна получиться показана ниже.
Теперь коротко о том, что будет храниться в каждом пакете:
- src\main\java\com\boots\config — классы с конфигурациями для MVC (MvcConfig) и безопасности (WebSecurityConfig);
- src\main\java\com\boots\controller — классы с контроллерами;
- src\main\java\com\boots\entity — классы с моделями;
- src\main\java\com\boots\repository — интерфейсы репозиториев;
- src\main\java\com\boots\service — классы c сервисами для моделей;
- src\main\webapp\resources — статические объекты: js, css, img;
- src\main\webapp\WEB-INF\jsp — представления в виде файлов .jsp.
Далее добавляем зависимости для работы модулей Spring, драйвер БД PostgreSQL, сервера Tomcat, JSTL.
Также добавляем плагин, позволяющий упаковывать архивы jar или war и запускать их «на месте»:
Заполним файл application.properties. Первые 3 строки содержат данные для подключения к БД (имя БД – «spring», логин и пароль). Последний 2 строки указывают путь к .jsp файлам:
Свойство spring.jpa.show-sql выводит тела запросов к БД в консоль.
spring.jpa.hibernate.ddl-auto позволяет задать стратегию формирования БД на основе наших моделей, имеет разные значения (none, create, update и др.). update в данном случае значит, что таблицы БД и поля будут созданы на основе наших моделей и буду изменяться вместе с ними.
Забегая вперед можно отметить, что нам нужно будет только создать БД с именем spring, а таблицы пользователей, ролей и их связующая таблица вместе с внешними ключами будут сформированы автоматически на основе моделей (пакет entity), к созданию которых мы сейчас перейдем.
4. Добавление сущностей, контроллеров, сервисов, репозиториев и представлений
4.1. Добавление сущностей (моделей)
Обязательное требование для всех сущностей: приватные поля, геттеры и сеттеры для всех полей и пустой конструктор (в примерах не представлены). Их не нужно писать вручную, нажмите Alt+Insert и Idea сделает это за вас.
Для импорта необходимых классов и библиотек используем комбинацию клавиш Alt+Enter.
User. В начале об аннотациях: Entity говорит о том, что поля класса имеют отображение в БД, Table(name = «t_user») указывает с какой именно таблицей.
GenerationType.IDENTITY параметр IDENTITY значит, что генерацией id будет заниматься БД. Существует другие стратегии. SEQUENCE – использует встроенный в базы данных, такие как PostgreSQL или Oracle, механизм генерации последовательных значений (sequence). TABLE – используется отдельная таблица с проинициализированным значениями ключей. Еще один вариант – AUTO, hibernate сам выберет из одну вышеописанных стратегий, но рекомендуется указывать стратегию явно.
Поле, находящееся под аннотацией Transient, не имеет отображения в БД. Список ролей связан с пользователем отношением многие ко многим (один пользователь может иметь несколько ролей с одной стороны и у одной роли может быть несколько пользователей с другой);FetchType.EAGER – «жадная» загрузка, т.е. список ролей загружается вместе с пользователем сразу (не ждет пока к нему обратятся).
Для того, чтобы в дальнейшим использовать класс Userв Spring Security, он должен реализовывать интерфейс UserDetails. Для этого нужно переопределить все его методы. Но в нашем примере мы будем использовать только метод getAuthorities(), он возвращает список ролей пользователя. Поэтому для остальных методов измените возвращаемое значение на true.
Role. Этот класс должен реализовывать интерфейс GrantedAuthority, в котором необходимо переопределить только один метод getAuthority() (возвращает имя роли). Имя роли должно соответствовать шаблону: «ROLE_ИМЯ», например, ROLE_USER. Кроме конструктора по умолчанию необходимо добавить еще пару публичных конструкторов: первый принимает только id, второй id и name.
4.2. Реализация слоя доступа к данным и сервисного слоя
Spring Data предоставляет набор готовых реализаций для создания слоя, обеспечивающего доступ к БД. Интерфейс JpaRepository предоставляет набор стандартных методов (findBy, save, deleteById и др.) для работы с БД.
UserRepository. Создаем интерфейс для пользователя в пакете repository и наследуем JpaRepository , указываем класс User и тип его id — Long.
Т.о. просто создав интерфейс и унаследовав JpaRepository можно выполнять стандартные запросы к БД. Если понадобиться специфичный метод просто добавляем его в интерфейс, опираясь на подсказки Idea. Например, нам нужен метод поиска пользователя в БД по имени. Пишем тип возвращаемого объекта, а затем IDE предлагает возможные варианты. Т.е. в данном случае имя метода определяет, тело запроса.
При необходимости можно использовать аннотацию Query над методом и писать запросы на HQL или SQL (нужно добавить nativeQuery = true).
RoleRepository. Создаем аналогично, собственные методы тут не понадобятся.
UserService. Содержит методы для бизнес-логики приложения. Этот класс реализует интерфейс UserDetailsService (необходим для Spring Security), в котором нужно переопределить один метод loadUserByUsername().
В этом классе можно увидеть еще один способ выполнения SQL запроса — с помощью EntityManager.
Рассмотрим метод saveUser(User user).
Сначала происходит поиск в БД по имени пользователя, если пользователь с таким именем уже существует метод заканчивает работу. Если имя пользователя не занято, добавляется роль ROLE_USER. Чтобы не хранить пароль в «сыром» виде он предварительно хэшируется с помощью bCryptPasswordEncoder. Затем новый пользователь сохраняется в БД.
4.3. Добавление контроллеров
RegistrationController. Отдельный контроллер нужен для страницы регистрации. Для обработки GET запроса используется аннотация @GetMapping("/registration"), для POST – @PostMapping("/registration").
Чтобы что-то добавить или получить со страницы мы обращаемся к model. В GET запросе на страницу добавляется новый пустой объект класса User. Это сделано для того, чтобы при POST запросе не доставать данные из формы регистрации по одному (username, password, passwordComfirm), а сразу получить заполненный объект userForm.
Метод addUser() в качестве параметров ожидает объект пользователя (userForm), который был добавлен при GET запросе. Аннотация Valid проверяет выполняются ли ограничения, установленные на поля, в данном случае длина не меньше 2 символов. Если ограничения не были выполнены, то bindingResult будет содержать ошибки.
AdminController. Доступ к странице admin имеют только пользователи с ролью администратора. В методе userList() нет ничего нового, он получает данные всех пользователей и добавляет их на страницу.
Настройки безопасности
WebSecurityConfig. Содержит 2 бина BCryptPasswordEncoder и AuthenticationManager, которые, уже встречались ранее в классе userService.
Кроме этого в методе configure() настраивается доступ к различным ресурсам сайта. В качестве параметров метода antMatchers() передаем пути, для которых хотим установить ограничение. Затем указываем, пользователям с какой ролью будет доступна эта страница/страницы.
4.4. Добавление представлений
index.jsp Главная страница, ниже представлены 2 варианта — для гостя и для авторизованного пользователя.
Для скрытия части контента на странице для авторизованных пользователей (ссылка на страницу регистрации и авторизации) можно использовать тег authorize из библиотеки тегов Spring Security. Параметр access принимает несколько выражений, можно, например, установить ограничение в зависимости от роли пользователя hasRole('ADMIN').
На этой странице используется тег form из библиотеки тегов, с помощью него осуществляется связка атрибута модели userForm (мы добавили его на страницу при GET запросе в контроллере) и формы:
Также необходимо указать путь для привязки свойств userForm:
Эту страницу, как уже говорилось, обрабатывает контроллер Spring'а по умолчанию. Важно указать действие: action="/login" и name инпутов.
5. Запуск приложения
В main класс Application нужно добавить следующее:
Перед тем, как переходить к следующему шагу, убедитесь в том, что структура вашего проекта соответствует представленной в начале.
Пришло время создать пустую БД с именем spring, это нужно сделать перед первым запуском приложения и только один раз.
Можете запустить приложение и посмотреть, как измениться БД – в ней создадутся 3 пустые таблицы. Нужно добавить роли пользователей в таблицу t_role:
Теперь можно попробовать зарегистрироваться. В приложение не предусмотрено метода для регистрации пользователя-администратора, но он нужен для демонстрации. Поэтому после регистрации нового пользователя, добавьте в таблицу пользователь-роли запись, дающую эту роль:
Если после добавления прав администратора вы не можете зайти на страницу администратора (ошибка 403) – перезайдите на сайт.
Заключение
Файлы css и js были созданы, но их содержимое не было представлено. При желании можно добавить дизайн, например, используя Bootstrap и интерактивности с помощью js.
В прошлой статье, мы спроектировали и реализовали простой сервис BookStore.
В этой части мы попытаемся добавить безопасности в наше приложение — сделаем отдельный микросервис аутентификации/авторизации, а в нашем приложении BookStore запретим вызов методов неавторизованными пользователями. И хотя существуют готовые решения (например, Spring Security), мы напишем всё сами, чтобы разобрать принципы работы.
Как можно решить эту задачу? Сервис авторизации может открывать сессию пользователя, выдавая ему идентификатор сессии. С этим идентификатором пользователь может обращаться к требуемому сервису, передавая в параметрах запроса (или в заголовке) идентификатор сессии. Далее сервис, получив этот идентификатор может обратиться к сервису авторизации и проверить, действительно ли такая сессия открыта и можно ли предоставить пользователю доступ к ресурсу.
Сейчас у нас всего один сервис, которому требуется авторизация. Но в реальности таких сервисов может быть очень много. И при такой архитектуре нагрузка на сервис авторизации будет расти лавинообразно. Поэтому хорошо бы авторизовать пользователя однократно, выдав ему что-то, что может приниматься всеми другими системами без валидации на стороне авторизационного сервиса. Для таких целей можно воспользоваться JWT.
JWT (JSON Web Tokens)
JWT — это стандарт, основанный на JSON, для создания токенов доступа. Он состоит из трех частей: заголовка, тела и подписи:
В заголовке указывается тип токена и алгоритм подписи:
В тело записывается необходимая пользовательская информация (payload). Для аутентификации и авторизации это может быть id пользователя, его роль, время действия токена:
- sub — уникальный идентификатор пользователя (subject)
- iat — время выпуска токена в unix-time формате (issued at)
- exp — время действия токена (expiration time). Также в unix-time формате. После окончания действия токена он не должен приниматься вызываемой стороной
Для того чтобы токен выглядел компактно заголовок и тело кодируются алгоритмом Base64-URL, разделяются точками и в конце добавляется подпись.
Основная идея JWT заключается в том, что подписанный токен нельзя подделать, т.к. любое изменение тела или заголовка приведет к невалидности подписи. Секретный ключ, которым подписывается токен, хранится в тайне на сервере. Принимающая токен сторона, зная секретный ключ, может с легкостью проверить подпись — взять тело и заголовок, вычислить HS256 и сравнить с присланным. Если они совпадают, то токен не модифицировался и можно доверять ему содержимому. Это позволяет реализовать следующую архитектуру нашей системы:
Пользователь делает запрос на авторизационный сервис, передавая свои аутентификационные данные (например, логин и пароль), а сервис формирует токен в виде JWT, в котором будет указан id пользователя и его права. Права определяют к каким данным, сервисам он будет иметь доступ. Если это администратор системы, то он может иметь больше прав, чем обычный пользователь.
Получив токен, пользователь будет использовать его при вызове других сервисов. Каждому сервису необходимо дать секретный ключ, с помощью которого он может проверить валидность токена и разрешить пользователю выполнять запрошенные действия. И для этого не потребуется каждый раз вызывать авторизационный сервис.
Когда время действия токена подойдет к концу, вызываемый сервис ответит ошибкой, которая будет указывать на необходимость повторно получить токен на сервисе авторизации.
Для того чтобы пользователь не вводил каждый раз логин и пароль, когда токен заканчивает свое действие, обычно при авторизации выписываются сразу два токена: access token и refresh token. Первый имеет короткий срок жизни и используется для доступа к ресурсам (скажем 5 минут), а второй более длинный (например неделю, месяц). Как только access token заканчивает свое действие, пользователь делает запрос на авторизационный сервис с refresh token, получая в ответ обновленные оба токена. Если пользователь был неактивен длительное время (больше чем срок действия refresh token), то ему придется заново аутентифицироваться, введя свой логин и пароль.
Бывают случаи, когда необходимо отозвать выданные токены. Это может потребоваться, когда токены скомпрометированы или когда пользователь хочет выйти из своего аккаунта на всех устройствах, где он уже входил и получил токены. Такая задача решается использованием черного списка, который хранится на сервере (например, в БД). Отозванные токены будут храниться в нем до истечения срока жизни. Объем такой базы будет намного меньше, чем БД с пользователями.
Auth-сервис
Перейдем от теории к практике и напишем простой сервис, который будет аутентифицировать пользователя и выдавать ему токен доступа. Шаги по созданию проекта ничем не отличаются от предыдущей части, поэтому я их опущу.
Пользователи будут храниться в БД. В идеале это должна быть отдельная БД, но в обучающих целях будем использовать ту же, что использовали в первой части. Опишем DAO пользователя:
Репозиторий будет выглядеть совсем просто:
Т.к. мы будем реализовывать Client Credentials Flow в терминах протокола OAuth, то здесь client — это и есть пользователь. Соответственно clientId, clientSecret — аутентификационные данные пользователя. В открытом виде пароль пользователя хранить нельзя, поэтому будем хранить некий хеш, о котором будет написано ниже.
Опишем сервис, который будет регистрировать нового клиента и проверять его аутентификационные данные:
Для правильного хранения паролей в БД будем использовать Bcrypt. Это криптографическая хеш-функция, основанная на шифре Blowfish. Воспользуемся реализацией в библиотеке jBCrypt, добавим зависимость в проект:
Реализуем интерфейс ClientService :
При регистрации клиента мы генерируем соль вызовом метода BCrypt.gensalt() и используем её для вычисления хеша. В результате получаем строку, содержащую соль и хеш пароля. Полученное значение сохраняем в БД. Пример сгенерированного хеша:
- $2a$10$ — заголовок (алгоритм bcrypt и количество раундов хеширования)
- N9qo8uLOickgx2ZMRZoMye — соль (16 байт)
- IjZAgcfl7p92ldGxad68LJZdL17lhWy — хеш (24 байта)
Теперь нам нужно описать сервис формирования токенов JWT. В качестве библиотеки воспользуется реализацией от Auth0:
Интерфейс и сама реализация сервиса:
Здесь мы формируем JWT из набора claims (утверждений):
- iss(Issuer) — издатель токена
- aud(Audience) — для какого сервиса предназначается токен (в нашем случае для сервиса BookStore)
- sub(Subject) — идентификатор клиента (clientId)
- iat — текущее время формирования токена
- exp — вычисленное время окончания действия токена (выдаем на 5 минут)
Секретный ключ для подписи будет браться из application.properties . Сгенерировать случайный ключ можно следующим образом:
Ответ в случае успешного получения токена:
Ответ в случае ошибки:
Остается описать контроллер:
Укажем в файле application.properties порт, на котором будет запускаться приложение, значение секретного ключа и настройки коннекта к БД:
Запустим приложение и попробуем сгенерировать токен. Для этого зарегистрируем пользователя (считаем, что этот эндпоинт не находится в открытом доступе и это может сделать администратор сервиса):
Используя эти credentials получим токен:
Если расшифровать значение токена (например на сайте jwt.io), то получим следующее содержимое:
Получив такой токен, принимающей стороне необходимо будет сверить подпись и удостовериться в том, что токен выпущен именно для неё (aud) и доверенным сервером (iss).
Авторизация
Нам осталось добавить авторизацию в написанное приложение Bookstore. Откроем проект из предыдущей части и добавим в зависимости библиотеку для работы с JWT (как было показано выше). Также добавим две новых настройки в application.properties:
Одна из них вам уже знакома, а вторую будем использовать для включение/отключения авторизации.
Для проверки полученного токена опишем интерфейс TokenService и его реализацию:
Здесь мы берем значение секретного ключа из настроек приложения, сверяем подпись и проводим все необходимые проверки.
Закроем все эндпоинты и разрешим доступ только при наличии авторизационного токена. Значение авторизационного токена необходимо положить в заголовок следующим образом:
Осталось написать фильтр, который будет осуществлять чтение заголовка и принимать решение об авторизации запроса:
Запускаем второе приложение и пытаемся сделать запрос:
Статус 401 указывает на необходимость авторизоваться. Получим свежий токен и повторим запрос, передав его в заголовке Authorization:
Время действия токена мы выбрали 5 минут, поэтому через этот промежуток времени сервис вернёт статус 403, что будет означать невалидность токена и необходимость получить новый:
В этой части мы познакомились с основами аутентификации и авторизации в веб-приложениях с использованием токенов доступа. В следующей части продолжим улучшать наше приложение, а также познакомимся с паттерном Api Gateway.
В прошлых частях мы успешно спроектировали и запустили два микросервиса: сервис BookStore и сервис аутентификации/авторизации.
Теперь мы можем расположить каждый из них на отдельном инстансе (например в AWS EC2), но в таком случае они будут выглядеть не как одно целое для конечного потребителя. Далее при развитии архитектуры количество сервисов будет только увеличиваться, и нам понадобится что-то, что свяжет наши сервисы и будет маршрутизировать запросы пользователя на каждый из них. Для этих целей используют шаблон проектирования API Gateway, который позволяет реализовать единую точку входа в нашу систему, и перенаправляет запросы на нужный микросервис.
Существует множество реализаций Api Gateway — например, Kong, Gravitee, Krakend и т.д. Мы же воспользуемся решением для экосистемы Spring Cloud — Spring Cloud Gateway.
Spring Cloud Gateway представляет собой Spring-Boot приложение, которое позволяет настраивать маршрутизацию в yaml-конфиге или в коде приложения, а также расширять логику путём написания своего кода. Реализуем схему, при которой все запросы на эндпоинт /registration и /login будут перенаправляться на сервис авторизации, /help будет вести на сайт spring, а остальные запросы будут маршрутизироваться в BookStore. Запускать всё вместе будем на одном инстансе на разных портах (аналогично реализуется на нескольких инстансах).
Начнём с генерации gradle-проекта на start.spring.io. После добавления компонента Gateway должен получиться следующий build.gradle:
На этом всё, остается сконфигурировать маршруты для наших запросов в соответствии со схемой. Как я говорил, конфигурацию можно писать кодом или в yaml-файле, который и будем использовать вместо application.properties :
Добавим теперь правила для сервиса авторизации, продолжив список в yaml-конфигурации (обращая внимание на табуляции, так как лишний tab может привести к ошибке, и приложение не запустится):
Здесь у нас появились фильтры — сущности, позволяющие модифицировать запрос. Полное описание фильтров можно найти в документации. Как можно догадаться из названия, RewritePath модифицирует путь запроса. Приходящие запросы на эндпоинты /registration и /login будут перенаправлены на сервис авторизации на эндпоинты /auth и /auth/token соответственно.
Следуйте за нами на нашей фан-странице, чтобы получать уведомления каждый раз, когда появляются новые статьи. Facebook
1- Почкму используется безопасность?
Аутентификация является процесс в котором авторизация на получение доступа (access privileges) пользователя подтверждается перед тем как они заходят в защищенный регион вебсайта (Website). Есть 2 главных способа аутентификации: базовая аутентификация и на основании формы (Form-based authentication).
С базовой аутентификацией, пользователь может получить доступ ко всем страницам (page) совершенно нормально, для страниц требующих аутентификацию, отобразиться окно чтобы пользователь ввел его username/password. Информация username/password упакуется и отправится вместе с запросом (request) к Server.
Когда пользователь вводит ссылку на браузере и нажимает на Enter для запроса на страницу (page). Информация "User Agent" создаетя и отправляется вместе с запросом. Обычно данная информация включает информацию браузера пользователя, информацию операционной системы. В случае базовой аутентификации (basic authentication) информация username/password упакована внутри "User Agent".
Почти все вебсайты используют аутентификацию на основе форм (Form-based Authentication). Вебсайт разрешает пользователю получить доступ любые обычные страницы не требуя пароль. Но если пользователь получает доступ в защищенную страницу, она перенаправит к странице логина.
В данной статье я детально говорю про способ использования Servlet Filter для безопасности приложения Java Web.
2- Понятие про Role и Principal
- Роль "EMPLOYEE" позволяет использовать функции продажи, и функции создания информации нового покупателя.
- Роль "MANAGER" позволяет использовать функции менеджмента сотрудников, и смотреть отчет прибыли.
Principal можно временно понять как "Субъект" после того как вошли в систему, они имеют права сделать что-то в системе. Один "Субъект" может иметь один или много ролей. Это зависит от разрешения пользоваться приложением для разных пользователей.
3- Безопасность с Servlet Filter
В приложении Java Servlet, один особенный Servlet Filter используется для обработки безопасности, обычно называется Security Filter.
Когда пользователь получает доступ в защищенную страницу (page), Security Filter проверит, если пользователь еще не вошел в систему, запрос пользователя будет перенаправлен (redirect) на страницу логина.
Если пользователь успешно вошел в систему, создается объект Principal, он приносит информацию пользователя, включая все роли.
Если пользователь успешно вошел в систему до этого, и получил доступ в защищенную страницу (page). Security Filter проверит подходят ли роли пользователей для доступа в эту страницу или нет. Если недействительно, он отобразит страницу оповещающую запрет доступа (Access Denied) пользователю.
Читайте также: