2
\$\begingroup\$

I've decided to implement my own custom authentication and authorisation in JSF. I am using Java's BCrypt to check the users' passwords.

The view side of things I can handle; I'm just implementing PrimeFaces validation tags later on. I use ManagedBean to validate the backend of things (if username is the right length, not any unallowed characters and if a user with a specified username exists).

Note about this file: some parts are temporary. I'm thinking of adding a util function to handle the error messages. The preformAuthentication function is now being called and even the validation fails, but it's for testing reasons.

LoginManagedBean.java

/*
 * Copyright (C) 2016 oskarmendel
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package tb.bmanager.main;

import java.io.Serializable;
import javax.ejb.EJB;
import javax.inject.Named;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import tb.bmanager.auth.AuthenticationActionBeanLocal;
import tb.bmanager.entitymanager.UserEntityFacade;
import tb.bmanager.util.validation.UserValidation;

/**
 * Controller for the login view.
 * 
 * @author oskarmendel
 * @version 0.00.00
 * %name LoginManagedBean.java
 * %date 14:38:19 PM, Jun 18, 2016
 */
@Named(value = "loginManagedBean")
@SessionScoped
public class LoginManagedBean implements Serializable{

    @EJB
    private AuthenticationActionBeanLocal login;

    @EJB
    private UserEntityFacade userFacade;

    String username;
    String password;

    private UserValidation userValidation;

    /**
     * Creates a new instance of LoginManagedBean
     */
    public LoginManagedBean() {
    }

    public void verifyLogin() {
        userValidation = UserValidation.getInstance();

        //Check username is within length & if its taken or not
        if(!userValidation.validateUsername(username)) {
            String message = "Your specified username is too long or contains illegal characters.";
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, message, null));
        }else if (userFacade.findByUsername(username) == null){
            String message = "User doesnt exists.";
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, message, null));
        }

        //Check if password is strong enough
        if(!userValidation.validatePassword(password)) {
            String message = "Your password needs to be longer than 4 characters.";
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, message, null));
        }

        login.preformAuthentication(username, password);
    }

    /**
     * 
     * @param username - the username of the user.
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 
     * @return the username of the user.
     */
    public String getUsername(){
        return this.username;
    }

    /**
     * 
     * @return the password of the user.
     */
    public String getPassword() {
        return password;
    }

    /**
     * 
     * @param password - the password for the user.
     */
    public void setPassword(String password) {
        this.password = password;
    }
}

When it comes to the server side of things, I've decided to implement the actual authentication in a EJB. This file is pretty straightforward.

The only part I'm curious about in this file is if I'm initialising the user session in a right way. Is this enough or should I add something more to this?

AuthenticationActionBean.java

/*
 * Copyright (C) 2016 oskarmendel
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package tb.bmanager.auth;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import tb.bmanager.entity.UserEntity;
import tb.bmanager.entitymanager.UserEntityFacade;
import tb.bmanager.util.BCrypt;

/**
 * Preforms the authentication of users, creating a new session if success.
 * 
 * @author oskarmendel
 * @version 0.00.00
 * %name AuthenticationActionBean.java
 * %date 17:23:53 PM, Jun 18, 2016
 */
@Stateless
public class AuthenticationActionBean implements AuthenticationActionBeanLocal {

    @EJB
    private UserEntityFacade userFacade;

    UserEntity user;

    /**
     * 
     * @param username
     * @param password
     */
    public void preformAuthentication(String username, String password) {
        FacesContext context = FacesContext.getCurrentInstance();
        System.out.println("Made it to perform Auth.");

        user = userFacade.findByUsername(username);

        if (user == null) {
            return;
        }

        if (user.getUsername().equals(username)) {
            System.out.println("Username matches");
            if(BCrypt.checkpw(password, user.getPassword())) {
                context.getExternalContext().getSessionMap().put("USER", user);
                System.out.println("Password matches");
            } else {
                String message = "The user / password combination is wrong.";
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, message, null));
            }
        }
    }
}

Here is the UserSessionFilter, the filter which checks if a user is logged in. Did I miss anything here? It's not necessary to read the whole file, just the doFilter method.

UserSessionFilter.java

/*
 * Copyright (C) 2016 oskarmendel
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package tb.bmanager.auth.filter;

import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import javax.faces.application.ResourceHandler;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 *
 * @author oskarmendel
 */
