JAVA EE: Разработка web-приложения. JAAS. Session.
Поиск по сайту:
О разработчеке сайта
Header image

Новые статьи и уроки JAVA

JAVA EE: Разработка web-приложения. JAAS. Session.

Девятая статья серии посвященной технологии Java EE. Вся статья целиком будет просвещенна службе Java Authentication and Authorization Service (JAAS или Jazz), сервис аутентификации и авторизации для платформы Java. JAAS поддерживает аутентификацию пользователей и управление доступом. Он поддерживает Java-реализацию технологии встраиваемых идентификационных модулей (Pluggable Authentication Module), а также классов и интерфейсов для управления допуском пользователей к выполнению важных, с точки зрения безопасности, программ.

JAAS отделяет механизм аутентификации и авторизации пользователей от самой программы, управление этим механизмом производится независимо от главной программы на стороне сервера.

Настройка системы безопасности веб-уровня сводится к двум задачам:

  1. Предотвращение несанкционированного доступа к защищенному контенту.
  2. Предотвращение кражи(чтения) защищенного контента во время передачи.

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

Вторая задача решается с использованием Transport Layer Security (TLS) или предшественником Secure Socket Layers (SSL). О решении второй задачи будет рассказано в следующей статье.

При реализации механизма защиты информации от несанкционированного доступа разделяют два вида схем:

  1. Декларативная безопасность. Необходимо указать все параметры безопасности для вашего приложения, в том числе требований к проверке подлинности, контроля доступа и безопасности ролей, с помощью аннотаций и / или дескрипторов развертывания. Иными словами, безопасность опирается на механизмы, предоставляемые контейнером Java EE для его управления.
  2. Программная безопасность. Все параметры безопасности реализуются полностью внутри сервлетов, классов, страниц. Примером такого подхода может служить реализации сервлета перенаправляющего пользователей на соответствующую страницу после успешной авторизации.
    if(req.isUserInRole("user")) {
           resp.sendRedirect("index");
    } else if(req.isUserInRole("administrator")) {
           resp.sendRedirect("adminzone");
    }

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

В статье будет использована декларативная безопасность.

1Для работы Вам может понадобится снимок с прошлого урока. Забрать можно здесь.

Создание защищенного контента

