java - 在自定义UserDetailsService中总是空的,参数名为 loadUserByUsername

我正在使用带有自定义UserDetailsService的spring-security进行基于表单的登录,登录表单可以正确提交。在调试应用程序时,我发现提交的请求到达了UsernamePasswordAuthenticationFilter的tryAuthentication方法。但是请求参数不能映射到username和password字段,因为它们一直为空,因此,自定义UserDetailsService的loadByUsername方法的参数username是空的,我无法正常登录。

首先,我在springSecurityFilterChain中设置web.xml:


<welcome-file-list>
 <welcome-file>index.xhtml</welcome-file>
</welcome-file-list>

<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>
 classpath:WEB-INF/applicationContext.xml,
 classpath:WEB-INF/applicationContext-security.xml
 </param-value>
</context-param>

<context-param>
 <param-name>log4jConfigLocation</param-name>
 <param-value>classpath:log4j.properties</param-value>
</context-param>

<!-- 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>

<!-- Spring Configuration -->
<listener>
 <listener-class>
 org.springframework.web.context.ContextLoaderListener
 </listener-class>
</listener>
<listener>
 <listener-class>
 org.springframework.web.context.request.RequestContextListener
 </listener-class>
</listener>

这是applicationContext.xml,引用了customUserDetailsService:


<bean id="propertyConfigurer"
 class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 ...
</bean>

<bean id="dataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource">
 ...
</bean>

<bean id="entityManagerFactory"
 class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
 ...
</bean>

<!-- Authentification -->
<bean id="customUserDetailsService" class="com.seraphim.security.auth.CustomUserDetailsService"/>

<!-- Transaction -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>

<context:component-scan base-package="com.seraphim"/>

<context:annotation-config/>

<tx:annotation-driven transaction-manager="transactionManager"/>

相应身份验证提供程序的applicationContext-security.xml:


<http
 auto-config="true"
 access-denied-page="/accessDenied.jsp">

 <intercept-url
 pattern="/pages/**"
 access="ROLE_ADMIN,ROLE_USER"/>
 <intercept-url
 pattern="/**"
 access="IS_AUTHENTICATED_ANONYMOUSLY"/>

 <form-login
 login-processing-url="/j_spring_security_check"
 login-page="/index.xhtml"
 default-target-url="/pages/main.xhtml"
 authentication-failure-url="/index.xhtml"/>

 <logout
 logout-url="/logout*"
 logout-success-url="/"/>

</http>

<authentication-manager>
 <authentication-provider user-service-ref="customUserDetailsService">
 <password-encoder hash="md5"/>
 </authentication-provider>
</authentication-manager>

在faces-config.xml中,我添加了LoginErrorPhaseListener来检测BadCredentialsException并添加LoginBean:


<lifecycle>
 <phase-listener>com.seraphim.security.auth.LoginErrorPhaseListener</phase-listener>
</lifecycle>

<managed-bean>
 <managed-bean-name>loginBean</managed-bean-name>
 <managed-bean-class>
 com.seraphim.bean.LoginBean
 </managed-bean-class>
 <managed-bean-scope>
 request
 </managed-bean-scope>
</managed-bean>

index.xhtml的username和password 字段被提交到loginBean:


<h:form id="loginForm">

 <h:outputLabel for="j_username" value="User:"/>
 <p:inputText id='j_username' label="user" required="true"/>

 <h:outputLabel for="j_password" value="Password:"/>

 <h:inputSecret id='j_password' label="pass2" required="true"/>

 <h:outputLabel for="_spring_security_remember_me" value="Remember"/>
 <p:selectBooleanCheckbox id='_spring_security_remember_me'/>

 <h:outputLabel/>
 <h:commandButton type="submit" id="login" action="#{loginBean.doLogin}" value="Login"/>

</h:form>

LoginBean.java


@Component
@Scope("request")
public class LoginBean {

 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();

 // It's OK to return null here because Faces is just going to exit.
 return null;

 }
}

现在在CustomUserDetailsService.java中,应该加载正确的用户,但是这里方法参数username总是空的,因此,当然找不到有效的用户:


public class CustomUserDetailsService implements UserDetailsService {

@Resource
IUserDao userDao;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

 User user = userDao.findByUsername(username);

 if (user == null) {
 throw new UsernameNotFoundException("user not found");
 }

 // build roles for user
 Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
 authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
 if(user.isAdmin()) {
 authorities.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
 }

 return new org.springframework.security.core.userdetails.User(
 user.getUsername(),
 user.getPassword(),
 user.isEnabled(), 
 user.isAccountNonExpired(),
 user.isCredentialsNonExpired(), 
 user.isAccountNonLocked(),
 authorities);
}

}

时间:

如果以这种方式将Spring Security与JSF集成,则需要在表单中设置prependId ="false",否则Spring Security所需的字段名将加上表单id,


<h:form id="loginForm" prependId ="false">...</h:form>

...