1
votes

I'm trying to use HttpServletRequest authenticate within a JSF managed bean to implement fine-grained authentication, depending on the particular object requested.

When I call authenticate within a preRenderView event listener, if authenticate causes a redirect to the login page, an exception is thrown. I can't call responseComplete after the call to authenticate, because FacesContext.getCurrentInstance returns null. Is it possible to call authenticate() at all in JSF, or do I have to use a ServletFilter? HttpServletRequest login and logout work within JSF, so I think it's reasonable to assume authenticate should work. Is this a bug in Mojarra JSF?

Here is my code:

The page where event listener is registered:

<ui:composition template="/template.xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:ice="http://www.icesoft.com/icefaces/component">
<ui:param name="pageTitle" value="Text Clustering Home Page"/>
<ui:define name="metadata">
<f:metadata>

   <f:event type="preRenderView" listener="#{permissionBean.preRender}"/>
</f:metadata>
</ui:define>
<ui:define name="body">

      Text Clustering Home Page
      <h:form>
          <h:panelGrid columns="1">
              <ice:outputText rendered="#{loginService.loggedIn}" value="Logged in User: #{loginService.currentUser.username}"/>
              <h:link rendered="#{!loginService.loggedIn}" value="Register" outcome="Register"/>

              <h:commandLink value="Logout" rendered="#{loginService.loggedIn}" action="#{loginService.logout}"/>
              <h:link value="Login" rendered="#{!loginService.loggedIn}" outcome="Login"/>
          </h:panelGrid> 
      </h:form>

The bean that contains the listener:

@Named
@RequestScoped
public class PermissionBean implements java.io.Serializable {

public void preRender(CompenentSystemEvent event) {
    HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
  HttpServletResponse response = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
  try {

      if (!request.authenticate(response)) {
          System.out.println("After authenticate, context = " +FacesContext.getCurrentInstance());
          if (FacesContext.getCurrentInstance()!=null) {                   
            FacesContext.getCurrentInstance().responseComplete();
          }
       }          

  } catch (Exception e) {  // may throw ServletException or IOException
      System.out.println("EXCEPTION calling authenticate");                
      e.printStackTrace();
  }                
}

}

The call to authenticate() doesn't throw an exception, but if it returns false, then FacesContext.getCurrentInstance() also returns null, and after the method exits I get this error:

javax.enterprise.resource.webcontainer.jsf.context|_ThreadID=23;_Thread
Name=Thread-3;|Exception when handling error trying to reset the
response.
java.lang.NullPointerException
at
com.sun.faces.facelets.tag.jsf.core.DeclarativeSystemEventListener.processEvent(EventHandler.java:126    )

Thanks, Ellen

1
So it is not null before you call authenticate? Actually, that surprises me. I've however never used it that way before. I think you really need to use a Filter for this.BalusC
I tested it and just before authenticate, it's not null. I can use a Filter, but it would be much cleaner to put it in a bean, since I need to look at the view parameters to determine if the user needs to be logged in to view the page. (The page displays an item based on itemId parameter - some items are public and don't require a log in at all, and some are items are restricted.)Ellen

1 Answers

1
votes

From HttpServletRequest#authenticate() javadoc:

boolean authenticate(HttpServletResponse response)
                     throws java.io.IOException,
                            ServletException

Use the container login mechanism configured for the ServletContext to authenticate the user making this request.

This method may modify and commit the argument HttpServletResponse.

(emphasis mine)

The container's implementation will redirect the response to the configured login page when the user has not been authenticated. This has likely also caused the HttpServletRequest to be destroyed and thus the current FacesContext instance to be completely disappeared.

Your best bet is to let JSF perform the checking the presence of the logged-in user and doing the redirect instead of the container. True, this requires duplicating the login page location in the JSF side, but I don't see other ways which are workable in JSF without moving the task to a Filter.

public void preRender(CompenentSystemEvent event) {
    ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();

    if (ec.getUserPrincipal() == null) {
        ec.redirect(ec.getRequestContextPath() + "/login.xhtml");
    }
}

The ExternalContext#redirect() will already implicitly call FacesContext#responseComplete(), so you don't need to do it yourself.