Для того что разграничивать доступ к контенту сайта, создадим раздел который будет доступен только для зарегистрированных пользователей.

  1. В папке WEB-INF создайте новую папку private
    Создание новой папки
  2. В файле настроек web.xml, на вкладе "Страницы", в имеющейся группе свойств JSP добавьте новый путь, ведущий к созданной нами ранее папке, в шаблоне адресов: /WEB-INF/private/*
    Редактирование группы свойств JSP
  3. В новой папке создайте jsp файл private_article. Подразумевается что через него будет выводиться статьи для зарегистрированных пользователей.
    Создание нового jsp файла
  4. Далее необходимо создать сервлет который будет обрабатывать запросы к защищенному контенту, назовем его private_controller.
    Создание нового сервлета
  5. Поправьте строку аннотации в созданном сервлете, что бы он перехватывал обращения к пути /private
    @WebServlet(name = "private_controller", urlPatterns = {"/private"})
  6. Заготовка защищаемого контента готова.

Механизм проверки подлиности

Механизм проверки подлинности используется для определения того, как пользователь получает доступ к закрытому контенту. Платформа Java EE поддерживает различные механизмы аутентификации, но в веб индустрии самой популярной является, запрос у пользователя логина и пароля. Для реализации этого механизма используется "Аутентификация на основе форм".

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

Расмотрим алгоритм действий при прохождении проверки на основе форм:

Алгоритм авторизации

 

  1. Когда пользователь обращается к защищенному контенту, будучи при этом не авторизованным, сервер его перенаправляет на страницу ввода логина и пароля.
  2. После заполнения всех полей он сабмитет форму и отправляет данные на сервер.
  3. Если все прошло успешно то пользователь перенаправляется на запрашиваемую им страницу.
  4. Если возникла ошибка то происходит редирект на страницу ошибку, где идет ее описание и дальнейшая повторная попытка авторизации.

Для работы механизма надлежащим образом, необходимо что бы поле action формы всегда имело значение j_security_check.  Это необходимо для условий легкой переносимости приложения.

Так же необходимо использовать следующие имена для полей ввода на форме:

  • для поля имени пользователя: j_username;
  • для поля пароля: j_password.

  Создание страниц авторизации

Выполните следующие действия:

  1. В папке private создайте два jsp файла: login.jsp и error.jsp.
  2. Замените имеющийся код в файле 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.
  3. Содержимое файла error.jsp замените на:
        <h1>Error</h1>
        <div id="loginBox">
        <p class="error">Ошибка входа</p>
        <p>Вернуться <strong><a href="private">к авторизации</a></strong>.</p>
        </div>
    В нем мы просто сообщаем что произошла ошибка авторизации и предлагаем вернуть на страницу ввода логина и пароля.

Мы подготовили все необходимые страницы для проведения авторизации.

JAAS. Настройка политики безопасности в дескриптере развертывания

Теперь начинается самое интересное. Для начала нам необходимо задать в настройках параметры механизма авторизации.

  1. Откройте файл web.xml и перейдите на кладку Безопасность.
  2. Разверните раздел "Настройки входа" и в списке вариантов входа выберите "Формирование".
  3. Здесь необходимо указать путь к файлу логина и файлу ошибки, а в поле "имя области" (realm) напишите "file".
    Настройки входа
    Немного поподробнее об областях\realms. При определении пользователя и группы на сервере, вы должны указать механизм или область в которой будут храниться данные для проверки, именуется эти области защиты пользовательских данных как realms. Например, учетные данные пользователя могут быть сохранены в локальном текстовом файле, или содержаться в базе данных сертификатов.

    GlassFish по умолчанию предоставляет 3 realms'а: file , admin-realm , certificate realms. В нашем приложения для первого примера мы воспользуетмся областью file, которая хранит имя и пароль пользователя в текстовом файле хранящимся на сервере. После мы легко и быстро создадим и  переключимся на JDBCRealm который будет брать данные с нашей базы данных.
  4. Создайте новую роль безопасности private.
    Создание роли безопасности
  5. Можете переключиться на просмотр 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>
  6. Вернувшись на вкладку "Безопасность" нажмите на кнопку "Добавить ограничения безопасности", назовите ее private_zone. После этого добавьте новый web-ресурс с адресом private/* и контролирующего все запросы методов GET и POST (ну или все, нас это не принципиально).
    Добавление нового веб-ресурса к ограничениям безопасности
  7. После этого включите проверку подлинности для созданной нами роли private.
    Ограничения безопасности
  8. В исходном виде наше последние действия в файле 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, выберите его.
Realms

  1. Загрузите редактор пользователей нажав на кнопку "Manager user".
  2. Создайте нового пользователя с следующими параметрами:
    • User ID: user;
    • Group List: private;
    • Password: %ваш_пароль%
    Создание нового пользователя в Realm File
  3. Теперь Вы можете пройти авторизацию, однако система все еще не будет пускать в закрытый раздел потому что указанная группа пользователя не сопоставлена ни с одной ролью. Сделать это можно создав дескриптер развертывания 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 и после этого по умолчанию группы и роли имеющие одно имя будут сопоставляться.
    Default Principal To Role Mapping

Осталось добавить логики в сервлет, который обрабатывает запросы к закрытому контенту, и наполнить смыслом сам закрытый контент (index.jsp).

  1. Откройте файл 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, сделаем это.

  1. Вернитесь в консоль администрирования сервера, на вкладку Realms.
  2. Нажмите кнопку new для создания нового Realm'а.
  3. В качестве имени напишите JDBCRealmMyblog и выберите класс:
    com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
  4. Далее будет следовать описание полей и значения для их заполнения:
    • 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.
    • Настройка JDBCRealm
  5. Нажмите "ОК" для создания нового 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.

12.10.2012

Комментарии

Здравствуйте!

Замечательная серия статей, спасибо!

У меня возник вопрос: можно ли (и как) JDBCRealm сконфигурировать не "тыкая мышкой в интерфейсе", а в glassfish-web.xml или ещё где?

05.03.2015 Автор: Alexander

Спасибо за статью, очень пригодилась.

23.10.2016 Автор: username