I have a small Spring/ JSF/ JDBC webapp, which worked fine until Spring Security was added. Since adding the springsecurityfilterchain, the sign-up button (as opposed to sign-in) on the login/welcome screen does not work anymore. the JSF primefaces loading bar appears, but stays indefinitely without transitioning to the next page. My initial investigation leads me to believe it has something to do with the springsecurityfilterchain, as commenting in/out this piece of code basically turns the problem on/off.
Any help is much appreciated.
Code: web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>KimaPortal</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>Resources Servlet</servlet-name>
<servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Resources Servlet</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>charEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>charEncodingFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<context-param>
<description>State saving method: 'client' or 'server' (=default). See JSF Specification 2.5.2</description>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
<param-value>resources.application</param-value>
</context-param>
<listener>
<listener-class>com.sun.faces.config.ConfigureListener</listener-class>
</listener>
<!-- Spring security filters -->
<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>
</filter-mapping>
</web-app>
application-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.xsd">
<import resource="datasource-config.xml" />
<import resource="webflow-config.xml"/>
<import resource="security-config.xml"/>
<!-- <bean id="userEntity" class="org.bluprnt.KimaPortal.domain.UserEntity"/> -->
<bean id="userEntityServices" class="org.bluprnt.KimaPortal.domain.UserEntityServices">
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="applicationContextProvider" class="org.bluprnt.KimaPortal.domain.ApplicationContextProvider"/>
</beans>
webflow-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xmlns:faces="http://www.springframework.org/schema/faces"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd
http://www.springframework.org/schema/faces
http://www.springframework.org/schema/faces/spring-faces.xsd">
<bean id="facesContextListener" class="org.springframework.faces.webflow.FlowFacesContextLifecycleListener">
</bean>
<!-- <bean id="jpaFlowExecutionListener" class="org.springframework.webflow.persistence.JpaFlowExecutionListener"> -->
<!-- <constructor-arg ref="entityManagerFactory" /> -->
<!-- <constructor-arg ref="transactionManager" /> -->
<!-- </bean> -->
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-listeners>
<webflow:listener ref="facesContextListener"/>
<webflow:listener ref="securityFlowExecutionListener" />
<!-- <webflow:listener ref="jpaFlowExecutionListener" /> -->
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<webflow:flow-registry id="flowRegistry" flow-builder-services="facesFlowBuilderServices" base-path="WEB-INF/flows">
<webflow:flow-location-pattern value="/**/*-flow.xml" />
</webflow:flow-registry>
<faces:flow-builder-services id="facesFlowBuilderServices" development="true"/>
<faces:resources/>
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="order" value="1"/>
<property name="flowRegistry" ref="flowRegistry"/>
<property name="defaultHandler">
<bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
</property>
</bean>
<bean class="org.springframework.faces.webflow.JsfFlowHandlerAdapter">
<property name ="flowExecutor" ref="flowExecutor" />
</bean>
<bean id="faceletsViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.faces.mvc.JsfView" />
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".xhtml" />
</bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<bean id="securityFlowExecutionListener"
class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
</beans>
security-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd">
<security:http auto-config="true">
<security:form-login login-page="/app/main" default-target-url="/app/account" />
<security:logout logout-url="/app/logout" logout-success-url="/app/main" />
</security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref="userEntityServices">
<security:password-encoder hash="md5"/>
</security:authentication-provider>
</security:authentication-manager>
<bean id="userEntityAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userEntityServices" />
<property name="hideUserNotFoundExceptions" value="false"/>
</bean>
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<ref bean="daoAuthenticationProvider" />
</constructor-arg>
</bean>
<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userEntityServices"/>
</bean>
</beans>
main-flow.xml
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="user" class="org.bluprnt.KimaPortal.domain.UserEntity" />
<view-state id="welcome" view="welcome.xhtml">
<transition on="newUser" to="signUp"/>
</view-state>
<view-state id="signUp" view="signUp.xhtml" model="user">
<transition on="backToSignIn" to="welcome"/>
<transition on="confirmSignUp" to="authentication">
<evaluate expression="userEntityServices.createUserEntity(user)" />
</transition>
</view-state>
<action-state id="authentication">
<evaluate expression="userEntityServices.authenticateUser(user)" />
<transition on="yes" to="authenticationSuccessful" />
<transition on="no" to="welcome" />
</action-state>
<end-state id="authenticationSuccessful" view="externalRedirect:account" />
</flow>
account-flow.xml
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<!-- use spring roles tables TO DO -->
<secured attributes="ROLE_USER"/>
<view-state id="userHome" view="authenticatedWelcome.xhtml">
</view-state>
</flow>
UserEntityServices.java
package org.bluprnt.KimaPortal.domain;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
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.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.*;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.DataSourceUtils;
public class UserEntityServices implements UserDetailsService {
private ApplicationContext ctx;
private DataSource ds;
private Connection c;
private AuthenticationManager authenticationManager;
public void createUserEntity(UserEntity user) {
try {
// Check if user exists
if (checkIfUserExists(user) == false){
createDBConnectionIfNA();
// retrieve a list of three random cities
String statement = new String("insert into kima_users " +
"values ('" + user.getUserName() +"', '"
+ user.getPassword() + "', '"
+ user.getFirstName() + "', '"
+ user.getLastName() + "');");
System.out.println(statement);
PreparedStatement ps = c.prepareStatement(statement);
ps.execute();
} else {
// throw error about user already existing
// TO DO !!
}
} catch (SQLException ex) {
// something has failed and we print a stack trace to analyze the error
ex.printStackTrace();
// ignore failure closing connection
try { c.close(); } catch (SQLException e) { }
} finally {
// properly release our connection
DataSourceUtils.releaseConnection(c, ds);
this.c = null;
this.ds = null;
}
}
public boolean checkIfUserExists (UserEntity user){
try {
createDBConnectionIfNA();
// SQL statement to check whether username already exists in DB
String statement = new String("select * from kima_users where userName = '" + user.getUserName() + "' ");
System.out.println(statement);
PreparedStatement ps = c.prepareStatement(statement);
ResultSet rs = ps.executeQuery();
if (!rs.next()){
//user does not exist
return false;
}else{
//user exists
return true;
}
} catch (SQLException ex) {
// something has failed and we print a stack trace to analyze the error
ex.printStackTrace();
// ignore failure closing connection
try { c.close();} catch (SQLException e) { }
} finally {
// properly release our connection
DataSourceUtils.releaseConnection(c, ds);
this.c = null;
this.ds = null;
}
//conservative guaranteed return value
return true;
}
//method to retrieve userEntity from DB by user name
public UserEntity getUserFromDBByUserName (String userName) {
try {
UserEntity user = new UserEntity();
createDBConnectionIfNA();
String statement = new String("select * from kima_users where userName = '" + userName + "' ");
System.out.println(statement);
PreparedStatement ps = c.prepareStatement(statement);
ResultSet rs = ps.executeQuery();
user.setUserName(rs.getString("userName"));
user.setPassword(rs.getString("password"));
user.setFirstName(rs.getString("firstName"));
user.setLastName(rs.getString("lastName"));
return user;
} catch (SQLException ex) {
// something has failed and we print a stack trace to analyse the error
ex.printStackTrace();
// ignore failure closing connection
try { c.close();} catch (SQLException e) { }
} finally {
// properly release our connection
DataSourceUtils.releaseConnection(c, ds);
this.c = null;
this.ds = null;
}
return null;
}
//method for user authentication as part of login procedure main-flow to account-flow
public boolean authenticateUser (UserEntity user){
System.out.println("2");
try {
Authentication request = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
//authenticationManager.authenticate calls loadUserByUsername(username)
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
return true;
} catch(AuthenticationException e){
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), "Sorry!"));
return false;
}
}
public AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
//userDetailsService implementation for spring security
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
System.out.println("1");
UserEntity user = getUserFromDBByUserName(userName);
if (user == null){
throw new UsernameNotFoundException(String.format("No such user with name '%s'", userName));
}
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
User userDetails = new User(user.getUserName(), user.getPassword(), authorities);
return userDetails;
}
// method to create DB connection and to pass on applicationContext, dataSource and Connection as instance variable
// ATTENTION: dataSource and connection to be closed after query is execute
private void createDBConnectionIfNA(){
// check whether there is an existing connection - create one if not
if (!(this.ctx != null && this.ds != null && this.c != null)) {
// Create a new application context. this processes the Spring config
ApplicationContextProvider acp = new ApplicationContextProvider();
ApplicationContext ctx = acp.getApplicationContext();
// Retrieve the data source from the application context
DataSource ds = (DataSource) ctx.getBean("dataSource");
// Open a database connection using Spring's DataSourceUtils
java.sql.Connection c = DataSourceUtils.getConnection(ds);
//pass on connection objects to instance variables
this.ctx = ctx;
this.ds = ds;
this.c = c;
}
}
}
pom.xml includes following dependencies (truncated due to size limit):
<org.springframework.version>4.2.4.RELEASE</org.springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>2.2</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-faces</artifactId>
<version>2.4.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.2.13</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>5.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>com.sun.facelets</groupId>
<artifactId>jsf-facelets</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.2.13</version>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>2.5</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-webflow</artifactId>
<version>2.4.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
</dependencies>