@WebFilter("/b/*")
public class UserSessionFilter implements Filter {

    private static final boolean debug = true;

    // The filter configuration object we are associated with.  If
    // this value is null, this filter instance is not currently
    // configured. 
    private FilterConfig filterConfig = null;

    private static final String AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
        + "<partial-response><redirect url=\"%s\"></redirect></partial-response>";


    public UserSessionFilter() {
    }    

    private void doBeforeProcessing(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
        if (debug) {
            log("UserSessionFilter:DoBeforeProcessing");
        }

        // Write code here to process the request and/or response before
        // the rest of the filter chain is invoked.
        // For example, a logging filter might log items on the request object,
        // such as the parameters.
        /*
    for (Enumeration en = request.getParameterNames(); en.hasMoreElements(); ) {
        String name = (String)en.nextElement();
        String values[] = request.getParameterValues(name);
        int n = values.length;
        StringBuffer buf = new StringBuffer();
        buf.append(name);
        buf.append("=");
        for(int i=0; i < n; i++) {
            buf.append(values[i]);
            if (i < n-1)
                buf.append(",");
        }
        log(buf.toString());
    }
         */
    }    

    private void doAfterProcessing(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
        if (debug) {
            log("UserSessionFilter:DoAfterProcessing");
        }

        // Write code here to process the request and/or response after
        // the rest of the filter chain is invoked.
        // For example, a logging filter might log the attributes on the
        // request object after the request has been processed. 
        /*
    for (Enumeration en = request.getAttributeNames(); en.hasMoreElements(); ) {
        String name = (String)en.nextElement();
        Object value = request.getAttribute(name);
        log("attribute: " + name + "=" + value.toString());

    }
         */
        // For example, a filter might append something to the response.
        /*
    PrintWriter respOut = new PrintWriter(response.getWriter());
    respOut.println("<P><B>This has been appended by an intrusive filter.</B>");
         */
    }

    /**
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param chain The filter chain we are processing
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet error occurs
     */
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        if (debug) {
            log("UserSessionFilter:doFilter()");
        }

        doBeforeProcessing(request, response);

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        HttpSession session = req.getSession(false);
        String loginURL = req.getContextPath() + "/login.xhtml";

        boolean loggedIn = (session != null) && (session.getAttribute("USER") != null);
        boolean loginRequest = req.getRequestURI().equals(loginURL);
        boolean resourceRequest = req.getRequestURI().startsWith(req.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/");
        boolean ajaxRequest = "partial/ajax".equals(req.getHeader("Faces-Request"));


        Throwable problem = null;
        try {
            if (loggedIn || resourceRequest) {
                if(!resourceRequest) {
                    res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
                    res.setHeader("Pragma", "no-cache"); // HTTP 1.0.
                    res.setDateHeader("Expires", 0); // Proxies.
                }
                chain.doFilter(req, res);
            }else if (ajaxRequest) {
                res.setContentType("text/xml");
                res.setCharacterEncoding("UTF-8");
                res.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
            }else {
                res.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
            }
        } catch (Throwable t) {
            // If an exception is thrown somewhere down the filter chain,
            // we still want to execute our after processing, and then
            // rethrow the problem after that.
            problem = t;
            t.printStackTrace();
        }

        doAfterProcessing(request, response);

        // If there was a problem, we want to rethrow it if it is
        // a known type, otherwise log it.
        if (problem != null) {
            if (problem instanceof ServletException) {
                throw (ServletException) problem;
            }
            if (problem instanceof IOException) {
                throw (IOException) problem;
            }
            sendProcessingError(problem, response);
        }
    }

    /**
     * Return the filter configuration object for this filter.
     */
    public FilterConfig getFilterConfig() {
        return (this.filterConfig);
    }

    /**
     * Set the filter configuration object for this filter.
     *
     * @param filterConfig The filter configuration object
     */
    public void setFilterConfig(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
    }

    /**
     * Destroy method for this filter
     */
    public void destroy() {        
    }

    /**
     * Init method for this filter
     */
    public void init(FilterConfig filterConfig) {        
        this.filterConfig = filterConfig;
        if (filterConfig != null) {
            if (debug) {                
                log("UserSessionFilter:Initializing filter");
            }
        }
    }

