Выполнение аутентификации пользователя в Java EE / JSF с использованием j_security_check

Мне интересно, каков текущий подход к аутентификации пользователя для веб-приложения, использующего JSF 2.0 (и если какие-либо компоненты существуют) и основные механизмы Java EE 6 (вход / проверка разрешений / выход из системы) с информацией о пользователе, хранящейся в JPA организация. Учебник Oracle Java EE немного скуден (обрабатывает только сервлеты).

Это без использования целого другого фреймворка, такого как Spring-Security (acegi) или Seam, но попытка, надеюсь, придерживаться новой платформы Java EE 6 (веб-профиль), если это возможно.


person ngeek    schedule 05.02.2010    source источник


Ответы (4)


После поиска в Интернете и опробования множества различных способов вот что я бы посоветовал для аутентификации Java EE 6:

Настройте область безопасности:

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

http://blog.gamatam.com/2009/11/jdbc-realm-setup-with-glassfish-v3.html

Примечание: в сообщении говорится о пользователе и таблице группы в базе данных. У меня был класс User с атрибутом перечисления UserType, сопоставленным с базой данных с помощью аннотаций javax.persistence. Я настроил область с той же таблицей для пользователей и групп, используя столбец userType в качестве столбца группы, и он работал нормально.

Использовать аутентификацию с помощью формы:

По-прежнему следуя приведенному выше сообщению в блоге, настройте свои web.xml и sun-web.xml, но вместо использования BASIC-аутентификации используйте FORM (на самом деле, не имеет значения, какой из них вы используете, но в итоге я использовал FORM). Используйте стандартный HTML, а не JSF.

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

 @Resource
 private SessionContext sessionContext;

С помощью принципала я могу проверить имя пользователя и, используя EJB Entity Manager, получить информацию о пользователе из базы данных и сохранить в моем SessionInformation EJB.

Выйти:

Я также искал лучший способ выйти из системы. Лучшее, что я нашел, - это использование сервлета:

 @WebServlet(name = "LogoutServlet", urlPatterns = {"/logout"})
 public class LogoutServlet extends HttpServlet {
  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
   HttpSession session = request.getSession(false);

   // Destroys the session for this user.
   if (session != null)
        session.invalidate();

   // Redirects back to the initial page.
   response.sendRedirect(request.getContextPath());
  }
 }

Хотя мой ответ очень поздний, учитывая дату вопроса, я надеюсь, что это поможет другим людям, которые попадают сюда из Google, так же, как и я.

Чао,

Витор Соуза

person Vítor E. Silva Souza    schedule 08.06.2010
comment
Небольшой совет: вы используете request.getSession (false) и вызываете для этого invalidate (). request.getSession (false) может вернуть null, если сеанса нет. Лучше сначала проверьте, является ли он нулевым;) - person Arjan Tijms; 24.12.2010
comment
@Vitor: Привет .. Не могли бы вы сказать что-нибудь о том, когда лучше перейти от безопасности на основе контейнеров к альтернативам, таким как Сиро или другим? См. Более конкретный вопрос здесь: stackoverflow.com/questions/7782720/ - person Rajat Gupta; 16.10.2011
comment
Похоже, что Glassfish JDBC Realm не поддерживает хранение хэшей с солеными паролями. Действительно ли лучше всего использовать его в таком случае? - person Lii; 19.09.2014
comment
Извините, ничем не могу вам помочь. Я не эксперт по Glassfish. Может быть, задать этот вопрос в новой ветке, чтобы узнать, что говорят люди? - person Vítor E. Silva Souza; 24.09.2014
comment
Лии, ты можешь работать с солёным, используя контейнер для стеклянной рыбы. Настройте свое исцеление, чтобы не использовать хеш. Он сравнит простое значение, которое вы вставляете для пароля на HttpServletResponse#login(user, password), таким образом вы можете просто получить из БД соль пользователя, итерации и все, что вы используете для соления, хешировать пароль, введенный пользователем с помощью этой соли, а затем попросить контейнер аутентифицировать с HttpServletResponse#login(user, password). - person emportella; 08.10.2014
comment
emportella, звучит как отличное решение! Это последняя недостающая информация для этого очень полезного ответа. - person Lii; 28.10.2014
comment
Также см. этот вопрос. Существует модуль входа с открытым исходным кодом для Glassfish и Firefly, который использует соленые пароли: m9aertner / PBKDF2. Это работает хорошо. - person Stijn de Witt; 28.08.2016

Я полагаю, вам нужна аутентификация на основе форм с использованием дескрипторы развертывания и j_security_check.

