Spring Security with JSF 2 and custom login form

Here, I am integrating Spring Security with JSF 2 using a custom login form.

First, the maven dependencies for Spring-Security (I consider that the JSF project is already set-up, if it is not the case, you can check here):

<properties>
    ...
    <spring.security.version>3.2.4.RELEASE</spring.security.version>
</properties>
...
<!-- Spring-Security dependencies -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>${spring.security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring.security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring.security.version}</version>
</dependency>

In the web.xml, we have to configure the Spring-Security filter as follow:

<!-- Enable Spring Filter: Spring Security works on the concept of Filters -->
<!-- Declare the Spring filter -->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>
        org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
</filter>
<!-- Defines urls pattern on which the filter is applied -->
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

Note: if you don’t add the forward to it, the managed bean will not be able to send the request to the filter (I experienced that!!).

We need a simple index.xhtml which will be protected by Spring (I don’t describe it here) and a custom login page which receive the credentials typed by the user.
This page contains only the user name, the password and another field method which is here to show how we can get it on the server:

<h:form id="loginFormId" prependId="false">
    <div class="full-box" style="text-align: center">
        <div class="child-box box-c3">
            <h3>Login</h3>
            <div class="fluid-row">
                <div class="fluid-column fluid-c12">
                    <div class="forLabel" style="width: 30%">
                        <h:outputLabel for="username" value="User" />
                    </div>
                    <div class="forField" style="width: 65%">
                        <h:inputText id="username" required="true"
                            requiredMessage="Please enter username (or go to hell...)" />
                        <h:messages for="username" />
                    </div>
                </div>
                <div class="fluid-column fluid-c12">
                    <div class="forLabel" style="width: 30%">
                        <h:outputLabel for="password" value="Password" />
                    </div>
                    <div class="forField" style="width: 65%">
                        <h:inputSecret id="password" required="true"
                            requiredMessage="Please enter password (otherwise you'll die!!)"
                            name="password" />
                        <h:messages for="password" />
                    </div>
                </div>
                <div class="fluid-column fluid-c12">
                    <div class="forLabel" style="width: 30%">
                        <h:outputText value="Authentication" />
                    </div>
                    <div class="forField" style="width: 65%">
                        <h:selectBooleanCheckbox id="method" name="method" />
                        <h:outputLabel for="method" value="LDAP" />
                    </div>
                </div>
            </div>
            <ui:fragment rendered="${!empty param['error']}">
                <div>Connection failed: user and/or password are wrong.</div>
            </ui:fragment>
            <div id="loginBtnPanelId">
                <h:commandButton id="btnLoginId" value="Login" type="submit"
                    action="${loginManager.doLogin()}" styleClass="loginPanelBtn" />
            </div>
        </div>
    </div>
</h:form>

The form is very simple, we can see the 3 fields and a portion which is conditionally rendered if error param is available.
The content of this form is sent to a managed bean which is responsible of forwarding data to the Spring-Security filter:

import java.io.IOException;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

@ManagedBean
@RequestScoped
public class LoginManager {

    public String doLogin() throws IOException, ServletException {
        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;
    }

}

Now, we need to have a look to the Spring configuration file, the security is configured as follow:

<security:http use-expressions="true">
    <!-- refers to http://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html -->
    <security:intercept-url pattern="/faces/login.xhtml"
        access="anonymous" />
    <security:intercept-url pattern="/css/**"
        access="anonymous" />
    <security:intercept-url pattern="/**"
        access="authenticated" />
    <security:form-login login-page="/faces/login.xhtml"
        authentication-failure-url="/faces/login.xhtml?error=1"
        default-target-url="/faces/index.xhtml"
        authentication-details-source-ref="myAuthenticationDetailsSource"
        username-parameter="username" password-parameter="password" />
</security:http>

We can see that we have to enable anonymous access to the login form (otherwise, you will see some strange messages in your browser, I let you guess why). To have a beautiful login page, we also allow anonymous access to the CSS, the rest of the site has a restricted access controlled by Spring.
As we use a custom login form, the form-login element must indicate which one we want to use with the login-page attribute.
To be able to store the custom field (the method if you forgot it), we have to implement a custom class, it is the one we see in the authentication-details-source-ref attribute.

This is how it is defined in the Spring configuration file:

<bean id="myAuthenticationDetailsSource"
    class="net.classnotfound.jsf.spring.security.security.MyAuthenticationDetailsSource">
</bean>

