Как сделать авторизацию в приложении java
Первый раз когда я услышал о безопасности в ЕЕ приложениях, это вызывало у меня страх. Так как не понятно было вовсе что и как работает. Было масса литературы, но все разбросано по просторам интернета. Сегодня, для тех кто испытывает то что и я когда-то, предоставляю урок, где все будет собрано воедино.
Скорее всего, если вы уже думали о том, чтобы использовать безопасность в своих приложениях, то вы уже знаете для каких целей она вам нужно.
Любой человек может зайти на наш сайт и принести ему вред будь то случайно или умеренно.
Представьте что вы разрабатываете банковскую систему. Вокруг вашего приложения крутятся деньги и не малые. Думаю что всегда найдутся те люди, которые захотят эти деньги получить. И если нет системы защиты и надеяться только на честность людей, то проблем не схлопотать.
Шаг 1. Типы реализации
У нас есть несколько вариантов безопасности, которую мы можем использовать. Начнем с того, что объяснит нам всю суть.
Ручная реализация
Суть состоит в том, что мы берем на себя ответственность за реализацию безопасности в нашем приложении, сами придумываем умные алгоритмы защиты, шифрования, на каждой страничке делаем проверку на правомерность пользователя выполнять действия.
И если что-то пойдет не так, мы допустим ошибку в своем алгоритме, то приложение стает под угрозой перед злоумышленниками.
Реализация с помощью сервера приложений
Естественно безопасность разрабатывается очень часто и есть уже конкретные шаблонны, как именно ее организовывать.
Чтобы уйти от написания безопасности вручную, уменьшить вероятность взлома системы и ускорить разработку каждый может воспользоваться реализацией безопасности, которую предлагает нам сервер приложений. Это хорошо продуманная и гибкая система.
Шаг 2. Основные понятия
Мы будем заниматься именно безопасностью которую предоставляет нам сервер приложений. Для начала, стоит познакомиться с некоторыми понятиями.
Шаг 3. Настройка безопасности
Мы создали основную структуру. Пока что нету ничего кроме статического html. Сейчас все ссылки доступны с приложения и можно дойти к любому файлу. Есть специально создано 2 ресурса: public, secured. Первым делом, ограничим доступ ко всему что находиться внутри secured folder. Для этого создаем внутри webapp директорию WEB-INF, и прописываем дескриптор развертывания web.xml.
Пример файла web.xml:
Результат работы по рут:
Результат, при доступе к закрытой страничке: Forbidden
Теперь у нас есть закрытый доступ к ресурсам, которые находятся по адресу secured/, доступ к содержимому имеет только менеджер. Но как указать что мы являемся менеджером?
Шаг 4. Аутентификация
Добавим теперь нашему приложению возможность аутентификации, которая есть у нас по умолчанию. Методы аутентификации:
При доступе к закрытым ресурсам вы увидите окно, которое попросит вас ввести свои данные.
Создаем нашу форму:
Соглашения, которых стоит придерживаться при использовании безопасности сервера приложений.
Форма(без стилей и ничего лишнего):
Последние два мы не будем рассматривать в нашем примере.
Шаг 5. Настройка сервера приложений
Теперь, осталось настроить наш сервер приложений и связать его с нашим приложением. Для связки, в зависимости от сервера, в приложении используется специальный файл, который имеет название: *-web.xml.
JBoss, WildFly: jboss-web.xml
GlassFish: sun-web.xml
Создаем нужный нам файл в директории WEB-INF.
В данном случае мы будем использовать properties файлы у нас на сервере, что будут хранить данные о пользователях.
Пример файла jboss-web.xml:
Теперь добавим пользователя, у которого будет право на просмотр скрытой информации. Для этого используем консольное приложение которое находиться в директории bin/add-user.sh (Linux), bin/add-user.bat (Windows).
И после корректного логина, мы попадаем на защищенную страницу.
Вот так просто вы добавили безопасность к своему приложению. Такой подход используется не часто, и больше всего распространен подход с использованием БД.
Шаг 6. Использование безопасности в сервлетах
Здесь очень кратко покажу базовые настройки для сервлетов. Для этого, создадим 2 сервлета в нашем проекте.
Есть и остальные параметры, их мы рассмотрим в след. уроках, с практическим примером.
В этой статье будет рассмотрено создание простого веб приложения с использованием 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, Long>, указываем класс 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.
- Аутентификация: идентификация сущности, на которой в данный момент выполняется код.
- Авторизация: После проверки подлинности убедитесь, что этот объект имеет необходимые права управления доступом или разрешения для выполнения конфиденциального кода.
В этом руководстве мы рассмотрим, как настроить JAAS в примере приложения, реализуя и настраивая его различные API, особенно LoginModule .
2. Как работает JAAS
При использовании JAAS в приложении задействовано несколько API:
- CallbackHandler : Используется для сбора учетных данных пользователя и дополнительно предоставляется при создании LoginContext
- Конфигурация : Отвечает за загрузку LoginModule реализаций и может быть дополнительно предоставлена в LoginContext creation
- Модуль входа : Эффективно используется для аутентификации пользователей
Мы будем использовать реализацию по умолчанию для Конфигурации API и предоставим наши собственные реализации для CallbackHandler и LoginModule API.
3. Обеспечение реализации обратного вызова
Прежде чем копаться в реализации LoginModule , нам сначала нужно предоставить реализацию для интерфейса CallbackHandler , который используется для сбора учетных данных пользователя .
У него есть один метод/| handle() , который принимает массив Callback s. Кроме того, JAAS уже предоставляет множество реализаций Обратного вызова , и мы будем использовать NameCallback и PasswordCallback для сбора имени пользователя и пароля соответственно.
Давайте посмотрим на нашу реализацию интерфейса CallbackHandler :
Итак, чтобы запросить и прочитать имя пользователя, мы использовали:
Аналогично, чтобы запросить и прочитать пароль:
Позже мы увидим, как вызвать CallbackHandler при реализации модуля Login .
4. Обеспечение реализации ЛогинМодуля
Для простоты мы предоставим реализацию, которая хранит жестко закодированных пользователей. Итак, давайте назовем его В памяти LoginModule :
В следующих подразделах мы дадим реализацию для более важных методов: initialize() , login () и commit() .
4.1. инициализация()
LoginModule сначала загружается, а затем инициализируется с помощью Subject и CallbackHandler . Кроме того, LoginModule s может использовать Map для обмена данными между собой, а другой Map для хранения личных данных конфигурации:
4.2. вход в систему()
В методе login() мы вызываем метод CallbackHandler.handler() с помощью NameCallback и PasswordCallback для запроса и получения имени пользователя и пароля. Затем мы сравниваем эти предоставленные учетные данные с жестко закодированными:
Метод login() должен возвращать true для успешной операции и false для неудачного входа в систему .
4.3. фиксация()
В противном случае вызывается метод abort () .
На данный момент наша реализация модуля Login готова и должна быть настроена таким образом, чтобы ее можно было динамически загружать с помощью поставщика услуг Configuration .
JAAS использует Конфигурацию поставщика услуг для загрузки Модуля входа s во время выполнения. По умолчанию он предоставляет и использует реализацию Config File , в которой Login Module s настраиваются через файл входа. Например, вот содержимое файла, используемого для нашего модуля Login :
Как мы видим, мы предоставили полное имя класса LoginModule implementation , флаг required и опцию для отладки.
Наконец, обратите внимание, что мы также можем указать файл входа в систему через java.security.auth.login.config системное свойство:
Мы также можем указать один или несколько файлов входа через свойство login.config.url в файле безопасности Java, $/jre/lib/security/java.security :
6. Аутентификация
Во-первых, приложение инициализирует процесс аутентификации, создавая LoginContext экземпляр . Для этого мы можем взглянуть на полный конструктор, чтобы иметь представление о том, что нам нужно в качестве параметров:
- имя : используется в качестве индекса для загрузки только соответствующего LoginModule s
- тема : представляет пользователя или службу, которая хочет войти в систему
- CallbackHandler : отвечает за передачу учетных данных пользователя из приложения в модуль Login
- config : отвечает за загрузку Модуля входа s, соответствующих параметру name
Здесь мы будем использовать перегруженный конструктор, в котором мы будем предоставлять нашу реализацию CallbackHandler :
Теперь, когда у нас есть CallbackHandler и настроенный Модуль входа в систему , мы можем запустить процесс аутентификации, инициализировав LoginContext объект :
На этом этапе мы можем вызвать метод login() для аутентификации пользователя :
Метод login () , в свою очередь, создает новый экземпляр нашего модуля Login и вызывает его метод login () . И, после успешной аутентификации, мы можем получить аутентифицированный Субъект :
Теперь давайте запустим пример приложения с подключенным модулем Login :
Когда нам будет предложено ввести имя пользователя и пароль, мы будем использовать testuser и testpassword в качестве учетных данных.
7. Авторизация
7.1. Определение разрешений
Право управления доступом или разрешение-это возможность выполнить действие над ресурсом . Мы можем реализовать разрешение, создав подкласс абстрактного класса Permission . Для этого нам нужно указать имя ресурса и набор возможных действий. Например, мы можем использовать Разрешение файла для настройки прав управления доступом к файлам. Возможные действия: чтение , запись , выполнение и так далее. Для сценариев, в которых действия не требуются, мы можем просто использовать разрешение Basic .
Затем мы предоставим реализацию разрешения через класс ResourcePermission , в котором пользователи могут иметь разрешение на доступ к ресурсу:
Позже мы настроим запись для этого разрешения с помощью политики безопасности Java.
7.2. Предоставление Разрешений
Обычно нам не нужно знать синтаксис файла политики, потому что мы всегда можем использовать инструмент Policy/| для его создания. Давайте взглянем на наш файл политики:
В этом примере мы предоставили разрешение test_resource пользователю testuser .
7.3. Проверка разрешений
Как и аутентификация, мы запустим простое приложение для авторизации, где в дополнение к модулю Login мы предоставим файл конфигурации разрешений:
8. Заключение
В этой статье мы продемонстрировали, как реализовать JAAS, изучив основные классы и интерфейсы и показав, как их настроить. В частности, мы внедрили модуль входа поставщика услуг .
В этом уроке мы продолжим увеличивать свои способности в настройке пользовательского интерфейса своих приложений и оборудовать их все более серьезными вещами. На этот раз мы оснастим свое Android приложение функцией входа в приложение по вводу логина и пароля. Это может пригодится для многих приложений, да и просто интересно, как это делается. Все довольно просто, ничего сложного в реализации этой возможности не будет.
Здесь пригодится вспомнить простенький урок о переходе между двумя экранами, этот прием встречался уже неоднократно, поэтому разъяснений по нему делать уже не буду.
Создаем новый проект, выбираем Blank Activity. Для начала создадим пользовательский интерфейс для приложения. Он будет состоять из полей ввода логина/пароля и кнопки для совершения входа. Открываем файл activity_main.xml и добавляем туда следующее:
Мы получили вот такой вид пользовательского интерфейса:
Сразу разберемся со вторым экраном, на который будет совершаться переход в случае успешного ввода логина и пароля. Создаем новый класс по имени Second.java:
И соответствующий ему layout файл по имени second_activity.xml:
Кстати, не забудьте добавить вторую activity в файл манифеста AndroidManifest.xml:
Проверяем работоспособность своего творения:
Вот так, все отлично работает, теперь мы можем сделать свое приложение насколько крутым, что им смогут пользоваться только знающие данные логина и пароля для входа.
Читайте также: