Archive

Author Archive

JSF and Spring Security Integration with JSF login page

February 26th, 2010

Few days ago, I was trying to integrate Facelets and Spring Security together with JSF login page rather than using jsp login page. After checking some articles and blogs I was finally able to integrate JSF 1.1 with Facelets and Spring Security .

I used JSF 1.1.4, Facelets 1.1.11 and Spring Security 2.0.4 and following configuration files.

web.xml :

<context-param>
	<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
	<param-value>.xhtml</param-value>
</context-param>

<context-param>
	<param-name>facelets.DEVELOPMENT</param-name>
	<param-value>true</param-value>
</context-param>

<context-param>
	<param-name>facelets.VIEW_MAPPINGS</param-name>
	<param-value>*.xhtml</param-value>
</context-param>

...

<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>
	<dispatcher>INCLUDE</dispatcher>
	<dispatcher>ERROR</dispatcher>
</filter-mapping>

...

<!-- Faces Servlet -->
<servlet>
	<servlet-name>Faces Servlet</servlet-name>
	<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>

<!-- Faces Servlet Mapping -->
<servlet-mapping>
	<servlet-name>Faces Servlet</servlet-name>
	<url-pattern>/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
	<servlet-name>Faces Servlet</servlet-name>
	<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>

<!-- Welcome files -->
<welcome-file-list>
	<welcome-file>/index.xtml</welcome-file>
</welcome-file-list>

applicationContext.xml :


<security:http auto-config='true' once-per-request="false">
	<security:intercept-url pattern="/login.xhtml*" filters="none"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" />
	<security:form-login login-page="/login.xhtml" default-target-url="/index.xhtml" authentication-failure-url="/login.xhtml?error=1" login-processing-url="/j_spring_security_check.xhtml" always-use-default-target="true" />

	<security:concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" expired-url="/login.xhtml?sessionInvaild=true" />
</security:http>

<security:authentication-provider>
	<security:user-service>
		<security:user name="roger" password="roger" authorities="ROLE_USER, ROLE_ADMIN" />
		<security:user name="demouser" password="demo" authorities="ROLE_USER" />
	</security:user-service>
</security:authentication-provider>

faces-config.xml


<navigation-rule>
	<from-view-id>/login.xhtml</from-view-id>
	<navigation-case>
		<from-outcome>dologin</from-outcome>
		<to-view-id>/j_spring_security_check.jsp</to-view-id>
	</navigation-case>
</navigation-rule>

<navigation-rule>
	<from-view-id>/*</from-view-id>
	<navigation-case>
		<from-outcome>logout</from-outcome>
		<to-view-id>/j_spring_security_logout.jsp</to-view-id>
	</navigation-case>
</navigation-rule>

Notice that the from-view-id has a .xhtml extension, but the to-view-id remains .jsp.
The reason to keep the to-view-id as jsp is – When processing navigation rules to a new facelet, there is no servlet forward like there would be with JSPs(facelets are not servlets). So there is no chance for the Spring Security filter chain to fire and do the login handling.
However, just switching to the .jsp extension is not enough, by default facelets believe that all navigation rules will only refer to facelets. So by default, even with the above rule, facelets tries to parse a file called “j_spring_security_check.xhtml”.

The entry facelets.VIEW_MAPPINGS as *.xhtml tells facelets that only navigation rules with to-view-ids that end in “.xhtml” should be treated as facelets and everything else should be deferred back to the default view handler. This allows “/j_spring_security_check.jsp” to be seen as something outside of facelets and causes a servlet forward to occur. But there is still one catch. Even though your navigation rule tells JSF to forward onto “/j_spring_security_check.jsp”, the forward request actually says “/j_spring_security_check.xhtml”. So you will need to update your Spring configuration to update the login-processing-url.

The above solution worked perfectly for jsf 1.1 however the same solution stopped working once I upgraded to JSF 1.2.

The configuration I was trying was with JSF 1.2.7, Facelets 1.1.12 and Spring Security 2.0.4.

While debugging the issue, I found out that in JSF 1.2 there are some new changes in its view handling. With jsf 1.2 the above solution will not work as the jsf view handler treats the /j_spring_security_check.jsp as facelet. Hence irrespective of extension, it always append .xhtml (javax.faces.DEFAULT_SUFFIX) by default for to-view-id and treats the request as new facelet request without causing the servlet forward. So there is no chance for the Spring Security filter chain to fire and do the login handling.

There are two solutions to fix the above issue.

Solution 1 :
As there are changes to view handler in jsf 1.2, we need to modify the web.xml and applicationContext.xml to make the login work for JSF 1.2.
You need to make the changes in web.xml such that you can use Facelets and JSP in the same application. The changes required to use Facelets and JSP in the same application are specified in Facelets FAQ http://wiki.java.net/bin/view/Projects/FaceletsFAQ#How_do_I_use_Facelets_and_JSP_in. Basically you need to specify facelets to use prefix mapping and jsp to use extension mapping. With this changes the view handler will not treat the jsp as facelet and causes a servlet forward to occur. However since now facelets use the prefix mapping and jsp use extension mapping, the .jsp will not change to .xhtml like the one mentioned above JSF1.1 example. So the login-processing-url needs to point at “/j_spring_security_check.jsp”. With this changes everything works properly.

Changed files.

web.xml :

   <context-param>
   	<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
   	<param-value>.xhtml</param-value>
   </context-param>

   <context-param>
   	<param-name>facelets.DEVELOPMENT</param-name>
   	<param-value>true</param-value>
   </context-param>

   <context-param>
   	<param-name>facelets.VIEW_MAPPINGS</param-name>
   	<param-value>*.xhtml</param-value>
   </context-param>

   ...

   <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>
   	<dispatcher>INCLUDE</dispatcher>
   	<dispatcher>ERROR</dispatcher>
   </filter-mapping>

   ...

   <!-- Faces Servlet -->
   <servlet>
   	<servlet-name>Faces Servlet</servlet-name>
   	<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
   	<load-on-startup>1</load-on-startup>
   </servlet>

   <!-- Faces Servlet Mapping -->
   <servlet-mapping>
   		<servlet-name>Faces Servlet</servlet-name>
   		<url-pattern>/faces/*</url-pattern>
   </servlet-mapping>   

   <!-- Welcome files -->
   <welcome-file-list>
   	     <welcome-file>/faces/index.xtml</welcome-file>
   </welcome-file-list>

applicationContext.xml :


 <security:http auto-config='true' once-per-request="false">
 	<security:intercept-url pattern="/faces/login.xhtml*" filters="none"/>
 	<security:intercept-url pattern="/**" access="ROLE_USER" />
 	<security:form-login login-page='/faces/login.xhtml' default-target-url='/faces/index.xhtml' authentication-failure-url="/faces/login.xhtml?error=1" login-processing-url="/j_spring_security_check.jsp" always-use-default-target='true' />
 	<security:concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" expired-url="/faces/login.xhtml?sessionInvaild=true" />
 </security:http>

 <security:authentication-provider>
 	<security:user-service>
 		<security:user name="roger" password="roger" authorities="ROLE_USER, ROLE_ADMIN" />
 		<security:user name="demouser" password="demo" authorities="ROLE_USER" />
 	</security:user-service>
 </security:authentication-provider>

Solution 2 :
To use the login action in bean and then use HttpRequestDispatcher to cause forword to /j_spring_security_check.jsp, there is one toturial on http://ocpsoft.com/java/acegi-spring-security-jsf-login-page/ which shows how to implement it.

JSF ,