Best way to log in, log out, and store session attributes in JSF

Morglor picture Morglor · Jan 22, 2012 · Viewed 7.2k times · Source

I am very new to JSF, and I am pretty confused about the best practices of managing user logins. There are a lot of answers floating around, but they require knowledge of JSF and Java EE which I don't seem to have.

When working with a classic JSP project, I handle logins in a very simple way. When a user successfully logs in, a session attribute "isLoggedIn" is created with the value true. When the user logs out, the session is invalidated. If the "isLoggedIn" attribute is either false or missing, pages that require you to be logged in redirect to the login screen.

How would this sort of thing be done in JSF? Should I store things like login status, username, and user role in an HttpSession, or in a managed session bean?

A lot of answers that I found say that you should use request.getRemoteUser() to manage logins, but don't give any further explanation. From what I was able to gather, this method is useful if you are using domain credentials to log into the application. So is it of any use if I'm storing user credentials (username + salted and hashed password) in a database?

It would be very helpful if someone can show me how a website with the following two pages would work:

  1. A login page where one can enter a username and password and press a "Login" button.
  2. A page with the message "Login Successful", and a logout button. When the log out button is pressed, the user is logged out and brought to the login page.

If a user tries to go to the second page while they are not logged in, they will be redirected to the first page.

Like I said before, I am very new to JSF and I simply cannot find a good tutorial for how to handle this sort of thing. So any links that you feel are useful will be appreciated.

If it matters, I am using the MyFaces implementation, and the PrimeFaces component library.

Answer

aroth picture aroth · Jan 22, 2012

You have two basic options:

  1. Use container-based authentication.
  2. Roll your own.

The first option is the officially recommended approach, and the exact details will vary depending upon what servlet container and/or web framework you are using.

Frankly, however, I often find configuring container-based authentication to be more trouble than it is worth (and more trouble than simply building a custom authentication layer that does what I want). So if you're interested in rolling your own, the approach I generally take (using Spring and Hibernate) is to have a User class like:

@Entity
@Table(name="users")
@NamedQueries({
    @NamedQuery(name="User.findAll", query="SELECT u FROM User u"),
    @NamedQuery(name="User.findByPrimaryEmail", query="SELECT u FROM User u WHERE u.primaryEmail = :email")
})
public class User {
    //fields
    private long id;
    private String primaryEmail;
    private String firstName;
    private String lastName;
    private String hashedPassword;
    private String salt;
    //...

    //relationships
    //...

    public User() {
        primaryEmail = null;
        firstName = null;
        lastName = null;
        salt = null;
        hashedPassword = null;
        //...
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }

    @Column(unique=true)
    public String getPrimaryEmail() {
        return primaryEmail;
    }
    public void setPrimaryEmail(String email) {
        this.primaryEmail = email;
        if (this.primaryEmail != null) {
            this.primaryEmail = email.toLowerCase();
        }
    }

    @Column
    public String getHashedPassword() {
        return hashedPassword;
    }
    public void setHashedPassword(String hashedPassword) {
        this.hashedPassword = hashedPassword;
    }

    @Column
    public String getSalt() {
        return salt;
    }
    public void setSalt(String salt) {
        this.salt = salt;
    }

    //(getters and setters for any other columns and relationships)

    @Transient
    public void setPassword(String passwordPlaintext) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (this.getSalt() == null) {
            this.setSalt(StringUtilities.randomStringOfLength(16));
        }

        this.setHashedPassword(this.computeHash(passwordPlaintext, this.getSalt()));
    }

    @Transient
    public boolean checkPasswordForLogin(String passwordPlaintext) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (StringUtilities.isEmpty(passwordPlaintext)) {
            return false;
        }
        return this.getHashedPassword().equals(this.computeHash(passwordPlaintext, this.getSalt()));
    }

    @Transient
    private String computeHash(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 2048, 160);
        SecretKeyFactory fact = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

        //I encode to base64 so that I can treat the hash as text in computations and when storing it in the DB
        return Base64.encodeBytes(fact.generateSecret(spec).getEncoded());
    }
}

And then a simple login form like:

<form id="loginForm" method="POST" action="/r/submitLogin">
    <div class="formRow">
        <span class="formLabel">Email</span>  <input type="text" class="textInput" name="email" />
    </div>
    <div class="formRow">
        <span class="formLabel">Password</span>  <input type="password" class="textInput" name="pass" />
    </div>
    <div class="formRow">
        <input type="submit" class="submitButton" value="Log In" />
    </div>
</form>

And a submitLogin implementation that goes like:

public ModelAndView submitLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String email = request.getParameter("email");
    String pass = request.getParameter("pass");

    EntityManager em = DatabaseUtil.getEntityManager(request);  //get an EntityManager, you can also use dependency-injection to do this if you prefer
    User user = getUserByPrimaryEmail(email, em);  //lookup the user by email address
    if (user == null) {
        //invalid username
        request.setAttribute("error", "User not found");
        return login(request, response);
    }

    try {
        if (user.checkPasswordForLogin(pass)) {
            //valid login, remember the user in the session
            request.getSession().setAttribute(Constants.SESSION_USER_KEY, user);

            //send the user to the default page
            response.sendRedirect("/r/indexPage");
            return null;
        }
        else {
            //invalid password
            request.setAttribute("error", "Incorrect password");
            return login(request, response);
        }
    }
    catch (Exception e) {
        //should only happen if checkPasswordForLogin() throws NoSuchAlgorithmException/InvalidKeySpecException
        LOG.error("Login processing failed!", e);
        request.setAttribute("error", "Cannot generate password hash?!?!?");
        return login(request, response);
    }
}

That's all there is to it, for a basic implementation at any rate. You can of course build features on top of this, such as "remember me" cookies/persistent logins, user roles, statuses, access levels, and so on. But for basic login/logout this is all you really need.

With this approach, the logged-in user will be available by doing request.getSession().getAttribute(Constants.USER_KEY), where Constants.USER_KEY is just some arbitrary string that you define. I typically use something along the lines of "<appName>.user".

If using container-based authentication, the user will typically be exposed by calling request.getUserPrincipal().