Spring Security UserDetailsService Not Validating Password

springspring-security

I'm implementing spring security 3.0.5 and in my form based login im extending the spring UserDetailsService. Currently my login form is only validating user name and not password. Where does spring security validate the password being posted to /j_spring_security_check?

security config:

<?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:util="http://www.springframework.org/schema/util"
    xmlns:context="http://www.springframework.org/schema/context"
    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-3.0.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util-3.0.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:annotation-config />
    <context:component-scan base-package="dc" />
    <global-method-security />
    <http access-denied-page="/auth/denied.html">
         <intercept-url filters="none" pattern="/javax.faces.resource/**" />
         <intercept-url filters="none" pattern="/services/rest-api/1.0/**" />
         <intercept-url filters="none" pattern="/preregistered/*"/>
         <intercept-url
            pattern="/**/*.xhtml"
            access="ROLE_NONE_GETS_ACCESS" />
        <intercept-url
            pattern="/auth/**"
            access="ROLE_ANONYMOUS,ROLE_USER" />
         <intercept-url
            pattern="/auth/*"
            access="ROLE_ANONYMOUS" />
         <intercept-url
            pattern="/registered/*"
            access="ROLE_USER" />
          <intercept-url
            pattern="/*"
           access="ROLE_ANONYMOUS" />
        <form-login
            login-processing-url="/j_spring_security_check.html"
            login-page="/auth/login.html"
            default-target-url="/home.html"
            authentication-failure-url="/login.html" />
         <logout invalidate-session="true" 
              logout-url="logout.html" 
              success-handler-ref="SuccessHandler"/>
        <anonymous username="guest" granted-authority="ROLE_ANONYMOUS"/>
        <remember-me user-service-ref="userManager" key="dfdfdfdff"/>
        <custom-filter after="FORM_LOGIN_FILTER" ref="xmlAuthenticationFilter"/>
    </http>
    <!-- Configure the authentication provider -->
    <authentication-manager alias="am">
        <authentication-provider user-service-ref="userManager">
                <password-encoder ref="passwordEncoder" />
        </authentication-provider>
        <authentication-provider ref="xmlAuthenticationProvider" />
    </authentication-manager>
</beans:beans>

beans:

<?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:util="http://www.springframework.org/schema/util"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="com.dc"/>
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
    <bean id="springContextHolder" class="SpringContextHolder" factory-method="getInstance" />
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter">
            <bean
                class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="database" value="MYSQL" />
                <property name="showSql" value="true" />
            </bean>
        </property>
    </bean>
     <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/webapp" />
        <property name="username" value="userid" />
        <property name="password" value="password" />
    </bean>       
    <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false"/>
    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="localhost"/>
        <property name="port" value="25"/>
    </bean>
    <bean id="utilities" class="UtilitiesImpl"/>
    <bean id="xmlAuthenticationFilter" class="com.dc.api.service.impl.XMLAuthenticationFilter">
        <property name="authenticationManager" ref="am" />
        <property name="utilities" ref="utilities"/>
    </bean>
    <bean id="xmlAuthenticationProvider" class="com.dc.api.service.impl.XMLAuthenticationProvider">
        <property name="userManager" ref="userManager"/>
    </bean>
    <bean id="DCLogoutSuccessHandler" class="LogoutSuccessHandler"/>
 </beans>

UserDetails Implementation:

import javax.inject.Inject;

import org.springframework.dao.DataAccessException;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.dc.api.dao.AuthorityDAO;
import com.dc.api.dao.UserDAO;
import com.dc.api.exception.ApiDataException;
import com.dc.api.exception.EmailNotFoundException;
import com.dc.api.helper.MailContentHelper;
import com.dc.api.model.Users;
import com.dc.api.model.vo.APIResponse;
import com.dc.api.service.UserManager;
import com.dc.api.service.Utilities;

@Service("userManager")
public class UserManagerImpl extends UserDetailsService {
    @Inject
    UserDAO userDAO;
    @Inject
    AuthorityDAO authorityDAO;
    @Inject
    PasswordEncoder passwordEncoder;
    @Inject
    Utilities utilities;

