In a previous post I showed you how to add Spring Security to your application.
In this continuation I’m going to show how to integrate JSF and Spring Security 3.
At the end, we’ll have a custom JSF login page.
The login page will continue to be shown until a successful authentication will occur.
Spring Security’s authentication error exception will be displayed as a JSF h:messages in the login page itself.

Our login page:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core"%>
<%@ taglib prefix="c_rt" uri="http://java.sun.com/jstl/core_rt"%>
<%@ taglib prefix="f"  uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h"  uri="http://java.sun.com/jsf/html"%>

<html>
    <head>
        <title>Spring Security Login</title>
    </head>
	<body>
		<f:view >
		    <h:form >
		        <h:panelGrid columns="2">
		            <h:outputLabel value="User Name" for="j_username"/>
		            <h:inputText id="j_username"
		                           value="#{loginBean.username}"
		                           required="true"/>
		            <h:outputLabel value="Password" for="j_password"/>
		            <h:inputSecret id="j_password"
		                             value="#{loginBean.password}"
		                             required="true"/>
		        </h:panelGrid>
		        <h:commandButton action="#{loginBean.doLogin}" value="Login"/>
		        <h:messages style="color: red;"/>
		    </h:form>
		</f:view>
    </body>
</html>

How our login page will look like:

Changes to be made to web.xml:

	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
		 <dispatcher>FORWARD</dispatcher>
		 <dispatcher>REQUEST</dispatcher>
	</filter-mapping>

The backing bean LoginBean.java:

package com.tutorial.backingbeans;

import java.io.IOException;

import javax.faces.application.FacesMessage;
import javax.faces.bean.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.tutorial.util.FacesUtils;

public class LoginBean {

	private String username;
	private String password;

	public LoginBean() {
	}

	public String doLogin() throws IOException, ServletException {
		ExternalContext context = FacesContext.getCurrentInstance()
				.getExternalContext();

		RequestDispatcher dispatcher = ((ServletRequest) context.getRequest())
				.getRequestDispatcher("/j_spring_security_check?j_username=" + username
								+ "&j_password=" + password);

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

		FacesContext.getCurrentInstance().responseComplete();
		// It's OK to return null here because Faces is just going to exit.
		return null;
	}

	public String getPassword() {
		return password;
	}

	public String getUsername() {
		return username;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setUsername(String username) {
		this.username = username;
	}
}

Changes to be made to applicationContext-secutiry.xml:

		<form-login
			login-page="/login.jsf"
			default-target-url="/loginSuccess.jsp"
			/>

LoginErrorPhaseListener.java:
This listener will capture Spring Security’s exceptions and show them in the login page as JSF messages.

package com.tutorial.listener;

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AbstractProcessingFilter;

import com.tutorial.util.FacesUtils;

public class LoginErrorPhaseListener implements PhaseListener
{
    private static final long serialVersionUID = -1216620620302322995L;

    @Override
    public void afterPhase(final PhaseEvent arg0)
    {}

    @Override
    public void beforePhase(final PhaseEvent arg0)
    {
        Exception e = (Exception) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(
                AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY);

        if (e instanceof BadCredentialsException)
        {
            FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(
                    AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY, null);
            FacesUtils.addErrorMessage("Username or password not valid.");
        }
    }

    @Override
    public PhaseId getPhaseId()
    {
        return PhaseId.RENDER_RESPONSE;
    }

}

We have to add this listener to faces-config.xml:

 <lifecycle>
  <phase-listener>com.tutorial.listener.LoginErrorPhaseListener</phase-listener>
 </lifecycle>

FacesUtils.java

package com.tutorial.util;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public class FacesUtils {

	public static void addErrorMessage(String msg) {
		addMessage(FacesMessage.SEVERITY_ERROR, msg);
	}

	private static void addMessage(FacesMessage.Severity severity, String msg) {
		final FacesMessage facesMsg = new FacesMessage(severity, msg, msg);
		FacesContext.getCurrentInstance().addMessage(null, facesMsg);
	}

	public static void addSuccessMessage(String msg) {
		addMessage(FacesMessage.SEVERITY_INFO, msg);
	}

	public static String getBundleKey(String bundleName, String key) {
		return FacesContext
				.getCurrentInstance()
				.getApplication()
				.getResourceBundle(FacesContext.getCurrentInstance(),
						bundleName).getString(key);
	}

}
 

2 Responses to Spring Security 3 + JSF integration

  1. Cássio says:

    Where are your authetication Provider in springsecurity.xml?

Leave a Reply