Java 3 - 2025

Week 4

  • Create a new servlet called "Login". Add this code.

Login Servlet

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
@WebServlet("/login")
public class Login extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("pageTitle", "Login");
        req.getRequestDispatcher("WEB-INF/login.jsp").forward(req, resp);
    }
}
  • Update the Login link in the top-nav.jspf file.

  • <a href="${appURL}/login" class="btn btn-outline-dark me-2">Login</a>

  • Create a new JSP called login.jsp. 

  • Add this code from the Bootstrap Signin Template.

  • I updated type="email" to type="text" so we can write Java to validate the input.

  • I updated the paragraph at the end to include a signup link

login.jsp

<main class="form-signin w-100 m-auto">
  <form>
    <h1 class="h3 mb-3 fw-normal">Please sign in</h1>

    <div class="form-floating">
      <input type="text" class="form-control" id="floatingInput" placeholder="name@example.com">
      <label for="floatingInput">Email address</label>
    </div>
    <div class="form-floating">
      <input type="password" class="form-control" id="floatingPassword" placeholder="Password">
      <label for="floatingPassword">Password</label>
    </div>

    <div class="form-check text-start my-3">
      <input class="form-check-input" type="checkbox" value="remember-me" id="flexCheckDefault">
      <label class="form-check-label" for="flexCheckDefault">
        Remember me for 30 days
      </label>
    </div>
    <button class="btn btn-primary w-100 py-2" type="submit">Sign in</button>
    <p class="mt-5 mb-3 text-body-secondary">Don't have an account? <a href="${appURL}/signup">Sign-up</a></p>
  </form>
</main>
  • Create a new CSS file at webapp/styles/login.css

login.css

.form-signin {
    max-width: 330px;
    padding: 1rem;
}

.form-signin .form-floating:focus-within {
    z-index: 2;
}

.form-signin input[type="email"] {
    margin-bottom: -1px;
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;
}

.form-signin input[type="password"] {
    margin-bottom: 10px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}
  • In top.jspf, add a <c:if> tag to apply this stylesheet to the Login page.

<c:if test="${pageTitle eq 'Login'}"><link rel="stylesheet" href="${appURL}/styles/login.css"></c:if>
  • Add form method and action attributes.

    <form method="post" action="${appURL}/login">

  • Add name and id attributes to all form inputs that match the ids. Add a value attribute to the checkbox.
    id="email" name="email"
    id="password" name="password"
    id="rememberMe" name="rememberMe" value="true"

login.jsp

  • Create a doPost method. Add code similar to the Signup servlet. Note there is only one password field. The agreeToTerms checkbox is now a rememberMe checkbox.  

Login Servlet

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String email = req.getParameter("email");
    String password = req.getParameter("password");
    String[] rememberMe = req.getParameterValues("rememberMe");
    req.setAttribute("email", email);
    req.setAttribute("password", password);
    req.setAttribute("rememberMe", (rememberMe != null && rememberMe[0].equals("true")) ? "true" : "");

    req.setAttribute("pageTitle", "Login");
    req.getRequestDispatcher("WEB-INF/login.jsp").forward(req, resp);
}
  • Add value attributes to the email and password form inputs. Add an expression to the checkbox to check it.
    value="${email}"
    value="${password}"
    ${rememberMe eq "true" ? "checked" : ""}

  • The last expression could also be written like this:
    <c:if test="${rememberMe == 'true'}">checked</c:if>

  • Add a c:if tag before the form tag to display any error message.
    <c:if test="${not empty loginFail}">
        <div class="alert alert-warning mb-2" role="alert">${loginFail}</div>
    </c:if>

login.jsp

  • In the servlet, get the User object from the database and validate that the email and password match. 

  • If they match, create an HttpSession, set the user as a session attribute, set a flash message session attribute greeting the user, and redirect the user to any page, like the home page.

Login Servlet

User user = null;
try {
    user = UserDAO.get(email);
} catch (RuntimeException e) {
    req.setAttribute("loginFail", "An error occurred."); // Use e.getMessage() to see the SQLException
}

if (user == null) {
    // No user found that matches the email
    req.setAttribute("loginFail", "No user found with that email address. <a href=\"signup\">Sign-up</a>"); // For security, it might be better to just say "No user found".
} else {
    boolean passwordMatches = false;
    try {
        passwordMatches = BCrypt.checkpw(password, String.valueOf(user.getPassword()));
    } catch (Exception e) {
        req.setAttribute("loginFail", "An error occurred."); // Use e.getMessage() to see the NoSuchAlgorithmException or InvalidKeySpecException
    }
    // No user found that matches the password
    if (!passwordMatches) {
        req.setAttribute("loginFail",  "The password you entered is incorrect."); // For security, it might be better to just say "No user found".
    } else {
        // Successful login
        user.setPassword(null); // Remove the password before setting the User object as a session attribute

        HttpSession session = req.getSession(); // Get existing HttSession object
        session.invalidate(); // Remove any existing session attributes
        session = req.getSession(); // Create new HttpSession
        session.setAttribute("activeUser", user);
        session.setAttribute("flashMessageSuccess", String.format("Welcome back%s!", (user.getFirstName() != null && !user.getFirstName().equals("") ? " " + user.getFirstName() : "")));

        resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/")); // Redirects to the home page
        return;
    }
}

