Skip to content
This repository has been archived by the owner on Apr 27, 2021. It is now read-only.

"No matching bean ... found" while initializing custom UserDetailsService and AuthenticationProvider #50

Closed
tswcode opened this issue Apr 10, 2013 · 6 comments

Comments

@tswcode
Copy link

tswcode commented Apr 10, 2013

In my business application I am using
the following application stack:

  • MySQL <> Hibernate <> Spring Data JPA (using JpaRepository interfaces)
  • Spring Framework (3.2.2) <> Apache Tiles + Thymeleaf

Before trying the new javaconfig implementations,
I have a working business application (as follows) using spring-security.xml.

Furthermore based on the entities / models from database (User, Role, Auth)
and repositories (JpaRepository from spring-data-jpa) only as interfaces,
I have implemented a custom UserDetailsService and AuthenticationProvider,
in which the repositories of the User model is getting @Inject-ed (and some more).

Thus for configurating these, I provided them as beans:

@Bean
public AuthenticationProvider usernamePasswordAuthenticationProvider() {
    UsernamePasswordAuthenticationProvider authProv =  new UsernamePasswordAuthenticationProvider();
    return authProv;
}

@Bean(name = "userDetailsService")
public UserDetailsService passwordUserDetailsService() {
    return new PasswordUserDetailsService();
}

Like in some of your examples, I configured these in a similar way:

protected AuthenticationManager authenticationManager(
        AuthenticationBuilder authenticationRegistry) throws Exception {
    return authenticationRegistry
            .add(usernamePasswordAuthenticationProvider())
            .userDetails(passwordUserDetailsService()).and()
            .build();
}

So far so good, I think the other config is not important at the moment. When I try to start my tomcat server (in eclipse), I noticed in the logs that spring-data-jpa created the required beans, e.g.:

DefaultListableBeanFactory  - Overriding bean definition for bean 'userRepository': replacing [Root bean: class [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean]; ... 

The next initialized beans are about my beans from java configuration (mvc, view, persistence), e.g.:

AnnotationConfigWebApplicationContext  - Bean 'dataSourceConfig' of type [class ...]

After the latter initialization usually the xml from spring security is read and beans are created. In case of the java config of spring security (the test problem now), it is complaining that it cannot inject the upper mentioned userRepository, though working in xml mode. The complete exception chain is listed here:
http://pastebin.com/KK9wXR46

Do you have any idea what the problem could be ?
I thought, maybe your java config is intitializing too early,
that my userRepository bean could not be found (though working in xml mode).

@rwinch
Copy link
Contributor

rwinch commented Apr 10, 2013

I'm guessing you have a circular dependency which can be handled by XML configuration, but not by the Java Configuration. Can you post your entire configuration? I have added a test that demonstrates Spring Data usage for an AuthenticationProvider in commit f52d3ef

@tswcode
Copy link
Author

tswcode commented Apr 10, 2013

Ok I'll post some more details concerning this security configuration, also because some of the following methods and classes have roots in some websites (or at least similar to them).

My business application has the demand to have the current User model object contained in a custom UserDetails class, which has no further autowire annotations. This class, called PasswordUserDetails (which also maps the database roles to security roles), is needed by the custom AuthenticationProvider class as followed:

@Service
public class UsernamePasswordAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException { }

    @Override
    protected UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        PasswordUserDetails dbUser = (PasswordUserDetails) userDetailsService.loadUserByUsername(username);
        if(dbUser == null) {
            throw new UsernameNotFoundException("User " + username + " not found!");
        }
        return dbUser;
    }
}

Next class beeing need for this demand, is the UserDetailsService implementation:

@Service
@Transactional(readOnly = true)
public class PasswordUserDetailsService implements UserDetailsService {

    @Inject
    private UserRepository userRepository;

    @Autowired
    private LogEventService logEventService;

    @Autowired
    private MessageSource messageSource;

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private LocaleResolver localeResolver;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        User user = null;
        PasswordUserDetails userDetails = null;

