In this recipe, we will use Spring security support to develop a JSF login application. The big surprise is that we will not use the classical approach, which is very complicated and problematic.
We have developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library. In addition, we have used Acegi/Spring libraries, which provide support for JSF 2.0. The necessary libraries are in the book code bundle, under the /JSF_libs/Acegi-Spring JSF 2.0
folder.
The key of this recipe consists in using an HttpRequestDispatcher
to provide support for JSF and Spring Security to function properly (JSF first, Spring after it). The bean that will map login credentials and apply the HttpRequestDispatcher
is listed next:
package packt.spring.login; import java.io.IOException; 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; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope("request") public class SpringLoginBean { private String user; private String password; private boolean storeUser = false; private boolean logIn = false; public String getUser() { return this.user; } public void setUser(final String user) { this.user = user; } public String getPassword() { return this.password; } public void setPassword(final String password) { this.password = password; } public boolean isStoreUser() { return this.storeUser; } public void setStoreUser(final boolean storeUser) { this.storeUser = storeUser; } public boolean isLogIn() { return this.logIn; } public void setLogIn(final boolean logIn) { this.logIn = logIn; } public String loginAction() 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; } }
Next, configure the Spring Security Filter Chain in web.xml
to process Servlet FORWARD as well as REQUEST.
<!-- Filter Config --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <!-- Filter Mappings --> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> </filter-mapping>
The Spring Security configuration is accomplished in the application_security-config.xml
file, listed next (the login-processing-url
value is /j_spring_security_check
, which is the location where the HttpRequestDispatcher
will make the forward):
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/ spring-security-2.0.1.xsd"> <global-method-security secured-annotations="enabled"> </global-method-security> <http auto-config="true" access-denied-page="/forbidden.jsp"> <intercept-url pattern="/faces/secured**" access="ROLE_ADMIN,ROLE_GUEST" /> <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" /> <form-login login-processing-url="/j_spring_security_check" login-page="/faces/login.jsp" default-target-url="/" authentication-failure-url="/faces/login.jsp" /> <logout logout-url="/logout*" logout-success-url="/" /> </http> <!-- User:admin Password:admin User:guest Password:guest --> <authentication-provider> <password-encoder hash="md5"/> <user-service> <user name="admin" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_ADMIN,ROLE_GUEST" /> <user name="guest" password="084e0343a0486ff05530df6c705c8bb4" authorities="ROLE_GUEST" /> </user-service> </authentication-provider> </beans:beans>
Finally, the login.jsp
page is in accordance with Spring Security's parameter naming specification. The submitted info is passed to the Spring Security Filter Chain (do not modify the j_username, j_password, _spring_security_remember_me
ids).
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%> <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <f:view> <h:form id="loginForm" prependId="false"> <h:panelGrid columns="4" footerClass="subtitle" headerClass="subtitlebig" styleClass="medium" columnClasses="subtitle,medium"> <f:facet name="header"> <h:outputText value="Login page:"/> </f:facet> <label for="j_username"> <h:outputText value="User:" /> </label> <h:inputText id="j_username" required="true" /> <label for="j_password"> <h:outputText value="Password:" /> </label> <h:inputSecret id="j_password" required="true" /> <label for="_spring_security_remember_me"> <h:outputText value="Remember me" /> </label> <h:selectBooleanCheckbox id="_spring_security_remember_me" /> <h:outputText value=" " /> <h:commandButton type="submit" id="login" action="#{springLoginBean.loginAction}" value="Login" /> </h:panelGrid> </h:form> <h:messages /> </f:view>
The login page will look like the following screenshot (when the secured page is forbidden you will be forwarded to this page):
Well, as you can see the idea is pretty simple. Instead of the hard work that is imposed by the classical approach, you can use a simple forward to a servlet. You don't even need a JSF backing bean, because the values only need to be intercepted by Spring Security on forward. This is not a problem if you still want to take advantage of JSF converters and validations.