Table of Content:
1. Understand the Solution to Remember Password Feature
2. Create Database Table and Java Domain Model Class
3. Code DAO Class
4. Update Code of the Login Page
5. Update Code of the Login Servlet
6. Update Code of the Authentication Filter
7. Update Code of the Logout Servlet
8. Test the Remember Password Feature
// copyright www.codejava.net import javax.persistence.*; @Entity @Table(name = "customer_auth") @NamedQueries({ @NamedQuery(name = "CustomerAuthToken.findBySelector", query = "SELECT c FROM CustomerAuthToken c WHERE c.selector = :selector") }) public class CustomerAuthToken implements java.io.Serializable { private Long id; private String selector; private String validator; private Customer customer; public CustomerAuthToken() { } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long getId() { return id; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "customer_id", nullable = false) public Customer getCustomer() { return customer; } // other getters and setters are hidden for brevity }Note that the customer table has one-to-many relationship with the customer_auth table, so in we also need to update code of the Customer domain model class as follows:
// copyright www.codejava.net import javax.persistence.*; @Entity public class Customer implements java.io.Serializable { private Set<CustomerAuthToken> customerAuthTokens = new HashSet<>(0); // other fields @OneToMany(fetch = FetchType.LAZY, mappedBy = "customer") public Set<CustomerAuthToken> getCustomerAuthTokens() { return customerAuthTokens; } // other setters and getters... }This allows us to look up a customer associated with a token (selector, validator) like this:
CustomerAuthToken token = … Customer customer = token.getCustomer();We refer a pair of (selector, validator) as a token.
// copyright www.codejava.net public class CustomerAuthDAO extends JpaDAO<CustomerAuthToken> implements GenericDAO<CustomerAuthToken> { public CustomerAuthToken findBySelector(String selector) { List<CustomerAuthToken> list = super.findWithNamedQuery( "CustomerAuthToken.findBySelector", "selector", selector); if (!list.isEmpty()) { return list.get(0); } return null; } }The findBySelector() method will be used to find a token in the authentication table, given a selector read from the cookie.Learn more about JPA/Hibernate here: Java Hibernate JPA Annotations Tutorial for Beginners
<input type="checkbox" name="rememberMe" value="true">Remember MeThe login form should look like this:
// copyright www.codejava.net public void doLogin() throws ServletException, IOException { String email = request.getParameter("email"); String password = request.getParameter("password"); boolean rememberMe = "true".equals(request.getParameter("rememberMe")); Customer customer = customerDAO.checkLogin(email, password); if (customer == null) { // login failed, show login form again with error messsage } else { // login succeed, store customer information in the session HttpSession session = request.getSession(); session.setAttribute("loggedCustomer", customer); if (rememberMe) { // create new token (selector, validator) // save the token into the database // create a cookie to store the selector // create a cookie to store the validator } // show destination page } }Here’s the code to create a new token:
// copyright www.codejava.net CustomerAuthToken newToken = new CustomerAuthToken(); String selector = RandomStringUtils.randomAlphanumeric(12); String rawValidator = RandomStringUtils.randomAlphanumeric(64); String hashedValidator = HashGenerator.generateSHA256(rawValidator); newToken.setSelector(selector); newToken.setValidator(hashedValidator); newToken.setCustomer(customer);We use the RandomStringUtils class from Apache Commons Lang library, so make sure you add the following dependency to your project:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency>And HashGeneratoris a utility class. Read this tutorial to create it: How to calculate MD5 and SHA hash values in JavaNotice we set a Customer object for the token:
newToken.setCustomer(customer);This Customer object is the result of the check login process:
Customer customer = customerDAO.checkLogin(email, password);And save the new token into the database:
CustomerAuthDAO authDao = new CustomerAuthDAO(); authDao.create(newToken);And here’s the code to create two cookies to store the values of selector and validator:
Cookie cookieSelector = new Cookie("selector", selector); cookieSelector.setMaxAge(604800); Cookie cookieValidator = new Cookie("validator", rawValidator); cookieValidator.setMaxAge(604800); response.addCookie(cookieSelector); response.addCookie(cookieValidator);Note that we set the max age of the cookies to 604,800 seconds = 7 days so they are stored permanently in the browser for that time, even when the user exits the browser. You can change this value depending on your need.
// copyright www.codejava.net public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // check user's login status in the session if (user is not logged in && current page requires authentication) { // forward to the login page } else { // user is logged in, move forward chain.doFilter(request, response); } }To implement the remember password feature, the workflow should be updated like this:
// copyright www.codejava.net public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // check user's login status in the session // read cookie if (user is not logged in && has cookie) { // read (selector, validator) from cookies // find a token by the selector in the database if (token is found) { // read the hashed value of validator from the token -> v1 // hash the validator from the database -> v2 if (v1 equals v2) { // user is authenticated, set login status in session // update new token in the database // update cookies } } } if (user is not logged in && current page requires authentication) { // forward to the login page } else { // user is logged in, move forward chain.doFilter(request, response); } }And here’s the example code to implement remember password feature in the authentication filter:
// copyright www.codejava.net HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; HttpSession session = httpRequest.getSession(false); boolean loggedIn = session != null && session.getAttribute("loggedCustomer") != null; Cookie[] cookies = httpRequest.getCookies(); if (!loggedIn && cookies != null) { // process auto login for remember me feature String selector = ""; String rawValidator = ""; for (Cookie aCookie : cookies) { if (aCookie.getName().equals("selector")) { selector = aCookie.getValue(); } else if (aCookie.getName().equals("validator")) { rawValidator = aCookie.getValue(); } } if (!"".equals(selector) && !"".equals(rawValidator)) { CustomerAuthDAO authDAO = new CustomerAuthDAO(); CustomerAuthToken token = authDAO.findBySelector(selector); if (token != null) { String hashedValidatorDatabase = token.getValidator(); String hashedValidatorCookie = HashGenerator.generateSHA256(rawValidator); if (hashedValidatorCookie.equals(hashedValidatorDatabase)) { session = httpRequest.getSession(); session.setAttribute("loggedCustomer", token.getCustomer()); loggedIn = true; // update new token in database String newSelector = RandomStringUtils.randomAlphanumeric(12); String newRawValidator = RandomStringUtils.randomAlphanumeric(64); String newHashedValidator = HashGenerator.generateSHA256(newRawValidator); token.setSelector(newSelector); token.setValidator(newHashedValidator); authDAO.update(token); // update cookie Cookie cookieSelector = new Cookie("selector", newSelector); cookieSelector.setMaxAge(604800); Cookie cookieValidator = new Cookie("validator", newRawValidator); cookieValidator.setMaxAge(604800); httpResponse.addCookie(cookieSelector); httpResponse.addCookie(cookieValidator); } } } }Note that after the user has been automatically authenticated, we create new values for selector and validator in order to tighten security.Learn more about authentication filter: How to implement authentication filter for Java web application
// copyright www.codejava.net protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getSession().removeAttribute("loggedCustomer"); Cookie[] cookies = request.getCookies(); if (cookies != null) { String selector = ""; for (Cookie aCookie : cookies) { if (aCookie.getName().equals("selector")) { selector = aCookie.getValue(); } } if (!selector.isEmpty()) { // delete token from database CustomerAuthDAO authDao = new CustomerAuthDAO(); CustomerAuthToken token = authDao.findBySelector(selector); if (token != null) { authDao.delete(token.getId()); Cookie cookieSelector = new Cookie("selector", ""); cookieSelector.setMaxAge(0); Cookie cookieValidator = new Cookie("validator", ""); cookieValidator.setMaxAge(0); response.addCookie(cookieSelector); response.addCookie(cookieValidator); } } } response.sendRedirect(request.getContextPath()); }Note that in the above code, we set the max age of the cookies to 0 to remove them.
chrome://settings/cookies/detail?site=localhost
Remember to test with different users, different browsers and different computers.That’s my step-by-step guide to implement the remember me/remember password feature for an existing Java web application. For learning to develop full website, I recommend you to take the Java Servlet, JSP and Hibernate: Build a Complete Website course.