Вы также можете сделать это в JSF, просто используя те же предопределенные имена полей j_username и j_password, как показано в руководстве.

E.g.

<form action="j_security_check" method="post">
    <h:outputLabel for="j_username" value="Username" />
    <h:inputText id="j_username" />
    <br />
    <h:outputLabel for="j_password" value="Password" />
    <h:inputSecret id="j_password" />
    <br />
    <h:commandButton value="Login" />
</form>

Вы можете выполнить отложенную загрузку в геттере User, чтобы проверить, вошел ли уже User в систему, а если нет, то проверьте, присутствует ли Principal в запросе, и если да, то получите User, связанный с j_username.

package com.stackoverflow.q2206911;

import java.io.IOException;
import java.security.Principal;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;

@ManagedBean
@SessionScoped
public class Auth {

    private User user; // The JPA entity.

    @EJB
    private UserService userService;

    public User getUser() {
        if (user == null) {
            Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
            if (principal != null) {
                user = userService.find(principal.getName()); // Find User by j_username.
            }
        }
        return user;
    }

}

User, очевидно, доступен в JSF EL пользователем #{auth.user}.

Для выхода выполните HttpServletRequest#logout() (и установите User в ноль!). Вы можете получить дескриптор HttpServletRequest в JSF с помощью _ 16_. Вы также можете полностью аннулировать сеанс.

public String logout() {
    FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
    return "login?faces-redirect=true";
}

Для остатка (определение пользователей, ролей и ограничений в дескрипторе развертывания и области) просто следуйте руководству по Java EE 6 и документации по сервлету как обычно.


Обновление: вы также можете использовать новый сервлет 3.0 _ 18_, чтобы выполнить программный вход вместо использования j_security_check, который сам по себе может быть недоступен диспетчеру в некоторых servletcontainers. В этом случае вы можете использовать полноценную JSF-форму и bean-компонент со свойствами username и password и login методом, которые выглядят следующим образом:

<h:form>
    <h:outputLabel for="username" value="Username" />
    <h:inputText id="username" value="#{auth.username}" required="true" />
    <h:message for="username" />
    <br />
    <h:outputLabel for="password" value="Password" />
    <h:inputSecret id="password" value="#{auth.password}" required="true" />
    <h:message for="password" />
    <br />
    <h:commandButton value="Login" action="#{auth.login}" />
    <h:messages globalOnly="true" />
</h:form>

И этот управляемый bean-компонент с областью видимости, который также запоминает изначально запрошенную страницу:

@ManagedBean
@ViewScoped
public class Auth {

    private String username;
    private String password;
    private String originalURL;

    @PostConstruct
    public void init() {
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        originalURL = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_REQUEST_URI);

        if (originalURL == null) {
            originalURL = externalContext.getRequestContextPath() + "/home.xhtml";
        } else {
            String originalQuery = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_QUERY_STRING);

            if (originalQuery != null) {
                originalURL += "?" + originalQuery;
            }
        }
    }

    @EJB
    private UserService userService;

    public void login() throws IOException {
        FacesContext context = FacesContext.getCurrentInstance();
        ExternalContext externalContext = context.getExternalContext();
        HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();

        try {
            request.login(username, password);
            User user = userService.find(username, password);
            externalContext.getSessionMap().put("user", user);
            externalContext.redirect(originalURL);
        } catch (ServletException e) {
            // Handle unknown username/password in request.login().
            context.addMessage(null, new FacesMessage("Unknown login"));
        }
    }

    public void logout() throws IOException {
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        externalContext.invalidateSession();
        externalContext.redirect(externalContext.getRequestContextPath() + "/login.xhtml");
    }

    // Getters/setters for username and password.
}

Таким образом, User будет доступен в JSF EL через #{user}.

