Friday, April 22, 2011

JSF 2 Session Management

This article explains how you could manage sessions in JSF. The scenario we are going to look at is a blogging site which has restricted sections to non-logged-in users. If a non-logged-in user visits a page with access restrictions he/she would be redirected or taken to the login page. Only the logged users would have access to those pages.

What you could do is create a JSF Project at least with the following libraries

jsf-api.jar
jsf-impl.jar
jstl-api-1.2.jar
jstl-impl-1.2.jar

Note: Am using JSF 2 libraries.

For session management, you need to create a bean with a Session Scope. In our case we can create a bean called LoginBean. UserBean will have an EventListener called verifyIfUserLoggedIn. This is event will be attached to any pages which you would to restrict access using <f:event > tag.(To avoid adding the <f:event /> tag to all the pages you want to restrict access, you can include it in the template). Below is the code

LoginBean.java
import java.io.IOException;
import java.io.Serializable;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;

@ManagedBean
@SessionScoped
public class LoginBean implements Serializable {
 private static final long serialVersionUID = 1L;
 private String username;
 private String password;
 private boolean isLoggedIn;

 public String login(){
  //custom member manager class
  MemberManager memberManager=new MemberManager();
  //default url in case of login failure;
  String url="login.jsf";

  //user a custom method to authenticate a user
  if(memberManager.authenticate(username, password)){
   //changed the state to true
   isLoggedIn=true;
   url="forum.jsf";
  }else{
   //set the message to display when authentication fails
   FacesContext.getCurrentInstance().addMessage("frmLogin:btnLogin", new FacesMessage("Invalid Username and or Password"));
  }
  return url;
 }

 /**
 * An event listener for redirecting the user to login page if he/she is not currently logged in
 * @param event
 */
 public void verifyUseLogin(ComponentSystemEvent event){
  if(!isLoggedIn){
   doRedirect("login.jsf");
  }
 }

 /**
 * Method for redirecting a request
 * @param url
 */
 private void doRedirect(String url){
  try {
   FacesContext context=FacesContext.getCurrentInstance();
   context.getExternalContext().redirect("login.jsf");
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 public String getUsername() {
  return username;
 }
 public void setUsername(String username) {
  this.username = username;
 }
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }
 public boolean isLoggedIn() {
  return isLoggedIn;
 }
 public void setLoggedIn(boolean isLoggedIn) {
  this.isLoggedIn = isLoggedIn;
 }
}

Login Form
Login page will have a structure the following code
<h:form id="frmLogin">
 <h:message for="btnLogin" />
 <h:panelgrid columns="2">
  <h:outputtext value="Username" /><h:inputtext value="#{loginBean.username}" style="width: 106px;" />
  <h:outputtext value="Password" /><h:inputsecret value="#{loginBean.password}" style="width: 108px;" />
  <h:outputtext value="" /><h:commandbutton action="#{loginBean.login}" value="Login" id="btnLogin" />
 </h:panelgrid>
</h:form>

Attach Event Listener
Attach event listener to the page or view you want to restrict access as shown below (preferably in the head section).
<f:metadata>
 <f:event listener="#{loginBean.verifyUseLogin}" type="preRenderView">
</f:event>

I hope this article will be of help. Regards

8 comments:

  1. Thanks v v v much. Its very helpful, kindly tell me that which fields or methods would be there in the MemberManager class.
    Thanks in advance.

    ReplyDelete
  2. Sorry Dani. I had been committed with a number of projects hence had little time to check the blog.

    To answer your question, the MemberManager would be a POJO (Plain Old Java Object) which has a method to authenticate the user against the database or LDAP server (i.e when using web forms for authentication).

    ReplyDelete
  3. Great post!

    I have a question. If we have multiple users accessing the application, we do not have no competition problems in relation to the attribute "isLoggedIn" ? A few days ago I talked with my colleagues about defining attributes direct inside a servlet (not jsf project) instead of inserting the object (User) and check if the session is logged in by checking the session. How is it treated this case with the JSF 2 and session scope?

    Thanks,
    Rodrigo

    ReplyDelete
  4. Hi Rodigo, To answer your question, session scoped beans are different from user to user. This due to the fact that sessions are associated to specific requests over a period of time (and or browser's life ie before you close the browser). So don't worry, they wont be sharing the logged in state.
    The property would be the same for all users if it was a static variable.

    ReplyDelete
  5. The following functionalities need to be incorporated in the Login page:
    • user should be able to login to the website by using a username and password combination.
    • If the user selects either the Remember Username or Remember Me check box, a cookie should be generated. When Remember Username check box is selected, it should store the username. When the Remember Me check box is selected, it should store the username and password combination. This will
    avoid retyping the username and password when a user returns to the Login page.


    <%
    String userName = request.getParameter("username");
    String password = request.getParameter("password");
    String rm_me = request.getParameter("rm_me");
    String rm_uname = request.getParameter("rm_uname");

    if (userName != null && password != null) {
    if (rm_me != null) {
    Cookie ckU = new Cookie("username", userName);
    Cookie ckP = new Cookie("password", password);
    response.addCookie(ckP);
    } else {
    if (rm_uname != null) { Cookie ckU = new Cookie("username", userName);
    }
    }

    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
    for (int i = 0; i < cookies.length; i++) {
    if (cookies[i].getName().equals("username")) {
    userName = cookies[i].getValue();
    }
    if (cookies[i].getName().equals("password")) {
    password = cookies[i].getValue();
    }
    }
    }

    %>

    However, on testing the page, the user had to retype the username each time the Login page was loaded. Identify the error in this code and give reason. In addition, rectify the error to ensure that the desired result is obtained.

    ReplyDelete
    Replies
    1. 1) create 2 checkboxes with the following names;
      A)"rm_me"
      B)"rm_uname"

      2)find this a add what is no there.
      .....

      if (userName != null && password != null) {
      if (rm_me != null) {
      Cookie ckU = new Cookie("username", userName);
      Cookie ckP = new Cookie("password", password);
      response.addCookie(ckP);
      response.addCookie(ckU); //add this 1st line
      ...
      ...
      else {
      if (rm_uname != null) { Cookie ckU = new Cookie("username", userName);
      response.addCookie(ckU) // add this 2nd line

      3) MAKE SURE THAT U PLACE U JSP TAGS IN BETWEEN TIITLE TAGS OR BODY TAGS.

      4) I HOPE IT WORKS. :)

      Delete
  6. Thank you good sir for the simple yet informative example. I have a question though, mind I'm new to JSF. You mentioned the <f:event listener... tag that has to be added to every page that requires authentication.

    But can't we instead implement the avax.faces.event.PhaseListener and write a verifyUseLogin method there and include it in the faces-config.xml as phase-listener?

    I guess this way we wouldn't have to worry about adding the <f:event listener... tag to every page ? But on the same account, what is better in terms of performance etc?

    Thank you once again!

    ReplyDelete
  7. it works correctly, thank you very much

    ReplyDelete