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

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

Я использую управляемую аутентификацию контейнера на основе формы со страницей входа 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>

3 ответов


Java EE 8 и выше

если вы находитесь на Java EE 8 или новее, поместите @RememberMe на заказ HttpAuthenticationMechanism вместе с RememberMeIdentityStore.

@ApplicationScoped
@AutoApplySession
@RememberMe
public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism {

    @Inject
    private IdentityStore identityStore;

    @Override
    public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {
        Credential credential = context.getAuthParameters().getCredential();

        if (credential != null) {
            return context.notifyContainerAboutLogin(identityStore.validate(credential));
        }
        else {
            return context.doNothing();
        }
    }
}

public class CustomIdentityStore implements RememberMeIdentityStore {

    @Inject
    private UserService userService; // This is your own EJB.

    @Inject
    private LoginTokenService loginTokenService; // This is your own EJB.

    @Override
    public CredentialValidationResult validate(RememberMeCredential credential) {
        Optional<User> user = userService.findByLoginToken(credential.getToken());
        if (user.isPresent()) {
            return new CredentialValidationResult(new CallerPrincipal(user.getEmail()));
        }
        else {
            return CredentialValidationResult.INVALID_RESULT;
        }
    }

    @Override
    public String generateLoginToken(CallerPrincipal callerPrincipal, Set<String> groups) {
        return loginTokenService.generateLoginToken(callerPrincipal.getName());
    }

    @Override
    public void removeLoginToken(String token) {
        loginTokenService.removeLoginToken(token);
    }

}

вы можете найти реальный пример на Java EE Kickoff приложение.


Java EE 6/7

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

этого проще всего достичь, если вы создадите другую таблицу БД с 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() метод a Servlet, который назначен на /login:

String username = request.getParameter("username");
String password = hash(request.getParameter("password"));
boolean remember = "true".equals(request.getParameter("remember"));
User user = userService.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();
        rememberMeService.save(uuid, user);
        addCookie(response, COOKIE_NAME, uuid, COOKIE_AGE);
    } else {
        rememberMeService.delete(user);
        removeCookie(response, COOKIE_NAME);
    }
}

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

вот как doFilter() метод a 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 = rememberMeService.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 сервлета):

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 is чрезвычайно трудно грубой силы, вы можете предоставить пользователю возможность заблокировать опцию" запомнить " на IP-адрес пользователя (request.getRemoteAddr()) и хранить/сравнивать его в базе данных. Это делает его немного более надежным. Кроме того, было бы полезно иметь" срок годности", хранящийся в базе данных.

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


Java EE 5 или ниже

пожалуйста, обновите.


обычно это делается так:

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

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

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


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

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

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

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