Девятая статья серии посвященной технологии Java EE. Вся статья целиком будет просвещенна службе Java Authentication and Authorization Service (JAAS или Jazz), сервис аутентификации и авторизации для платформы Java. JAAS поддерживает аутентификацию пользователей и управление доступом. Он поддерживает Java-реализацию технологии встраиваемых идентификационных модулей (Pluggable Authentication Module), а также классов и интерфейсов для управления допуском пользователей к выполнению важных, с точки зрения безопасности, программ.
JAAS отделяет механизм аутентификации и авторизации пользователей от самой программы, управление этим механизмом производится независимо от главной программы на стороне сервера.
Настройка системы безопасности веб-уровня сводится к двум задачам:
- Предотвращение несанкционированного доступа к защищенному контенту.
- Предотвращение кражи(чтения) защищенного контента во время передачи.
Первая из задач, как правило, представляет собой процесс в котором проверяется является идентификация пользователя и определение имеющихся у него прав, и последующее разрешение или запрет на просмотр определенного контента и использование тех или иных функций системы.
Вторая задача решается с использованием Transport Layer Security (TLS) или предшественником Secure Socket Layers (SSL). О решении второй задачи будет рассказано в следующей статье.
При реализации механизма защиты информации от несанкционированного доступа разделяют два вида схем:
- Декларативная безопасность. Необходимо указать все параметры безопасности для вашего приложения, в том числе требований к проверке подлинности, контроля доступа и безопасности ролей, с помощью аннотаций и / или дескрипторов развертывания. Иными словами, безопасность опирается на механизмы, предоставляемые контейнером Java EE для его управления.
- Программная безопасность. Все параметры безопасности реализуются полностью внутри сервлетов, классов, страниц. Примером такого подхода может служить реализации сервлета перенаправляющего пользователей на соответствующую страницу после успешной авторизации.
if(req.isUserInRole("user")) {
resp.sendRedirect("index");
} else if(req.isUserInRole("administrator")) {
resp.sendRedirect("adminzone");
}
Когда пользователь заходит на сайт, ему могут быть присвоены некоторые "роли", которые предоставляют или запрещают доступ к тем или иным возможностям системы (доступ к определенному контенту или функциям).
В статье будет использована декларативная безопасность.
Создание защищенного контента
Для того что разграничивать доступ к контенту сайта, создадим раздел который будет доступен только для зарегистрированных пользователей.
- В папке WEB-INF создайте новую папку private
- В файле настроек web.xml, на вкладе "Страницы", в имеющейся группе свойств JSP добавьте новый путь, ведущий к созданной нами ранее папке, в шаблоне адресов: /WEB-INF/private/*
- В новой папке создайте jsp файл private_article. Подразумевается что через него будет выводиться статьи для зарегистрированных пользователей.
- Далее необходимо создать сервлет который будет обрабатывать запросы к защищенному контенту, назовем его private_controller.
- Поправьте строку аннотации в созданном сервлете, что бы он перехватывал обращения к пути /private
@WebServlet(name = "private_controller", urlPatterns = {"/private"})
- Заготовка защищаемого контента готова.
Механизм проверки подлиности
Механизм проверки подлинности используется для определения того, как пользователь получает доступ к закрытому контенту. Платформа Java EE поддерживает различные механизмы аутентификации, но в веб индустрии самой популярной является, запрос у пользователя логина и пароля. Для реализации этого механизма используется "Аутентификация на основе форм".
Аутентификация на основе форм имеет главное преимущество в том что, позволяет разработчикам проектировать внешний вид формы входа так, чтобы она лучше подходила визуально под общий интерфейс системы.
Расмотрим алгоритм действий при прохождении проверки на основе форм:
- Когда пользователь обращается к защищенному контенту, будучи при этом не авторизованным, сервер его перенаправляет на страницу ввода логина и пароля.
- После заполнения всех полей он сабмитет форму и отправляет данные на сервер.
- Если все прошло успешно то пользователь перенаправляется на запрашиваемую им страницу.
- Если возникла ошибка то происходит редирект на страницу ошибку, где идет ее описание и дальнейшая повторная попытка авторизации.
Для работы механизма надлежащим образом, необходимо что бы поле action формы всегда имело значение j_security_check
. Это необходимо для условий легкой переносимости приложения.
Так же необходимо использовать следующие имена для полей ввода на форме:
- для поля имени пользователя:
j_username
;
- для поля пароля:
j_password
.
Создание страниц авторизации
Выполните следующие действия:
- В папке private создайте два jsp файла: login.jsp и error.jsp.
- Замените имеющийся код в файле login.jsp следующим:
<h1>Авторизация</h1>
<form action="j_security_check" method="POST">
<div id="loginBox">
<p><strong>Ваш логин:</strong>
<input placeholder="Введите логин" type="text" size="20" name="j_username"></p>
<p><strong>Пароль:</strong>
<input placeholder="Введите пароль" type="password" size="20" name="j_password"></p>
<p><input type="submit" value="Авторизоваться"></p>
</div>
</form>
Как видно, мы учли все условия описанные выше и поле action имеет значение j_security_check.
- Содержимое файла error.jsp замените на:
<h1>Error</h1>
<div id="loginBox">
<p class="error">Ошибка входа</p>
<p>Вернуться <strong><a href="private">к авторизации</a></strong>.</p>
</div>
В нем мы просто сообщаем что произошла ошибка авторизации и предлагаем вернуть на страницу ввода логина и пароля.
Мы подготовили все необходимые страницы для проведения авторизации.
JAAS. Настройка политики безопасности в дескриптере развертывания
Теперь начинается самое интересное. Для начала нам необходимо задать в настройках параметры механизма авторизации.
- Откройте файл web.xml и перейдите на кладку Безопасность.
- Разверните раздел "Настройки входа" и в списке вариантов входа выберите "Формирование".
- Здесь необходимо указать путь к файлу логина и файлу ошибки, а в поле "имя области" (realm) напишите "file".
Немного поподробнее об областях\realms. При определении пользователя и группы на сервере, вы должны указать механизм или область в которой будут храниться данные для проверки, именуется эти области защиты пользовательских данных как realms. Например, учетные данные пользователя могут быть сохранены в локальном текстовом файле, или содержаться в базе данных сертификатов.
GlassFish по умолчанию предоставляет 3 realms'а: file , admin-realm , certificate realms. В нашем приложения для первого примера мы воспользуетмся областью file, которая хранит имя и пароль пользователя в текстовом файле хранящимся на сервере. После мы легко и быстро создадим и переключимся на JDBCRealm который будет брать данные с нашей базы данных.
- Создайте новую роль безопасности private.
- Можете переключиться на просмотр xml файла и увидите код объявления наших настроек безопасности:
<login-config>
<auth-method>FORM</auth-method>
<realm-name>file</realm-name>
<form-login-config>
<form-login-page>/WEB-INF/private/login.jsp</form-login-page>
<form-error-page>/WEB-INF/private/error.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<description/>
<role-name>private</role-name>
</security-role>
- Вернувшись на вкладку "Безопасность" нажмите на кнопку "Добавить ограничения безопасности", назовите ее private_zone. После этого добавьте новый web-ресурс с адресом
private/*
и контролирующего все запросы методов GET и POST (ну или все, нас это не принципиально).
- После этого включите проверку подлинности для созданной нами роли private.
- В исходном виде наше последние действия в файле web.xml выглядит так:
<security-constraint>
<display-name>private_zone</display-name>
<web-resource-collection>
<web-resource-name>private articles</web-resource-name>
<description/>
<url-pattern>/private/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>private</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>file</realm-name>
<form-login-config>
<form-login-page>/WEB-INF/private/login.jsp</form-login-page>
<form-error-page>/WEB-INF/private/error.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<description/>
<role-name>private</role-name>
</security-role>
Уже сейчас Вы можете пройти на страницу http://localhost:80808/myblog/private и уведите страницу авторизации:
Нажмите на кнопку "Авторизоваться" и попадете на страницу ошибки.
Пользователи, группы, роли
Для тех кто уже работал с системы распределенного доступа, вполне понятна схема разделения пользователей по группам, а группы (или пользователей) по ролям. Вкратце объясню не посвященным.
Пользователь является уникальной личностью для сервера. По желанию можно кластер пользователей объединить в группы, которые можно назвать как набор авторизованных пользователей. Для того, чтобы указать, какие пользователи и/или группы имеют доступ к защищенным ресурсам, необходимо создать роли.
В официальной документации от oracle есть хороший рисунок иллюстрирующий приведенное выше описание:
Создание пользователей и групп на сервере
Наш фронт работ перемещается непосредственно на сервер, в консоль администрирования. Напомню что она находится на 4848 порту (http://localhost:4848).
Загрузив консоль пройдите по дереву Configuration->server-config->Security->Realms
. Там Вы уведите 3 предопределенных Realm'а, но нам необходим file, выберите его.
- Загрузите редактор пользователей нажав на кнопку "Manager user".
- Создайте нового пользователя с следующими параметрами:
- User ID: user;
- Group List: private;
- Password: %ваш_пароль%
- Теперь Вы можете пройти авторизацию, однако система все еще не будет пускать в закрытый раздел потому что указанная группа пользователя не сопоставлена ни с одной ролью. Сделать это можно создав дескриптер развертывания glassfish сервера glassfish-web.xml и уже в нем произвести сопоставление ролей и групп путем добавления следующего кода:
<security-role-mapping>
<role-name>private</role-name>
<group-name>private</group-name>
</security-role-mapping>
Однако, у нас очень простой случай, когда имя группы совпадает с именем роли, поэтому можно обойтись более простым путем: в консоли администрирования сервера поднимитесь до ветви Security и там включите опцию Default Principal To Role Mapping
и после этого по умолчанию группы и роли имеющие одно имя будут сопоставляться.
Осталось добавить логики в сервлет, который обрабатывает запросы к закрытому контенту, и наполнить смыслом сам закрытый контент (index.jsp).
- Откройте файл private_cotroller.java и замените обработчик запросов следующим:
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
if ("/private".equals(request.getServletPath())){
request.setAttribute("name", request.getUserPrincipal().getName());
request.getRequestDispatcher("WEB-INF/private/private_article.jsp").
forward(request, response);
}else
if ("/logout".equals(request.getServletPath())){
HttpSession session = request.getSession(false);
if (session!= null){
session.invalidate();
}
response.sendRedirect("/");
}
}
В атрибут name мы передаем имя обратившего пользователя путем вызова функции getUserPrincipal().getNaime()
у экземпляра HttpServletRequest
. Если вы хотите сопоставить роль польозвателя и нужную вам, то это делает методом:
request.isUserInRole("roleName");
Методом
HttpSession session = request.getSession(false);
мы получаем сессию пользователя. Значение false
говорит о том что если сессии нет, то ее создовать не нужно, иначе сервлет автоматически создаст новую сессию для клиента и вернет ее.
1Автор статьи подразумевает что читатель знаком с понятием веб-сессий, если по каким то причинам это не так, то о них можно почитать на примере php здесь.
Далее, что бы отменить авторизацию (произвести выход пользователя из системы) мы убиваем его сессию методом session.invalidate();
.
Сохранение в сессии каких либо значений и извлечение их от туда происходит путем вызова методов:
session.setAttribute("name","value");
session.getAttribute("name");
Осталось добавить в файл private_article.jsp код:
<h1>Добро пожаловать в закрытый раздел, ${name}!</h1>
теперь можете пройти авторизацию и попасть на эту самую страницу.
JDBCRealm. Использование базы данных для хранения данных пользователей
Теперь закрепим знания решив задачу модернизации нашей системы авторизации. Хранить учетные данные пользователей в файле оправданно только если их ничтожно мало и они не изменяемы в длительном промежутке времени, в ином случае правильней использовать для таких целей базу данных. В созданной нами в 3 статье БД уже имеет таблицы users и groupuser, осталось лишь настроить JAAS на использование этих таблиц.
Сервер Glassfish предоставляет возможность использования JDBCRealm'а который использует БД для получания логина, пароля и группы пользователя.
Для перестройки нашего веб приложения потребуется намного меньше телодвижений чем может показаться с начала. Нас всего то и нужно создать новый Realm и объявить его в файле web.xml, сделаем это.
- Вернитесь в консоль администрирования сервера, на вкладку Realms.
- Нажмите кнопку new для создания нового Realm'а.
- В качестве имени напишите JDBCRealmMyblog и выберите класс:
com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
- Далее будет следовать описание полей и значения для их заполнения:
- JAAS Context:
jdbcRealm
- индификатор логин-модуля.
- JNDI:
jdbc/myblog
- JNDI имя JDBC ресурса через которое будет происходит подключение к БД.
- User Table:
users
- Таблица с юзерами
- User Name Column:
login
- Поле в котором хранится имя пользователя.
- Passwrod Column:
pass
- Поле с паролем пользователя.
- Group Table:
groupuser
- Таблица в которой хранятся привязки пользователей к группам.
- Group Table User Name Column:
users_login
- Поле в таблице групп хранящая имя пользователя.
- Group Name Column:
name
- Поле в таблице групп хранящая имя группы.
- Digest Algorithm:
none
- Чаще всего пароли не хранятся в БД открыто, а шифруются различными хэш алгоритмами или даже поточным шифрованием. В этом поле указывается названия алгоритмов шифрования. В нашем случае пароли хранятся открыто и потому ставим none.
- Нажмите "ОК" для создания нового Realm'а.
Осталось только поменять в файле web.xml тэг определения области заменив file на созданный нами:
<realm-name>JDBCRealmMyblog</realm-name>
Создайте в базе данных группу private и привяжите к ней одного из пользователя которого мы зарегистрировали на прошлом уроке.
Теперь можно вновь пройти всю авторизацию и попасть на закрытый раздел сайта.
Аннотации JAAS
Во всех предыдущих статьях мы использовали, где это возможно, аннотации для настроек, что бы избежать мароки с xml файлами. JAAS так же не будет исключением и сейчас добавлением одной строчки в сервлет мы удалим множество строк из дескриптера развертывания.
в private_controller после аннотации объявления сервлета добавьте следующию:
@ServletSecurity( @HttpConstraint(rolesAllowed = {"private"}) )
Этой аннотацией мы объявили группу пользователей с которыми работает данный сервлет. Теперь можете удалить из web.xml код:
<security-constraint>
<display-name>private_zone</display-name>
<web-resource-collection>
<web-resource-name>private articles</web-resource-name>
<description/>
<url-pattern>/private/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>private</role-name>
</auth-constraint>
</security-constraint>
Немного о параметрах:
@ServletSecurity
– предоставляет способ, альтернативный дескриптору развертывания, управлять доступом к WEB- приложениям. Конейнер сервлетов должен обрабатывать и интерпретировать аннотацию @ServletSecurity во всех классах и подклассах реализующих интерфейс javax.servlet.Servlet. Аннотация может иметь два параметра:
value
– имеет тип HttpConstraint и описывает ограничения применяемые ко всем HTTP-методам, не упомянутым в httpMethodConstraints;
httpMethodConstraints
– массив элементов HttpMethodConstraint
. Специальные ограничения для HTTP-методов, таких как GET, POST, и других.
@HttpConstraint
– первый параметр @ServletSecurity
, описывает правила доступа к сервлету и имеет следующие параметры:
value
– действие выполняемое по умолчанию, если массив rolesAllowed
пуст;
массив rolesAllowed – имена ролей, которым разрешен доступ;
transportGuarantee
– требования к транспортному слою текущего соединения, о нем поговорим подробнее в следующей статье.
Разграничивать роли доступа можно так же и в EJB компонентах, для этого используются:
@DeclareRoles
- определяет роли безопасности в пределах работающего приложения или класса.
@RolesAllowed
- определяет список ролей которым осуществлен доступ к тому или иному методу.
Подвидем итоги статьи. Мы познакомились с технологией JAAS, изучили ее теоретическую состовляющию и реализовали практический пример Form аутентификации с использование file Realm и JDBC Realm. Научились создавать настройки безопасности гролей пользователей, разграничать эти настройки по методам (POST,GET ...) и привязывать к ним группы пользователей. Используя консоль администрирования мы создали JDBC Realm который использует БД для получении необходимой информации о пользователе. Мы использовали два метода задания настроек: xml формат в дескрипторе развертывания и @аннотации. Помимо этого мы научились использовать основные возможности веб сессий (получение, дезактивация, добавление и удаление атрибутов).
В следующей статье мы подробно расмотрим возможности использования зашифрованного протокала передачи данных (https).
1Готовый снимок урока можно забрать на GitHub.
Комментарии
Здравствуйте!
Замечательная серия статей, спасибо!
У меня возник вопрос: можно ли (и как) JDBCRealm сконфигурировать не "тыкая мышкой в интерфейсе", а в glassfish-web.xml или ещё где?
Спасибо за статью, очень пригодилась.