And the class which implements the org.springframework.security.authentication.AuthenticationDetailsSource interface:

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

/**
 * This class provides a custom AuthenticationDetail which stores the connection
 * type method (Oracle/Ldap)
 *
 */
public class MyAuthenticationDetailsSource implements AuthenticationDetailsSource {

    /**
     * returns the {@link WebAuthenticationDetails} according to LuxFact rule
     * 
     * @param context
     */
    @Override
    public WebAuthenticationDetails buildDetails(final HttpServletRequest context) {
        return new MyAuthenticationDetails(context);
    }

}

This class provides an implementation of a org.springframework.security.web.authentication.WebAuthenticationDetails which will store our extra parameter, its implementation is very simple:

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.web.authentication.WebAuthenticationDetails;

/**
 * this class stores data used for authentication (mainly the authentication
 * method)
 * 
 */
public class MyAuthenticationDetails extends WebAuthenticationDetails {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private final String method;

    public MyAuthenticationDetails(final HttpServletRequest request) {
        super(request);
        method = request.getParameter("method");
    }

    public String getMethod() {
        return method;
    }

}

As we can see, this class does nothing else than getting the custom parameter from the request and storing it.

Now, to use this extra parameter, we need to customize the way a user is authenticated. As this task is, by default, done by Spring, it means that we have to write a new class which implements the org.springframework.security.authentication.AuthenticationProvider interface. We can see its declaration in the Spring configuration file here:

<bean id="myAuthenticationProvider"
    class="net.classnotfound.jsf.spring.security.security.MyAuthenticationProvider">
</bean>

<security:authentication-manager>
    <!-- create a custom AuthenticationProvider class to tune the login 
        process -->
    <security:authentication-provider
        ref="myAuthenticationProvider" />
</security:authentication-manager>

And its implementation:

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

public class MyAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(final Authentication authentication) throws AuthenticationException {

        Authentication res = isValid(authentication);
        if (!res.isAuthenticated()) {
            throw new BadCredentialsException("Bad credentials");
        }
        return res;
    }

    private Authentication isValid(final Authentication authentication) {
        Authentication res = authentication;
        System.out.println("Selected method: "+((MyAuthenticationDetails)authentication.getDetails()).getMethod());
        if ("Admin".equals(authentication.getPrincipal())&&"Password".equals(authentication.getCredentials())) {
            res = createSuccessAuthentication(authentication);
        }
        return res;
    }

    @Override
    public boolean supports(final Class authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

    protected Authentication createSuccessAuthentication(final Authentication authentication) {
        // Ensure we return the original credentials the user supplied,
        // so subsequent attempts are successful even with encoded passwords.
        // Also ensure we return the original getDetails(), so that future
        // authentication events after cache expiry contain the details
        final UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), authentication.getAuthorities());
        result.setDetails(authentication.getDetails());

        return result;
    }

}

As we can see, the extra parameter is extracted from the org.springframework.security.core.Authentication.getDetails() method.

The whole Spring configuration file looks like:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">


    <!-- Enable security annotations usage -->

    <bean id="myAuthenticationDetailsSource"
        class="net.classnotfound.jsf.spring.security.security.MyAuthenticationDetailsSource">
    </bean>

    <bean id="myAuthenticationProvider"
        class="net.classnotfound.jsf.spring.security.security.MyAuthenticationProvider">
    </bean>


    <security:authentication-manager>
        <!-- create a custom AuthenticationProvider class to tune the login 
            process -->
        <security:authentication-provider
            ref="myAuthenticationProvider" />
    </security:authentication-manager>

    <security:http use-expressions="true">
        <!-- refers to http://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html -->
        <security:intercept-url pattern="/faces/login.xhtml"
            access="anonymous" />
        <security:intercept-url pattern="/css/**"
            access="anonymous" />
        <security:intercept-url pattern="/**"
            access="authenticated" />
        <security:form-login login-page="/faces/login.xhtml"
            authentication-failure-url="/faces/login.xhtml?error=1"
            default-target-url="/faces/index.xhtml"
            authentication-details-source-ref="myAuthenticationDetailsSource"
            username-parameter="username" password-parameter="password" />
    </security:http>

</beans>

Source files are available here.

2 comments

  1. This works but it doesn’t render faces tags. I believe you are probably missing something like

    Faces Servlet
    javax.faces.webapp.FacesServlet
    1

    Faces Servlet
    *.xhtml

    maybe?

Leave a comment