How to implement "Stay Logged In" when user login in to the web application
You cannot login a user completely via HttpServletRequest.login(username, password) since you shouldn't keep both username and plain text password in the database. Also you cannot perform this login with a password hash which is saved in the database. However, you need to identify a user with a cookie/DB token but log him/her in without entering password using custom login module (Java class) based on Glassfish server API.
See the following links for more details:
http://www.lucubratory.eu/custom-jaas-realm-for-glassfish-3/
Custom Security mechanism in Java EE 6/7 application
Java EE 8 and up
If you're on Java EE 8 or newer, put @RememberMe
on a custom HttpAuthenticationMechanism
along with a 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);
}
}
You can find a real world example in the Java EE Kickoff Application.
Java EE 6/7
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.
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.
Assume the following login form:
<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>
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);
}
}
(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)
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);
}
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);
}
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.
It's also a good practice to replace the UUID
value whenever the user has changed its password.
Java EE 5 or below
Please, upgrade.
Normally this is done like this:
When you log in a user you also set a cookie on the client ( and store the cookie value in the database ) expiring after a certain time (1-2 weeks usually).
When a new request comes in you check that the certain cookie exists and if so look into the database to see if it matches a certain account. If it matches you will then "loosely" log in that account. When i say loosely i mean you only let that session read some info and not write information. You will need to request the password in order to allow the write options.
This is all that is. The trick is to make sure that a "loosely" login is not able to do a lot of harm to the client. This will somewhat protect the user from someone who grabs his remember me cookie and tries to log in as him.