我有一个小的Spring / JSF / JDBC webapp,在添加Spring Security之前一直运行良好。自添加springsecurityfilterchain以来,登录/欢迎屏幕上的注册按钮(而不是登录)不再起作用。出现JSF primefaces加载栏,但无限期保留而不转换到下一页。 我的初步调查让我相信它与springsecurityfilterchain有关,因为评论这段代码基本上可以打开/关闭问题。
非常感谢任何帮助。
代码: 的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>
应用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>
安全-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>
主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>
帐户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包含以下依赖项(由于大小限制而被截断):
<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>
答案 0 :(得分:0)
我们遇到了完全相同的问题。该问题源于Spring过滤器链。启用 spring-security 后,您在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>
</filter-mapping>
这会导致添加一组完整的过滤器。其中一个过滤器是为了防止CSRF (Cross-site Request Forgery)。不知何故, webflow-2 和 spring-security 的组合会导致CSRF令牌在处理链中丢失。当查看过滤器链在CsrfFilter
中停止的相应日志部分时,这一点就变得明显了。
DEBUG 2016-07-25 06:47:59,013 (FilterChainProxy.java:324) - /start-flow.do?execution=e1s1 at position 1 of 14 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG 2016-07-25 06:47:59,013 (HttpSessionSecurityContextRepository.java:171) - HttpSession returned null object for SPRING_SECURITY_CONTEXT
DEBUG 2016-07-25 06:47:59,014 (HttpSessionSecurityContextRepository.java:101) - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@42281bdb. A new one will be created.
DEBUG 2016-07-25 06:47:59,014 (FilterChainProxy.java:324) - /start-flow.do?execution=e1s1 at position 2 of 14 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
DEBUG 2016-07-25 06:47:59,014 (FilterChainProxy.java:324) - /start-flow.do?execution=e1s1 at position 3 of 14 in additional filter chain; firing Filter: 'HeaderWriterFilter'
DEBUG 2016-07-25 06:47:59,014 (HstsHeaderWriter.java:128) - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@481c2a9e
DEBUG 2016-07-25 06:47:59,014 (FilterChainProxy.java:324) - /start-flow.do?execution=e1s1 at position 4 of 14 in additional filter chain; firing Filter: 'CsrfFilter'
DEBUG 2016-07-25 06:47:59,015 (CsrfFilter.java:106) - Invalid CSRF token found for http://localhost:8080/center/start-flow.do?execution=e1s1
在最后一行中,清楚地显示执行的最后一个过滤器是CsrfFilter
,它是我们设置中14个过滤器中的4个过滤器。它会抛出错误Invalid CSRF token found for ....
并取消过滤器链。如果我们在CsrfFilter
中设置断点,我们也可以清楚地看到csrfToken
实际上是null
。
作为一个快速解决方案,我们现在只是禁用了CSRF并对此表示满意:
<security:http auto-config="true" use-expressions="true">
<!-- CSRF: Cross-site request forgery must be DISABLED when using web-flow!
Web-flow request do not provide correct CSRF tokens!!! -->
<security:csrf disabled="true"/>
<security:intercept-url pattern="/**" access="permitAll" />
<security:intercept-url pattern="/secure/**" access="hasRole('ROLE_USER')" />
<security:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />
</security:http>
截至目前,我担心如果您需要CSRF,您需要提供自己的修复程序。关于webflow-2中关于CSRF的SO的讨论请参阅:here(有一些详细的安全问题here)。