Aeseir Aeseir - 13 days ago 9
Java Question

Could not autowire field: private org.springframework.security.core.userdetails.UserDetailsService

I'm new to Spring so I been mucking around with security side of things.
Everytime I run my application I get:


org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityConfig': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.security.core.userdetails.UserDetailsService com.entirety.app.config.SecurityConfig.userDetailsServiceImplementation; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.core.userdetails.UserDetailsService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}


I've gone through my code with a fine comb and can't pinpoint the issue.

Spring Framework Version: 3.2.5.RELEASE
Spring Security Version: 3.2.0.M2

SecurityConfig.java

package com.entirety.app.config;

import com.entirety.app.service.implement.UserDetailsServiceImplementation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;

@Autowired
private UserDetailsService userDetailsServiceImplementation;

@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(userDetailsServiceImplementation)
.authorizeUrls()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/sec/**").hasRole("MODERATOR")
.antMatchers("/*").permitAll()
.anyRequest().anonymous().and().exceptionHandling().accessDeniedPage("/denied").and()
.formLogin()
.loginProcessingUrl("/j_spring_security_check")
.loginPage("/login")
.failureUrl("/error-login")
.and()
.logout()
.logoutUrl("/j_spring_security_logout")
.logoutSuccessUrl("/");
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}


CrmUserService.java

@Service("userService")
@Transactional
public class CrmUserService implements UserDetailsService {
@Autowired
private UserDAO userDAO;

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

com.entirety.app.domain.User domainUser = userDAO.getUser(login);

boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;

return new User(
domainUser.getLogin(),
domainUser.getPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(domainUser.getAuthority().getId())
);
}

public Collection<? extends GrantedAuthority> getAuthorities(Integer role) {
List<GrantedAuthority> authList = getGrantedAuthorities(getRoles(role));
return authList;
}

public List<String> getRoles(Integer role) {

List<String> roles = new ArrayList<String>();

if (role.intValue() == 1) {
roles.add("ROLE_MODERATOR");
roles.add("ROLE_ADMIN");
} else if (role.intValue() == 2) {
roles.add("ROLE_MODERATOR");
}
return roles;
}

public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}


There is no logical reason why it should be failing yet it does.

NOTE:

My IDE can link the autowire (that is it knows where its being autowired from and everything) yet compiling it fails.

EDIT 2:
I've removed the following code from SecurityConfig.java file

@Autowired
private UserDataService userDataService;


And added it to one of the POJOs and Controllers I know are called on regular basis and contain other services.
In this case pasted that code into my baseController.java file which directs to rendering the homepage.
The service is compile properly and i can even call it within the controller.

So the problem is isolated only to the config file such as SecurityConfig.java, thats the only time it doesn't work.

EDIT 3: Added extra files

SecurityInitializer.java

@Order(2)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}


WebInitializer.java

@Order(1)
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { PersistanceConfig.class, SecurityConfig.class }; //To change body of implemented methods use File | Settings | File Templates.
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}

@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}

// @Override
// protected Filter[] getServletFilters() {
// CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
// characterEncodingFilter.setEncoding("UTF-8");
// return new Filter[] { characterEncodingFilter};
// }
}


mvc-dispatcher-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

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

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>

</beans>


web.xml

<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<display-name>Spring MVC Application</display-name>

<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

Answer

Update (after providing complete example)

It seems you have multiple issues.

web.xml vs AbstractAnnotationConfigDispatcherServletInitializer

The application you created had a web.xml that configures a DispatcherServlet named mvc-dispatcher. The mvc-dispatcher had a configuration of mvc-dispatcher-servlet.xml which loaded all beans within the package com.springapp.sectest. This means that mvc-dispatcher can find your UserDetailsService.

The application also had a AbstractAnnotationConfigDispatcherServletInitializer which created a DispatcherServlet that loaded your WebConfig java configuration. This DispatcherServlet could not see any Beans created by mvc-dispatcher.

In short, you should either configure a DispatcherServlet using web.xml or AbstractAnnotationConfigDispatcherServletInitializer and NOT both.

getRootConfigClasses vs getServletConfigClasses

Any configuration within getRootConfigClasses is typically called the root or parent configuration and cannot view beans defined in getServletConfigClasses. Spring Security protects your application defined in the getRootConfigClasses with a Filter named springSecurityFilterChain that is created by the @EnableWebSecurity annotation. This means it is generally best to place Spring Security's configuration in the getRootConfigClasses. There are some exceptions to this, like if you want to do method security on your Spring MVC Controllers.

Any configuration within getServletConfigClasses is typically called the child configuration and can view beans defined in the getRootConfigClasses. The getServletConfigClasses configures the DispatcherServlet and must contain beans for Spring MVC (i.e. Controllers, ViewResovlers, etc). Any Spring MVC beans defined in getRootConfigClasses are visible but not used.

This setup, while a bit confusing, allows you to have multiple DispatcherServlet instances with isolated configurations. In practice, it is VERY rare to have multiple DispatcherServlet instances though.

Given the information above, you should move the UserDetailsService declaration along with all of its dependent beans to the getRootConfigClasses. This will ensure the SecurityConfig.java can find the UserDetailsService.

Pull Request for fixes

I have submitted a PR to get your application so that it starts with no errors https://github.com/worldcombined/AnnotateFailed/pull/1 . A few notes

  • the test does not use a parent and child context so it is not reflecting running the application
  • I had to move the resources folder so that was picked up properly
  • I did not make it so you can actually authenticate. There was no login.jsp and the UserDetailsService you provided always returns null Instead I attempted to demonstrate that the error on here was answered.

Original Answer

Based upon this comment:

@ComponentScan is undertaken in my mvc-dispatcher-servlet.xml as <context:component-scan base-package="com.entirety.app"/>

It appears that you are configuring the services within the Dispatcher configuration and not within the root context.

Spring Security's configuration is typically configured in the root context. For example, one might extend the AbstractSecurityWebApplicationInitializer as shown below:

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
      extends AbstractSecurityWebApplicationInitializer {
}

How are you importing the Security configuration? If the Spring Security configuration is in the root context then it will not see beans defined in the Dispatcher Servlet context.

The solution is to move the configuring of your services in the root context or move the Spring Security configuration to the dispatcher's ApplicationContext. For example, moving Spring Security configuration to the dispatcher context would look like the following if your dispatcher servlet was named mvc.

public class SecurityWebApplicationInitializer
      extends AbstractSecurityWebApplicationInitializer {
    protected String getDispatcherWebApplicationContextSuffix() {
        return "mvc";
    }
}