ARTICLE AD BOX
I would like to implement a remember-me functionality in my very plain servlet based system. No Spring, No Shiro nothing. I'm mightily struggling to make it work. On paper it seems fairly straight forward. I used the following thread to get me started \[Very nice Remember Me thread\](How to implement "Stay Logged In" when user login in to the web application)
To the best of my comprehension the above linked thread offered 3 solutions. 2 by BalusC and the 3rd one by (Ilyua) Basin at the bottom of the thread.
My current attempt is to try and implement solution 2 of BalusC for Java 6/7. As I was unable to make the other 2 work. Here I'm running into 2 issues. :( However I will only mention one issue so that my question doesn't get closed. :) The filter seems to be called 3 times during the login process. When my index.jsp page requests a protected resource /jsp/startup.jsp , the filter is called with the session being null and everything is good. During this call I check the cookie and the DB entry and if either are missing I forward the user to the login.jsp page to provide their credentials for the first time. User specifies the credentials and submits the form and the filter is called the second time. Seeing in the filter that we are coming from the login.jsp I forward the call to the servlet that processes the login page. That servlet now has a session that is not null. So it creates a cookie, inserts a row for this user in the DB with a unique cookie ID, and inserts the user object into the session. At the end of the servlet I forward the user to the startup.jsp page that they requested to begin with. This once again triggers a call to the filter, now for the third time, and this is where everything breaks down. In this third call to the filter the session is back to being null much to my shock and dismay which completely messes up all of the logic.
Clearly I'm missing something (or many things) as this functionality has been implemented in many many places and successfully. Would love to figure out what it is that I'm missing.
public class MyServletFilter extends SecurityParentServlet implements Filter { protected static final Logger logger = Logger.getLogger(MyServletFilter.class); @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fchain) throws IOException, ServletException { logger.debug("MyServletFilter was called"); HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String sUserName = request.getParameter("username"); if ((sUserName != null) && (sUserName.length() > 0)) { // The user is logging in so we should not be here fchain.doFilter(req, resp); return; } // If the user is logged in and in the session then send them along HttpSession session = request.getSession(false); if ((session != null) && (session.getAttribute("user") != null)) { fchain.doFilter(req, resp); return; } else { // Try to auto log the user in. They must have a cookie, // be in the DB and DB record must not be expired String uuid = getCookieValue(request, COOKIE_NAME); logger.debug("MyServletFilter cookie value = "+uuid); RememberMeUser rmUser = null; if (uuid != null) { rmUser = DBHandler.getRememberUser(uuid); logger.debug("MyServletFilter rmUser from DB = "+rmUser); if (rmUser != null) { request.login(rmUser.getAccount(), rmUser.getPassword()); session.setAttribute("user", rmUser); logger.debug("MyServletFilter adding the cookie"); addCookie(response, uuid); // Extends age. logger.debug("User is "+request.getRemoteUser()); } else { logger.debug("MyServletFilter deleting the cookie"); removeCookie(response); } } if (rmUser == null) { goToLogin(request, response); } else { fchain.doFilter(req, resp); } } } } @WebServlet("/SecurityParentServlet") public class SecurityParentServlet extends HttpServlet { public static final String COOKIE_NAME = "rememberme"; public static final int COOKIE_AGE = 30 * 86400; public static void addCookie(HttpServletResponse response, String value) { Cookie cookie = new Cookie(COOKIE_NAME, value); cookie.setPath("/"); cookie.setMaxAge(COOKIE_AGE); response.addCookie(cookie); } public static void removeCookie(HttpServletResponse response) { Cookie cookie = new Cookie(COOKIE_NAME, null); cookie.setPath("/"); cookie.setMaxAge(0); response.addCookie(cookie); } public static boolean jSecurityCheck(HttpServletRequest request, HttpServletResponse response, String username, String password) throws Exception { HashMap<String, String> formParams = new HashMap<String, String>(); formParams.put("j_username", username); formParams.put("j_password", password); byte[] postData = getDataString(formParams).getBytes( StandardCharsets.UTF_8 ); int postDataLength = postData.length; // TODO: detect container http listener has SSL/TLS enabled // (request.isSecure() unreliable due to possible offload) // String s = "http://" + request.getLocalAddr() + ":" + request.getLocalPort() // + request.getContextPath() + "/j_security_check"; String s = "http://localhost:8080/j_security_check"; URL url = new URL(s); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); try { conn.setDoOutput(true); conn.setUseCaches(false); conn.setInstanceFollowRedirects(false); conn.setRequestMethod("POST"); String PROP = "sun.net.http.allowRestrictedHeaders"; if (!"true".equals(System.getProperty(PROP))) { request.getServletContext().log("must set to true: " + PROP); } System.out.println(System.getProperty(PROP)); System.out.println(request.getHeader("Host")); System.out.println(request.getHeader("Cookie")); conn.setRequestProperty("Host", request.getHeader("Host")); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", Integer.toString(postDataLength)); conn.setRequestProperty("charset", "utf-8"); conn.setRequestProperty("Cookie", request.getHeader("Cookie")); OutputStream wr = conn.getOutputStream(); try { wr.write(postData); } finally { wr.close(); } // Request sent wait for response int code = conn.getResponseCode(); String location = null; if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) { location = conn.getHeaderField("Location"); if (location != null) { response.sendRedirect(location); return true; } } else { // if getInputStream() succeeds, then http 200 or such // meaning login failure (Tomcat) conn.getInputStream().close(); return false; } throw new ServletException("Unexpected j_security_check response " + code + " Location: " + location); } finally { conn.disconnect(); } } private static String getDataString(HashMap<String, String> params) throws Exception { StringBuilder result = new StringBuilder(); boolean first = true; for(HashMap.Entry<String, String> entry : params.entrySet()){ if (first) first = false; else result.append("&"); result.append(URLEncoder.encode(entry.getKey(), "UTF-8")); result.append("="); result.append(URLEncoder.encode(entry.getValue(), "UTF-8")); } return result.toString(); } public static String getCookieValue(HttpServletRequest request, String name) { String sValue = null; Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (name.equals(cookie.getName())) { sValue = cookie.getValue(); } } } return sValue; } protected void forward(HttpServletRequest request, HttpServletResponse response, String sName) { try { response.reset(); RequestDispatcher dispatcher = request.getRequestDispatcher("/ewa/"+sName+".jsp"); if (dispatcher != null) dispatcher.forward(request, response); } catch (Exception e) { // logger.error(e.getMessage(), e); } } protected void goToLogin(HttpServletRequest request, HttpServletResponse response) { try { response.reset(); RequestDispatcher dispatcher = request.getRequestDispatcher("Login1.jsp"); if (dispatcher != null) dispatcher.forward(request, response); } catch (Exception e) { // logger.error(e.getMessage(), e); } } @WebServlet("/ProcessLogin") public class ProcessLogin extends SecurityParentServlet { protected static final Logger logger = Logger.getLogger(ProcessLogin.class); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String sUserName = request.getParameter("username"); String sPassword = request.getParameter("password"); String sRememberMe = request.getParameter("remember_me"); RememberMeUser rmUser = new RememberMeUser(); if (sRememberMe != null) { // 1.Generate the unique Id to store in the cookie String uuid = UUID.randomUUID().toString(); rmUser.setUniqueId(uuid); // 2. Add the cookie addCookie(response, uuid); // 3. Save the user credentials in the DB with cookie id as the index if (DBHandler.isRememberUserInDB(sUserName)) DBHandler.updateRememberMeUser(sUserName, uuid, sPassword); else DBHandler.insertRememberMeUser(uuid, sUserName, sPassword); } else { // Delete cookie and table entry removeCookie(response); DBHandler.deleteRememberMeUser(sUserName); } // Last step Log the user in try { request.login(sUserName, sPassword); rmUser.setAccount(sUserName); // For now we wil leave th PW unset and fetch from the DB if needed // logger.debug("User is "+request.getRemoteUser()); // Sadly this only works at the time login method is called any subsequent calls makes this value disappear, hence we store the user in the session HttpSession session = request.getSession(false); session.setAttribute("user", rmUser); // Login. // if (jSecurityCheck(request, response, sUserName, sPassword)) forward(request, response, "index"); } catch (Exception e) { logger.error("Exception trying to login "+sUserName, e); // Logging the user in automatically failed so send them to the Login screen goToLogin(request, response); } } }