0

I'm developing a web application with JSF 2.0. I implemented login through a managed bean (LoginHandler) and check if the user is logged in with a filter.

Now I got the request to be able to login to the application by sending a request with username and password as parameters. Which is the best way to do that?

I tried using f:metadata and a preRenderView event, but it seems a filter is the better solution? I also tried writing an HTTPFilter on url of the login page, but the problem is I need to store user data in my managed bean, and when I first access the application I don't have a session from which to get my manage bean.

Otpion 1: f:metadata and action in managedbean LoginHandler: on login page:

<f:metadata>
    <f:viewParam name="username" value="#{loginManager.username}"/>
    <f:viewParam name="password" value="#{loginManager.password}"/>
    <f:event type="preRenderView" listener="#{loginManager.singleSignOn}"/>            
</f:metadata>

The method singleSignOn in LoginHandler checks if username and password are set. If that's the case, it does the same logic as submitting the login form (it calles the same method), and if the login is successful, it forwards to the welcome page. Otherwise it returns null (and the login page is displayed)

Otpion 2: filter:

public void doFilter(ServletRequest req, ServletResponse res,
        FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    HttpSession session = request.getSession(false);

    String username = request.getParameter("username");
    String password = request.getParameter("password");

    LoginHandler loginHandler = null;
    if (session != null) {
        loginHandler = (LoginHandler) session.getAttribute("loginHandler");

        if (loginHandler != null && username != null && password != null) {
            boolean loginOk = false;
            //do logic using methods and objects in loginHandler and set loginOk if login is successful
            loginOk = loginHandler.login(username, password);
            if (loginOk) {
                // login OK
                response.sendRedirect(request.getContextPath() + WELCOME_PAGE_URL);
            }
        }
    }

    chain.doFilter(request, response);
}

As said, option 2 has the problem that session isn't set the first time I access the application (i.e., i need to try to login twice - the second time everything works)

Thanks

EDIT1: updated comment and description to better reflect the role of the class LoginHandler

Patrick M
  • 13
  • 8
  • Option 2 is indeed more common. Solution: don't check the loginHandler (className indicates a 'service' and services should not be stored in sessions anyway). But an even better solution is to not create something homegrown. If you already have questions about this, you are bound to (unintentionally) introduce security holes. Better to use some existing framework like PicketLink, Shiro or even SpringSecurity (if you already use spring). – Kukeltje Jun 29 '17 at 10:51
  • @Kukeltje thanks for the answer. I would stick to my current framework at the moment but will consider upgrading to something like spring in a second moment. My class LoginHandler is a sessionscoped bean in the JSF application that calls logic from a login EJB, and holds information about the currently logged user. This is my first jsf/EJB application and I'm not familiar with the best practices yet. I need to use LoginHandler in the filter in order to let the user log into the application, is there a way to do it? EDIT: updated question to make it clear what I use LoginHandler for – Patrick M Jun 29 '17 at 12:05
  • So you combine a service and a bean that holds state in one? Not a good design. Split them in a LoginService (request scoped or even static methods or..) and a e.g. LoggedInUser (session scoped). https://stackoverflow.com/questions/30639785/jsf-controller-service-and-dao – Kukeltje Jun 29 '17 at 12:15

2 Answers2

4
  1. On when to use f:param & f:metadata , i will advice you read this answer as a great one is provided as to such. never use f:viewParam to handle user inputs as data is sent over HTTP GET request.

  2. It is also advised against using servlets with JSF application as session scoped beans are attributes of HTTPSessions,you will find an explanations as to why here.

  3. finally there are four ways to register a system even listner in a JSF application. With a f:event tag as you used in your question provided, by annotating a class with the @ListnerFor(systemEventClass = typeOfEvent.class), by calling a subscribeToEvent method and finally by registering a system event listner in faces-config.xml file as so

     <application>
        <system-event-listener>
          <system-event-listener-class>listenerClass</system-event-listener-class>
          <system-event-class>eventClass</system-event-class>
         </system-event-listener>
    </application>
    

In the situation where you use the f:event tag(as you did in your question) you should make sure you enclose it in a f:view tag

    <f:view>
      <f:event type="preRenderView" listener="#{loginManager.singleSignOn}"/>  
        ......
         <f:viewParam name="username" value="#{loginManager.username}"/>
         <f:viewParam name="password" value="#{loginManager.password}"/>
        ......
    </f:view>

and your bean method accepting a ComponentSystemEvent as so

public void singleSignOn(ComponentSystemEvent event) {
     if (!login) {
          //do JSF stuff here when validating credentials fails or passes
         //eg. perform navigation

        }
}

NB: there is a an answer on this question that address how to implements filters with jsf,use it as head start

for additional material, I will advice you continue with Basic steps for starting session in jsf.

basamoahjnr
  • 145
  • 11
  • 1
    Good clarification. Some additions Add1:security sensitive user inputs. Add2: login filters/servlets are exempt as they are imo not 'businesslogic'. Add3: it indeed is an option, but so is his 'option 2' as long as it is done right. – Kukeltje Jun 29 '17 at 20:45
0

I went with the prerenderview event:

singleSignOn.xhtml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html">
<f:metadata>
    <f:viewParam name="userName" value="#{loginCredentials.userName}" />
    <f:viewParam name="password" value="#{loginCredentials.password}" />
    <f:event type="preRenderView" listener="#{loginHandler.singleSignOn}" />
</f:metadata>
<h:head>
    <title>singleSignOn</title>    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</h:head>    
<h:body />      
</html>

LoginCredentials is a request scoped POJO with fields userName and password.

singleSignOn() in LoginHandler (part):

public void singleSignOn(ComponentSystemEvent event) {
    if (loginCredentials.getUserName() != null
            && loginCredentials.getPassword() != null) {
        login();
    } else {
        // error handling (i did output on syserr)
    }
}

LoginCredentials and LoginHandler.login are the same bean/method that are bound to the h:form on the login page

Patrick M
  • 13
  • 8