如何实现“保持登录"当用户登录到 Web 应用程序时 [英] How to implement "Stay Logged In" when user login in to the web application

查看:20
本文介绍了如何实现“保持登录"当用户登录到 Web 应用程序时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在大多数网站上,当用户要提供用户名和密码登录系统时,会出现保持登录状态"之类的复选框.如果您选中该框,它将让您在同一 Web 浏览器的所有会话中保持登录状态.如何在 Java EE 中实现相同的功能?

On most websites, when the user is about to provide the username and password to log into the system, there's a checkbox like "Stay logged in". If you check the box, it will keep you logged in across all sessions from the same web browser. How can I implement the same in Java EE?

我将基于 FORM 的容器管理身份验证与 JSF 登录页面一起使用.

I'm using FORM based container managed authentication with a JSF login page.

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

推荐答案

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 应用程序中的="noreferrer">真实示例一>.

You can find a real world example in the Java EE Kickoff Application.

如果您使用的是 Java EE 6 或 7,自制一个长期存在的 cookie 来跟踪唯一的客户端并使用 Servlet 3.0 API 提供的程序化登录HttpServletRequest#login() 当用户未登录但存在 cookie 时.

If you're on Java EE 6 or 7, homegrow a long-living cookie to track the unique client and use the Servlet 3.0 API provided programmatic login HttpServletRequest#login() when the user is not logged-in but the cookie is present.

如果您创建另一个数据库表,其中 java.util.UUID 值为 PK,相关用户的 ID 为 FK,这是最容易实现的.

This is the easiest to achieve if you create another DB table with a java.util.UUID value as PK and the ID of the user in question as 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>

以及映射到 /loginServletdoPost() 方法中的以下内容:

And the following in doPost() method of a Servlet which is mapped on /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);
    }
}

(COOKIE_NAME 应该是唯一的 cookie 名称,例如 remember" 并且 COOKIE_AGE 应该是秒,例如 2592000 30 天)

(the COOKIE_NAME should be the unique cookie name, e.g. "remember" and the COOKIE_AGE should be the age in seconds, e.g. 2592000 for 30 days)

以下是映射到受限页面的 FilterdoFilter() 方法的样子:

Here's how the doFilter() method of a Filter which is mapped on restricted pages could look like:

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 helper 方法(可惜它们在 Servlet API 中没有):

In combination with those cookie helper methods (too bad they are missing in Servlet 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 非常难以暴力破解,但您可以为用户提供一个选项来锁定记住"用户 IP 地址的选项 (request.getRemoteAddr()) 并将其存储/比较到数据库中.这使它更加健壮.此外,具有到期日期"存储在数据库中会很有用.

Although the UUID is extremely hard to brute-force, you could provide the user an option to lock the "remember" option to user's IP address (request.getRemoteAddr()) and store/compare it in the database as well. This makes it a tad more robust. Also, having an "expiration date" stored in the database would be useful.

每当用户更改密码时替换 UUID 值也是一个好习惯.

It's also a good practice to replace the UUID value whenever the user has changed its password.

请升级.

这篇关于如何实现“保持登录"当用户登录到 Web 应用程序时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