Проведення автентифікації користувача в Java EE / JSF за допомогою j_security_check


156

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

Це не використовуючи цілий інший фреймворк, наприклад Spring-Security (acegi) або Seam, але намагаємось сподіватися на нову платформу Java EE 6 (веб-профіль), якщо це можливо.

Відповіді:


85

Після пошуку в Інтернеті та спробу багатьох різних способів, ось що я запропонував би для автентифікації Java EE 6:

Налаштування сфери безпеки:

У моєму випадку я мав користувачів у базі даних. Тому я дотримувався цієї публікації в блозі, щоб створити Realm JDBC, який міг би автентифікувати користувачів на основі імені користувача та паролів хешованих MD5 в таблиці моєї бази даних:

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

Примітка: публікація розповідає про користувача та групову таблицю в базі даних. У мене був клас користувача з атрибутом enum UserType, відображеним за допомогою приміток javax.persistent до бази даних. Я налаштував область із тією ж таблицею для користувачів та груп, використовуючи стовпець userType, як стовпець групи, і він працював чудово.

Використовуйте автентифікацію форми:

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

Потім скористайтеся порадою BalusC вище про ледачий ініціалізацію інформації про користувача з бази даних. Він запропонував зробити це в керованому вигляді, отримавши головне з контексту обличчя. Натомість я використовував значну частину сеансу для зберігання інформації про сеанс для кожного користувача, тому я ввів контекст сеансу:

 @Resource
 private SessionContext sessionContext;

З принципалом я можу перевірити ім'я користувача та за допомогою менеджера сутностей EJB отримати інформацію про користувача з бази даних та зберігати в моєму SessionInformationEJB.

Вийти:

Я також роздивився навколо найкращого способу виходу. Найкращий, який я знайшов, - це використання сервлета:

 @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, як і я.

Чіао,

Вітор Суза


15
Невелике слово поради: ви використовуєте request.getSession (помилково) і викликуте недійсний () для цього. request.getSession (false) може повернути нуль, якщо немає сеансу. Краще перевірте, чи спочатку це недійсне;)
Арджан Тіджмс

@Vitor: Привіт. Чи хотіли б ви сказати щось про те, коли добре перейти від безпеки на базі контейнера до альтернативних варіантів, таких як shiro чи інші? Більше сфокусованого запитання дивіться тут: stackoverflow.com/questions/7782720/…
Rajat Gupta

Схоже, що JDBC Realm Glassfish не підтримує збереження солоних хешей паролів. Чи справді найкраща практика використовувати його в такому випадку?
Лій

Вибачте, не можу тобі допомогти. Я не експерт із Скляних рибок. Може, задайте це питання новою темою, щоб побачити, що кажуть люди?
Vítor E. Silva Souza

1
Лій, ти можеш працювати з солоним, використовуючи контейнер зі скляною рибою. Налаштуйте своє тіло, щоб не використовувати хеш. Він порівняє звичайне значення, яке ви вставляєте для пароля HttpServletResponse#login(user, password), таким чином, ви можете просто отримати від БД користувачу сіль, ітерації та все, що ви використовуєте для засолювання, хеш-пароль, який користувач ввів за допомогою цієї солі, а потім попросить контейнер пройти автентифікацію на HttpServletResponse#login(user, password).
emportella

152

Я припускаю, що ви хочете перевірити автентифікацію на основі форми, використовуючи дескриптори розгортання та j_security_check.

Ви також можете зробити це в JSF, просто використовуючи ті ж predefinied імена полів j_usernameі , j_passwordяк показано в керівництві.

Напр

<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>

Ви можете здійснити ледачу завантаження в Usergetter, щоб перевірити, чи 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значення null!). Ви можете отримати ручку HttpServletRequestв JSF за допомогою ExternalContext#getRequest(). Ви також можете повністю визнати недійсним сеанс.

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

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


Оновлення : ви також можете використовувати новий Servlet 3.0 HttpServletRequest#login()для здійснення програмного входу, а не використання j_security_checkякого може бути недоступним диспетчером у деяких контейнерах сервлетів. В цьому випадку ви можете використовувати fullworthy форми JSF і боб з 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>

І цей перегляд охоплював керований боб, який також запам'ятовує спочатку запитувану сторінку:

@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}.


1
Я оновив питання, щоб включити відмову від відповідальності, що диспетчеризація j_security_checkможе працювати не на всіх контейнерах сервлетів.
BalusC

1
Посилання з підручника 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"})})
ngeek

3
І ваша думка бути ..? Чи застосовується це в JSF? Ну, в JSF є лише один сервлет, FacesServletі ви не можете (і не хочете) його змінювати.
BalusC

1
@BalusC - Коли ти кажеш, що вище - це найкращий спосіб, ти маєш на увазі використання j_security_check або програмного входу?
simgineer

3
@simgineer: запитувана URL-адреса доступна як атрибут запиту з іменем, визначеним у RequestDispatcher.FORWARD_REQUEST_URI. Атрибути запиту розміщені у JSF, доступні ExternalContext#getRequestMap().
BalusC

7

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


4

Проблема HttpServletRequest.login не встановлює стан аутентифікації в сесії , було виправлено в 3.0.1. Оновіть скляну рибку до останньої версії, і ви закінчите.

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

glassfishv3/bin/pkg set-authority -P dev.glassfish.org
glassfishv3/bin/pkg image-update

4
посилання розірвано. на яке питання ви зверталися?
simgineer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.