    /**
     * Return a String representation of this object.
     */
    @Override
    public String toString() {
        if (filterConfig == null) {
            return ("UserSessionFilter()");
        }
        StringBuffer sb = new StringBuffer("UserSessionFilter(");
        sb.append(filterConfig);
        sb.append(")");
        return (sb.toString());
    }

    private void sendProcessingError(Throwable t, ServletResponse response) {
        String stackTrace = getStackTrace(t);        

        if (stackTrace != null && !stackTrace.equals("")) {
            try {
                response.setContentType("text/html");
                PrintStream ps = new PrintStream(response.getOutputStream());
                PrintWriter pw = new PrintWriter(ps);                
                pw.print("<html>\n<head>\n<title>Error</title>\n</head>\n<body>\n"); //NOI18N

                // PENDING! Localize this for next official release
                pw.print("<h1>The resource did not process correctly</h1>\n<pre>\n");                
                pw.print(stackTrace);                
                pw.print("</pre></body>\n</html>"); //NOI18N
                pw.close();
                ps.close();
                response.getOutputStream().close();
            } catch (Exception ex) {
            }
        } else {
            try {
                PrintStream ps = new PrintStream(response.getOutputStream());
                t.printStackTrace(ps);
                ps.close();
                response.getOutputStream().close();
            } catch (Exception ex) {
            }
        }
    }

    public static String getStackTrace(Throwable t) {
        String stackTrace = null;
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            pw.close();
            sw.close();
            stackTrace = sw.getBuffer().toString();
        } catch (Exception ex) {
        }
        return stackTrace;
    }

    public void log(String msg) {
        filterConfig.getServletContext().log(msg);        
    }

}

What did I forget or what could I add besides protection from CSRF? I want my applications auth to be well implemented from the start so I don't have to go back and redo it.

\$\endgroup\$
1
\$\begingroup\$

Warning!

The AuthenticationActionBean is stateless and not allowed to contain statefull informations. But actually it contains a user! This is bad because if two people are logging in at the nearly same time it may be that both are logged in even if the second one had a wrong password.

Furthermore if you send the login-form twice, the preformAuthentication may becomes values that has changed meanwhile and not yet checked. Example:

public void verifyLogin() { <-- here you send "Admin" and "Test123"
    userValidation = UserValidation.getInstance();

    //Check username is within length & if its taken or not
    if(!userValidation.validateUsername(username)) {
        String message = "Your specified username is too long or contains illegal characters.";
        FacesContext.getCurrentInstance().addMessage(null, 
            new FacesMessage(FacesMessage.SEVERITY_WARN, message, null));
    }else if (userFacade.findByUsername(username) == null){
        String message = "User doesnt exists.";
        FacesContext.getCurrentInstance().addMessage(null, 
            new FacesMessage(FacesMessage.SEVERITY_WARN, message, null));
    }

    //Check if password is strong enough
    if(!userValidation.validatePassword(password)) {
        String message = "Your password needs to be longer than 4 characters.";
        FacesContext.getCurrentInstance().addMessage(null, 
            new FacesMessage(FacesMessage.SEVERITY_WARN, message, null));
    }
    <-- here you send the form again but change the values to "" and "".
    login.preformAuthentication(username, password); 

    <-- the values from the second 
    <-- form submit has not yet been checked!
    <--
    <-- Yes, the seconds submit will fail, but the first submit
    <-- checks values from second submit.
}
\$\endgroup\$
  • \$\begingroup\$ Great help thanks, good that you pointed out the problem with the stateless bean. This problem should be resolved just by switching annotations correct me if I'm wrong. As for the other issue, i am not completely sure about how to solve this.. Any ideas? \$\endgroup\$ – BrokenProgrammer Jun 22 '16 at 13:57
  • \$\begingroup\$ Did you understand the second issue? \$\endgroup\$ – Peter Rader Jun 22 '16 at 15:17
  • \$\begingroup\$ It is if the form would be submitted twice without the server-side finishes, correct me if i misunderstood. If so i would have to add some functionality that checks if there is already an authentication in progress for this session or am i totally off track here? \$\endgroup\$ – BrokenProgrammer Jun 22 '16 at 16:27
  • \$\begingroup\$ Exactly! I like the way you think. I would suggest to add a String username = this.username; at the first line of the method verifyLogin. This way you push the username to a thread-local-variable and the whole method does use the very own copy of the username. Do the same to the password. \$\endgroup\$ – Peter Rader Jun 22 '16 at 17:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.