End Day 7

Start Day 8

  • The setMaxInactiveInterval() method can be used with the session object to increase the session timeout.

    • This method sets the value, in seconds, that the session will remain open between client requests.

    • For a 30-day duration, the interval must be 2592000. (30 days * 24 hrs/day * 60 min/hr * 60 secs/min)

    • It is important to set this value before any data is stored in the session.

  • This feature will work on Azure, not localhost, because of how often the server needs to restart.

Login Servlet

HttpSession session = req.getSession(); // Get existing HttSession object
session.invalidate(); // Remove any existing session attributes
session = req.getSession(); // Create new HttpSession
if(rememberMe != null && !rememberMe[0].equals("true")){
    session.setMaxInactiveInterval(2592000); // Set session timeout to 30 days (in seconds)
}
session.setAttribute("activeUser", user);
String firstName = user.getFirstName() != null ? " " + user.getFirstName() : "";
session.setAttribute("flashMessageSuccess", String.format("Welcome back%s!", firstName));
  • Write JavaScript code to remember a user's email address the next time they try to sign in.

  • When the form is submitted, if "Remember Me" is checked, the email is stored in localStorage. If not, the email is removed.

  • When the page loads, it checks if an email is stored in localStorage and fills in the email field if found. If an email is found in localStorage, the "Remember Me" checkbox automatically checks.

remember-me.js

document.addEventListener("DOMContentLoaded", function () {
    const emailInput = document.getElementById("email");
    const rememberMeCheckbox = document.getElementById("rememberMe");

    // Load saved email if it exists
    const savedEmail = localStorage.getItem("rememberedEmail");
    if (savedEmail) {
        emailInput.value = savedEmail;
        rememberMeCheckbox.checked = true;
    }

    document.getElementById("loginForm").addEventListener("submit", function (event) {
        if (rememberMeCheckbox.checked) {
            localStorage.setItem("rememberedEmail", emailInput.value);
        } else {
            localStorage.removeItem("rememberedEmail");
        }
    });
});
  • In top.jspf, add a <c:if> tag to apply this JS to the Login page.

<c:if test="${pageTitle eq 'Login'}"><script src="${appURL}/scripts/remember-me.js"></script></c:if>
  • Create a new servlet called "Signout". Add this code.

  • No return statement is needed after sendRedirect because that is the last line.

  • Update the Sign out link in the top-nav.jspf file.
    <a href="${appURL}/signout" class="btn btn-outline-secondary">Sign out</a>

Signout Servlet

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
@WebServlet("/signout")
public class Signout extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(); // Get existing session
        session.invalidate(); // Remove all existing session attributes
        session = req.getSession(); // Create new session
        session.setAttribute("flashMessageWarning", "You are logged out. See you next time!");
        resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/")); // Redirects to the home page
    }
}

AdminUsers servlet

  • The path "/users" displays sensitive information that only logged in admin users should see.
  • Add this code at the beginning of the doGet() method to get the activeUser from the session and check if the user is an active admin.
HttpSession session = req.getSession();
User userFromSession = (User)session.getAttribute("activeUser");
if(userFromSession == null || !userFromSession.getStatus().equalsIgnoreCase("active") || !userFromSession.getPrivileges().equalsIgnoreCase("admin")) {
	session.setAttribute("flashMessageWarning", "You must be logged in to view this content");
	resp.sendRedirect("login");
	return;
} else {
  	// Code that already exists in the doGet method.
}
  • Alternatively, you can hide the page completely by responding with a 404 page not found.
    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
    return;

top-nav.jspf

  • Update the top navigation with an Admin menu that only displays for logged in, active, admin users.
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
    <li class="nav-item">
        <a class="nav-link active" aria-current="page" href="${appURL}">Home</a>
    </li>
    <c:if test="${not empty sessionScope.activeUser && sessionScope.activeUser.privileges eq 'admin' && sessionScope.activeUser.status eq 'active'}">
        <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false">Admin</a>
            <ul class="dropdown-menu">
                <li><a class="dropdown-item" href="${appURL}/users">Users</a></li>
            </ul>
        </li>
    </c:if>
</ul>

Login Servlet

  • If the email and password are correct, make sure the user is active.
if(!user.getStatus().equalsIgnoreCase("active")) {
    req.setAttribute("loginFail",  "Your account is locked or inactive. Please reset your password.");
    req.setAttribute("pageTitle", "Login");
    req.getRequestDispatcher("WEB-INF/login.jsp").forward(req, resp);
    return;
}

End Day 8