Fork me on GitHub

Programming, Internet & more

Custom login page with JSF and Spring-Security 3

Spring-Security is great, but it is mostly used together with spring-mvc. It works well with JSF also but there are a few pitfalls, you can come across if you haven’t used spring-security with JSF before. In this tutorial I will explain how to provide a custom login page implemented in JSF for spring-security.

Requirements

  • You should have spring-security together with jsf up and running.

JSF login page

First of all, a custom login page must be created. It can be a simple JSF-page but there must some fields with their appropriate id’s. Please make sure the id’s are exactly the same as shown below. Here is my login.jsf:

<h:body>
  <h:form id="loginForm" prependId="false">
    <!-- Messages must be global here, to show bad credentials -->
    <h:messages globalOnly="true"/>
               
    <!-- Id's must not be changed to support spring security check -->
    <h:panelGrid columns="3">
    <h:outputLabel for="j_username" value="User: * " />  
    <h:inputText id="j_username" required="true" label="username" />  
    <h:message for="j_username" display="text" style="color:red"/>
                   
    <h:outputLabel for="j_password" value="Password: * " />  
    <h:inputSecret id="j_password" label="password" required="true" />  
    <h:message for="j_password" display="text" style="color:red"/>
                   
    <h:outputLabel for="_spring_security_remember_me" value="Remember me: " />
    <h:selectBooleanCheckbox    id="_spring_security_remember_me" />        
    </h:panelGrid>
           
    <h:commandButton type="submit" id="login" value="Login"
        action="#{loginController.doLogin}" />
    </h:form>
</h:body>

Login controller

To get the login page working, a controller must be provided. Therefore you’ve to create a managedBean with the name loginController. The controller will do a redirect to the standard spring-security check page, which would also be used by the standard spring login page.

Please note that the controller must implement the PhaseListener interface. This is needed to provide feedback to the user if the login was unsuccessful or some required fields are missing. Here is the LoginController:

package de.slackspace.tutorials.customloginpage;

import java.io.IOException;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.WebAttributes;

@ManagedBean(name="loginController")
@RequestScoped
public class LoginController implements PhaseListener {

     protected final Log logger = LogFactory.getLog(getClass());
   
    /**
     *
     * Redirects the login request directly to spring security check.
     * Leave this method as it is to properly support spring security.
     *
     * @return
     * @throws ServletException
     * @throws IOException
     */
    public String doLogin() throws ServletException, IOException {
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();

        RequestDispatcher dispatcher = ((ServletRequest) context.getRequest())
                .getRequestDispatcher("/j_spring_security_check");

        dispatcher.forward((ServletRequest) context.getRequest(),
                (ServletResponse) context.getResponse());

        FacesContext.getCurrentInstance().responseComplete();

        return null;
    }

    public void afterPhase(PhaseEvent event) {
    }

    /* (non-Javadoc)
     * @see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
     *
     * Do something before rendering phase.
     */
    public void beforePhase(PhaseEvent event) {
        Exception e = (Exception) FacesContext.getCurrentInstance().
          getExternalContext().getSessionMap().get(WebAttributes.AUTHENTICATION_EXCEPTION);
 
        if (e instanceof BadCredentialsException) {
            logger.debug("Found exception in session map: "+e);
            FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(
                    WebAttributes.AUTHENTICATION_EXCEPTION, null);
            FacesContext.getCurrentInstance().addMessage(null,
              new FacesMessage(FacesMessage.SEVERITY_ERROR,
                "Username or password not valid.", "Username or password not valid"));
        }
    }

    /* (non-Javadoc)
     * @see javax.faces.event.PhaseListener#getPhaseId()
     *
     * In which phase you want to interfere?
     */
    public PhaseId getPhaseId() {
        return PhaseId.RENDER_RESPONSE;
    }
}

web.xml

To keep the project structure neat and clean, I have a separate spring config for spring-security. To use it, you have to define a context-param in the web.xml.

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    /WEB-INF/spring/root-context.xml
    /WEB-INF/spring/security.xml
  </param-value>
</context-param>

To enable the login page to redirect to the standard spring security check page, we have to enable that also in web.xml.

<!-- Enable Spring Security -->
<filter>
 <filter-name>springSecurityFilterChain</filter-name>
 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
   
<!-- Allow login pages with JSF which redirects to security check,
 therefore we have to add the forward entry here -->
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
 </filter-mapping>

security.xml

The last step is to define the new custom login page in security.xml. We can use the spring login-page property for this.

<http use-expressions="true">
        <!-- Custom login page -->
        <form-login login-page="/login.jsf" />        
</http>

Conclusion

Now you can use the JSF login page. If you try to access a protected page, you should get your JSF login page instead of the default one.

Source-Code

You can find a full working example at GitHub.

Category: spring
-->

2 Comments

  1. Ali Mohamed
    Posted June 27, 2016 at 15:43 | Permalink

    Hi, first at all i want to thank you for this tutorial it’s help me a lot! I use the same code to secure my app but it works for un one user with access=”ROLE_ADMIN” and didn’t work for other rule. Can you please to solve this problem.

    Thank you!!

    • Christian
      Posted June 30, 2016 at 06:31 | Permalink

      Please look at my example project on GitHub where it will work and find out the differences between your code and the project’s code.

Post a Comment

Your email is kept private. Required fields are marked *

*
*