    private void encodePassword(Users user) {
        if (user.getPassword() == null && user.getRawPassword() != null) {
            user.setPassword(passwordEncoder.encodePassword(user.getRawPassword(), null));
            user.setRawPassword(null);
        }
    }

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        Users user = null;
        try {
            user = userDAO.findByUsername(username);
            if (user != null) {

            }
        } catch (DataAccessException ex) {
            throw new UsernameNotFoundException("Invalid login", ex);
        }
        if (user == null) {
            throw new UsernameNotFoundException("User not found.");
        }
        return user;
    }

    public Users getUser(String username) {
        try {
            return userDAO.findByUsername(username);
        } catch (DataAccessException ex) {
            // ignore
            log.warn("Duplicate username: " + username);
        }
        return null;
    }

    public boolean isUsernameTaken(String username) {
        try {
            if (userDAO.findByUsername(username) == null) {
                return false;
            } else {
                return true;
            }
        } catch (DataAccessException ex) {
            // ignore
            log.warn("Duplicate username: " + username);
        }
        return true;
    }

    public boolean isLoginValid(String username, String password) throws ApiDataException {
        Users user = null;
        try {
            user = userDAO.findByUsername(username);
        } catch (DataAccessException ex) {
            throw new ApiDataException("Data Access Exception while verifying login");
        }
        if (user == null) {
            return false;
        }
        if (passwordEncoder.isPasswordValid(user.getPassword(), password, null)) {
            return true;
        }
        return false;
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
    public void saveUser(Users user) {
        encodePassword(user);
        userDAO.save(user);
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
    public void updateUser(Users user) {
        encodePassword(user);
        userDAO.update(user);
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
    public void resetPassword(String username, MailContentHelper mailContentHelper) {
        String newPassword = utilities.generateSecret(8);
        this.changePassword(username, newPassword, mailContentHelper);
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
    public void changePassword(String username, String password, MailContentHelper mailContentHelper) {
        Users user = userDAO.findByUsername(username);
        user.setPassword(null);
        user.setRawPassword(password);
        encodePassword(user);
        userDAO.update(user);
        String firstName = user.getFirstName();
        firstName = (firstName == null) ? user.getUsername() : firstName;
        //SimpleMailMessage message = mailContentHelper.retrieveContent(new Object[]{firstName, password, user.getEmail()});
        //utilities.sendMail(message);
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <context-param>
    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
    <param-value>/WEB-INF/facelet/dc.taglib.xml</param-value>
  </context-param>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        WEB-INF/dc-context-api.xml
        WEB-INF/dc-context-security.xml
    </param-value>
  </context-param>
  <context-param>
    <param-name>resteasy.resource.method-interceptors</param-name>
    <param-value>org.jboss.resteasy.core.ResourceMethodSecurityInterceptor</param-value>
  </context-param>
  <context-param>
    <param-name>resteasy.resources</param-name>
    <param-value>
         com.dc.web.actions.GlobalWebService</param-value>
  </context-param>
  <context-param>
    <param-name>resteasy.servlet.mapping.prefix</param-name>
    <param-value>/services/rest-api</param-value>
  </context-param>
  <context-param>
    <param-name>resteasy.media.type.mappings</param-name>
    <param-value>json : application/json, xml : application/xml</param-value>
  </context-param>
  <context-param>
    <param-name>resteasy.resources</param-name>
    <param-value>
            com.WebService
        </param-value>
  </context-param>
  <context-param>
    <param-name>primefaces.THEME</param-name>
    <param-value>none</param-value>
  </context-param>
  <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>/WEB-INF/log4j.properties</param-value>
    </context-param>
     <context-param>
        <param-name>log4jRefreshInterval</param-name>
        <param-value>1000</param-value>
    </context-param>
    <context-param> 
        <param-name>primefaces.PRIVATE_CAPTCHA_KEY</param-name> 
        <param-value>6LeL-MISAAAAAG6k07ch22oy-mxXBUi1MXKmrWiD</param-value>
    </context-param>
    <context-param> 
        <param-name>primefaces.PUBLIC_CAPTCHA_KEY</param-name> 
        <param-value>6LeL-MISAAAAAPTK5lYI9tK0SWWY2BqC2Hun7sH3</param-value>
    </context-param>
  <filter> 
    <filter-name>PrimeFaces FileUpload Filter</filter-name> 
        <filter-class>org.primefaces.webapp.filter.FileUploadFilter </filter-class>
        <init-param> 
            <param-name>thresholdSize</param-name> <param-value>51200</param-value>
        </init-param> 
        <init-param>
            <param-name>uploadDirectory</param-name>
            <param-value>url/upload</param-value> 
        </init-param>
    </filter>
  <filter-mapping>
    <filter-name>PrimeFaces FileUpload Filter</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
  </filter-mapping>
  <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>*.html</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
  </filter-mapping>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>*.xhtml</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
  </filter-mapping>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/dc_security_check</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
  </filter-mapping>
  <listener>
    <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
  </listener>
  <listener>
    <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
  </listener>
  <listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
  <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
  <servlet>
    <servlet-name>Resteasy</servlet-name>
    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
  </servlet>
  <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>Resteasy</servlet-name>
    <url-pattern>api/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>30</session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
  <login-config>
    <auth-method>BASIC</auth-method>
  </login-config>
</web-app>

Best Answer

It compares the password you submit to the password returned by the UserDetails object returned by your UserDetailsService. Please post your config and your UserDetailsService if you need more help.

EDIT: Thanks for the info. It does exactly what you're guessing. The ProviderManager (which is used by default) has the following in its JavaDoc:

If a subsequent provider successfully authenticates the request, the earlier authentication exception is disregarded and the successful authentication will be used.

So your problem is the latter provider "overruling" the decision of the first one.

Related Topic