        user = userRepository.findByName(username);
        if(user != null) {
            userDetails = new PasswordUserDetails(
                    user,
                    logEventService.isLatestEvent(user, 
                            LogEventType.UNBLOCKED) > 0,
                    user.getDefaultAuthentication().isEnabled());
        } else {
            throw new UsernameNotFoundException(
                    getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials",
                               "Bad credentials"));
        }
        return userDetails;
    }

    public String getMessage(String code, String defaultMessage) {
        Locale requestLocale = localeResolver.resolveLocale(request);
        return messageSource.getMessage(code, null, defaultMessage, requestLocale);
    }

}

Concerning my SecurityConfig class, it is similar to your class mentioned in your commit,
but also includes the following class level annotation:

@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)

Which is needed to get the current User model object from the PasswordUserDetails of the security context as follows:

public static PasswordUserDetails getCurrentPasswordUserDetails() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    org.springframework.security.core.Authentication securityAuthentication = securityContext.getAuthentication();
    Object securityPrinzipal = securityAuthentication.getPrincipal();
    PasswordUserDetails passwdUserDetails = null;
    if(securityPrinzipal instanceof PasswordUserDetails) {
        passwdUserDetails = (PasswordUserDetails) securityPrinzipal;
    }

    return passwdUserDetails;
}

If I disable the upper annotation, the exceptions on startup do not happen.
That means, this resolves a closer localization of the cause:

  • either my kind of circular dependency
  • or the enabling of global method security

@rwinch
Copy link
Contributor

rwinch commented Apr 10, 2013

Thanks for the response. I can't be certain without your complete configuration, but I believe I found the issue. I have pushed out a fix as part of 36f51bc. Please ensure you update to the latest snapshot to try the changes I made and let me know if this resolves the issue you were having.

rwinch added a commit that referenced this issue Apr 10, 2013
@tswcode
Copy link
Author

tswcode commented Apr 10, 2013

Ok, last action for me today. I enforced that maven updated to the latest snapshot and ran my webapp.
So far neither errors nor warnings.

But when I try to log in, an exception is raised due to a not registered mapping:

2013-04-10 22:32:56,120 WARN  [http-nio-8080-exec-4] PageNotFound  - No mapping found for HTTP request with URI [/pitsas/j_spring_security_check] in DispatcherServlet with name 'dispatcher'

My first thought about this was to just cut out this "issue" part of code into a "tiny" example project,
which I can (and am allowed to - concerning my company) push to my github repository.
Including an example embedded database, which could get the "all config in one big test case" project.

I will do this as soon as possible, I hope to have some time at the coming weekend, if not before.

@rwinch
Copy link
Contributor

rwinch commented Apr 10, 2013

I think this is a different issue (and not with the Spring Security Java Config). Java Configuration defaults to use /login instead of /j_spring_security_check. From the README

Notice that Spring Security uses different defaults that will make your HTTP requests appear more RESTful. For example, the URL /login POST is used to authenticate users. The URL /login GET is used to request the user for credentials (i.e. present a login form).

These defaults also follow best practices of not having information leakage. Specifically the old defaults j_spring_security_check it was quite obvious that Spring Security was used. If an attacker observed this and Spring Security had a bug in it, the attacker would have an easier time identifying how to attack your application.

If you want to use the old conventions, you can do something like is done in the NamespaceHttpFormLoginTests. Of course you would use the values that align with the old defaults that looks something like:

http
    .formLogin()
        .usernameParameter("j_username")
        .passwordParameter("j_password")
        .loginPage("/authentication/login")
        .failureUrl("/authentication/login?failed")
        .loginProcessingUrl("/j_spring_security_check")
        .defaultSuccessUrl("/default")

@tswcode
Copy link
Author

tswcode commented Apr 11, 2013

Indeed it is written in the README file. I like the approach to make spring security more secure. 👍
I am happy to close this issue so fast. It is really solved: my business application works (nearly)
the same as having used xml configuration. (I don't really like xml configs)

Concerning the "nearly", I'm preparing the next issue concerning a custom remember-me service.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests

2 participants