Как реализовать "Оставайтесь в системе", когда пользователь заходит в веб-приложение

На большинстве веб-сайтов, когда пользователь собирается указать имя пользователя и пароль для входа в систему, установите флажок "Оставайтесь в системе". Если вы установите флажок, это позволит вам войти во все сеансы из одного и того же веб-браузера. Как я могу реализовать то же самое в Java EE?

Я использую аутентификацию, управляемую контейнером на основе FORM, с помощью страницы входа в JSF.

<security-constraint>
    <display-name>Student</display-name>
    <web-resource-collection>
        <web-resource-name>CentralFeed</web-resource-name>
        <description/>
        <url-pattern>/CentralFeed.jsf</url-pattern>
    </web-resource-collection>        
    <auth-constraint>
        <description/>
        <role-name>STUDENT</role-name>
        <role-name>ADMINISTRATOR</role-name>
    </auth-constraint>
</security-constraint>
 <login-config>
    <auth-method>FORM</auth-method>
    <realm-name>jdbc-realm-scholar</realm-name>
    <form-login-config>
        <form-login-page>/index.jsf</form-login-page>
        <form-error-page>/LoginError.jsf</form-error-page>
    </form-login-config>
</login-config>
<security-role>
    <description>Admin who has ultimate power over everything</description>
    <role-name>ADMINISTRATOR</role-name>
</security-role>    
<security-role>
    <description>Participants of the social networking Bridgeye.com</description>
    <role-name>STUDENT</role-name>
</security-role>

Ответ 1

Использовать долгоживущий файл cookie для отслеживания уникального клиента и использовать новый API Servlet 3.0 при условии, что он входит в программу HttpServletRequest#login(), когда пользователь не вошел в систему, но файл cookie присутствует.

Это проще всего, если вы управляете пользователями в таблице базы данных, которые предоставляются Glassfish на JDBCRealm и являются той же таблицей, что и ваш объект User. Затем вы можете создать другую таблицу со значением java.util.UUID в качестве PK и идентификатором соответствующего пользователя в качестве FK.

Предположим, что следующая форма входа:

<form action="login" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="checkbox" name="remember" value="true" />
    <input type="submit" />
</form>

И следующее в doPost() методе Servlet, которое отображается на /login:

String username = request.getParameter("username");
String password = hash(request.getParameter("password"));
boolean remember = "true".equals(request.getParameter("remember"));
User user = userDAO.find(username, password);

if (user != null) {
    request.login(user.getUsername(), user.getPassword()); // Password should already be the hashed variant.
    request.getSession().setAttribute("user", user);

    if (remember) {
        String uuid = UUID.randomUUID().toString();
        rememberDAO.save(uuid, user);
        addCookie(response, COOKIE_NAME, uuid, COOKIE_AGE);
    } else {
        rememberDAO.delete(user);
        removeCookie(response, COOKIE_NAME);
    }
}

(COOKIE_NAME должно быть уникальным именем cookie, например "remember", а COOKIE_AGE должно быть временем в секундах, например 2592000 в течение 30 дней)

Здесь, как метод doFilter() для Filter, который отображается на ограниченных страницах, может выглядеть так:

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
User user = request.getSession().getAttribute("user");

if (user == null) {
    String uuid = getCookieValue(request, COOKIE_NAME);

    if (uuid != null) {
        user = rememberDAO.find(uuid);

        if (user != null) {
            request.login(user.getUsername(), user.getPassword());
            request.getSession().setAttribute("user", user); // Login.
            addCookie(response, COOKIE_NAME, uuid, COOKIE_AGE); // Extends age.
        } else {
            removeCookie(response, COOKIE_NAME);
        }
    }
}

if (user == null) {
    response.sendRedirect("login");
} else {
    chain.doFilter(req, res);
}

В сочетании с этими вспомогательными методами cookie (слишком плохо они отсутствуют в API Servlet):

public static String getCookieValue(HttpServletRequest request, String name) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
    }
    return null;
}

public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
    Cookie cookie = new Cookie(name, value);
    cookie.setPath("/");
    cookie.setMaxAge(maxAge);
    response.addCookie(cookie);
}

public static void removeCookie(HttpServletResponse response, String name) {
    addCookie(response, name, null, 0);
}

Несмотря на то, что UUID чрезвычайно трудный для грубой силы, вы можете предоставить пользователю возможность заблокировать опцию "запомнить" для IP-адреса пользователя (request.getRemoteAddr()) и сохранить/сравнить его в базе данных. Это делает его еще более надежным.

Также рекомендуется заменить значение UUID, когда пользователь изменил свой пароль.

Ответ 2

Обычно это выполняется следующим образом:

Когда вы регистрируете пользователя, вы также устанавливаете cookie на клиенте (и сохраняете значение cookie в базе данных), истекающего через определенное время (обычно 1-2 недели).

Когда приходит новый запрос, проверьте, существует ли определенный файл cookie, и если да, посмотрите в базу данных, чтобы узнать, соответствует ли она определенной учетной записи. Если он будет соответствовать, вы "свободно" войдете в эту учетную запись. Когда я говорю свободно, я имею в виду, что вы только позволяете этой сессии читать какую-то информацию и не писать информацию. Вам нужно будет запросить пароль, чтобы разрешить параметры записи.

Это все, что есть. Хитрость заключается в том, чтобы убедиться, что "свободный" логин не может нанести большой вред клиенту. Это несколько защитит пользователя от кого-то, кто схватит его, запомнит мне cookie и попытается войти в систему как он.

Ответ 3

Вы не можете полностью войти в систему через HttpServletRequest.login(имя пользователя, пароль), так как вы не должны хранить как имя пользователя, так и пароль обычного текста в базе данных. Также вы не можете выполнить этот логин с хешем пароля, который сохраняется в базе данных. Тем не менее, вам нужно идентифицировать пользователя с маркером cookie/DB, но введи его в систему без ввода пароля с помощью пользовательского модуля входа (класс Java) на основе API сервера Glassfish.

Для получения дополнительной информации см. следующие ссылки:

http://www.lucubratory.eu/custom-jaas-realm-for-glassfish-3/

Механизм пользовательской безопасности в приложении Java EE 6/7