person BalusC    schedule 05.02.2010
comment
Я обновил вопрос, включив в него заявление об отказе от ответственности, что отправка в j_security_check может работать не на всех контейнерах сервлетов. - person BalusC; 05.02.2010
comment
Ссылка из руководства Java по использованию программной безопасности с веб-приложениями: java .sun.com / javaee / 6 / docs / tutorial / doc / gjiie.html (с использованием сервлетов): в классе сервлета вы можете использовать: @WebServlet(name="testServlet", urlPatterns={"/ testServlet "}) @ServletSecurity(@HttpConstraint(rolesAllowed = {"testUser", "admin”})) И на уровне каждого метода: @ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"), @HttpMethodConstraint(value="POST", rolesAllowed={"testUser"})}) - person ngeek; 05.02.2010
comment
И ваша точка зрения ..? Применимо ли это в JSF? Что ж, в JSF есть только один сервлет, FacesServlet, и вы не можете (и не хотите) его изменять. - person BalusC; 05.02.2010
comment
Конечно, это было сделано только для полноты картины и перекрестной ссылки, если кто-то хочет посмотреть, как выглядят решения для сборки с вашим собственным сервлетом. - person ngeek; 06.02.2010
comment
Другой подход, по-видимому, заключается в использовании аннотации @SecurityLogin в управляемом компоненте, как описано в статье «Улучшение конфигурации безопасности JSF с помощью защищенных управляемых компонентов» (хотя она была написана на JSF 1.2 раз, но все же кажется полезной для JSF 2.0) на blogs.sun.com/enterprisetechtips/entry/ - person ngeek; 09.02.2010
comment
@BalusC, знаете ли вы о каких-либо контейнерах, которые в настоящее время не поддерживают это, или, точнее, нам следует избегать, если мы хотим сделать Java EE 6 с Servlet 3? Есть ли у кого-то, от кого можно ожидать соответствия, проблемы с этим? - person jamiebarrow; 30.03.2010
comment
На данный момент Servlet 3.0 доступен только в Glassfish v3. Tomcat 7.0 все еще находится в стадии разработки. - person BalusC; 30.03.2010
comment
@BalusC Ваш класс UserManager - SessionScoped. Разве это не непрактично, поскольку вы также ссылаетесь на сущность пользователя в нем? Если вы измените какие-либо атрибуты в пользовательском объекте (в другом месте), ваш пользовательский экземпляр устареет. - person Theo; 10.12.2010
comment
@ Тео: Нет, это не так. Зарегистрированный пользователь должен быть сохранен для всего сеанса. В другом месте нужно просто получить пользователя из диспетчера пользователей и изменить его (желательно с помощью методов диспетчера пользователей), а не получить другой экземпляр. - person BalusC; 10.12.2010
comment
@BalusC Я согласен с тем, что пользовательский принципал должен храниться на уровне сеанса. Но пользовательский объект JPA может содержать намного больше, чем пользовательский принципал. Почему бы не получать свежий экземпляр из базы данных при каждом запросе? Потому что пользовательский объект может быть изменен в некоторых внутренних методах, которые не используют какие-либо классы из внешнего интерфейса (например, UserManager). - person Theo; 10.12.2010
comment
@Theo: потому что это неэффективно в обычном веб-приложении. Однако вы можете просто сделать это, если этого требует конкретная среда / функциональные требования. - person BalusC; 10.12.2010
comment
В итоге я использовал программный вход с HttpServletRequest # login (). Если вы, как и я, ищете способ перенаправить пользователя на страницу, с которой он пришел, я предлагаю рассмотреть этот вопрос ссылка @BalusC, может быть, вы можете сослаться на этот вопрос в обновлении 2? - person fvdnabee; 04.08.2011
comment
@BalusC: Не могли бы вы сказать что-нибудь о том, когда лучше перейти от безопасности на основе контейнеров к альтернативам, таким как shiro или другим? См. Более конкретный вопрос здесь: stackoverflow.com/questions/7782720/ - person Rajat Gupta; 16.10.2011
comment
Я обнаружил одну проблему - при вызове request.login () из реализации HttpServletRequest файл cookie SSO не создается Glassfish 3.1 - вы должны вызывать действие через форму (j_security_check). Это пахнет! - person javabeats; 20.02.2012
comment
Что касается ленивой инициализации, я бы предпочел поместить этот материал в метод bean-компонента @PostConstruct (@PostConstruct void initialize() { if (user == null) { Principal principal =..., и пусть getUser() просто возвращает user. - person DenisGL; 06.04.2012
comment
@Denis: это не сработает, если bean-компонент с областью сеанса создается до входа пользователя в систему. - person BalusC; 06.04.2012
comment
Что делать, если у меня нет выбора, например, JavaEE 5 + JSF 2. Есть ли способ достичь того же результата, что и HttpServletRequest # login? - person setzamora; 13.04.2012
comment
@BalusC - Когда вы говорите, что это лучший способ, вы имеете в виду использование j_security_check или программный вход? - person simgineer; 18.06.2012
comment
@simgineer: программный вход. Это позволяет более детально контролировать проверку. - person BalusC; 18.06.2012
comment
@BalusC - я переключился в программный режим, как было предложено, чтобы я мог сразу установить экземпляр пользователя без использования фильтра, однако, когда я использую указанный выше метод входа, он не переводит меня на запрошенный URL-адрес после успешного входа в систему. Есть ли дополнительные шаги, которые мы должны сделать, чтобы пользователь был перенаправлен или перенаправлен на исходный ресурс, который он запросил? Или, может быть, вы знаете, что я неправильно настроил? - person simgineer; 25.06.2012
comment
@simgineer: запрошенный URL-адрес доступен как атрибут запроса с именем, определенным RequestDispatcher.FORWARD_REQUEST_URI. Атрибуты запроса в JSF доступны ExternalContext#getRequestMap(). - person BalusC; 25.06.2012
comment
@BalusC - это правильный вызов для получения карты в UserBean.login()? Map<String, String> rpMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap(); Если это так, на моей карте есть 5 записей, в которых не было запрошенного исходного ресурса. Вот содержимое моей карты: rpmap: key: j_idt25 value: j_idt25 key: j_idt25:username value: admin key: j_idt25:password value: admin key: j_idt25:j_idt33 value: Login key: javax.faces.ViewState value: 1617763866821071380:1071397943707136043 - person simgineer; 25.06.2012
comment
Чтобы узнать, как добавить добавление пароля к этому решению (что вы должны), см. Комментарий emportella к ответу Витора Э. Сильвы Соуза. - person Lii; 29.10.2014
comment
@BalusC, в случае использования метода {{HttpServletRequest # login ()}}, как настроить {{login-config}} в web.xml? И как добиться, чтобы страница входа отображалась при попытке доступа к ограниченным ресурсам без входа в систему? - person Vsevolod Golovanov; 04.03.2015
comment
@Vsevolod: Точно так же. Единственное отличие состоит в том, что страница входа отправляется через JSF-форму в JSF-компонент с HttpServletRequest#login(), а не через простую HTML-форму в /j_security_check. - person BalusC; 04.03.2015
comment
@BalusC, также должен быть неограниченный доступ к странице входа, это была моя проблема. - person Vsevolod Golovanov; 04.03.2015
comment
@BalusC Сейчас я тоже использую программный вход. Я провожу некоторые проверки перед использованием request.login, чтобы узнать, подтвержден ли пользователь администратором из моего веб-приложения или активен ли пользователь (мое веб-приложение позволяет администраторам действовать и деактивировать пользователей). Мой вопрос в том, что если бы пользователь изменил мою веб-страницу и изменил форму так, чтобы она использовала действие j_security_check, я бы подумал, что он сможет обойти мой программный вход в систему, верно? Мне нужно изменить логин-конфигурацию или что-то в этом роде, чтобы этого не могло произойти. Или у вас есть другое предложение. Я действительно думаю, что это было бы непохоже на сценарий, но это могло быть паршиво. - person Wesley Egbertsen; 12.11.2015
comment
@BalusC Я подтвердил и с помощью консоли разработчика в браузере заменил html, где была форма из JSF, на чистую HTML-форму с материалом j_security_check и мог войти в систему. У меня есть идея установить переменную в сеансе только при программном входе в систему и в фильтре в защищенной области проверить, установлена ​​ли эта переменная, если не аннулировать сеанс и перенаправить на страницу входа. - person Wesley Egbertsen; 12.11.2015
comment
@BalusC Почему вы поместили объект пользователя в карту сеанса? у объекта есть поле пароля, разве это не угроза безопасности? Должен ли я очистить поле пароля перед добавлением его в sessionMap? - person usertest; 08.09.2016
comment
@usertest: где в коде вы видели, что сущность User имеет свойство password? - person BalusC; 08.09.2016
comment
@BalusC User user = userService.find (имя пользователя, пароль); Я предположил, что пользователь является лицом с адресом электронной почты и паролем. Я ошибаюсь? - person usertest; 08.09.2016

Следует отметить, что это вариант полностью оставить вопросы аутентификации на фронт-контроллер, например веб-сервер Apache и вместо этого оцените HttpServletRequest.getRemoteUser (), который является представлением JAVA для переменной среды REMOTE_USER. Это также позволяет использовать сложные конструкции журналов, такие как аутентификация Shibboleth. Фильтрация запросов к контейнеру сервлетов через веб-сервер - хороший вариант для производственной среды, часто для этого используется mod_jk.

person Matthias Ronge    schedule 05.11.2012

Проблема, HttpServletRequest.login не устанавливает состояние аутентификации в сеансе, была исправлена ​​в версии 3.0. 1. Обновите Glassfish до последней версии, и все готово.

Обновление довольно простое:

glassfishv3/bin/pkg set-authority -P dev.glassfish.org
glassfishv3/bin/pkg image-update
person Hans Bacher    schedule 20.05.2010
comment
ссылка не работает. о какой проблеме вы имели в виду? - person simgineer; 13.06.2012