1

I'm implementing Spring Security using Java configuration in my Spring Web MVC project, and for some reason the @Autowired annotation is not injecting fields in my security config class. I found this very similar question on SO, but my setup is much simpler and the accepted answer doesn't apply at all in my case.

For reference, I followed the first three chapters of Spring's own security documentation (here) and got in-memory authentication working pretty quickly. I then wanted to switch to JDBC authentication and inject a DataSource with the @Autowired annotation (as shown in this example). However, I get this error:

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 javax.sql.DataSource com.tyedart.web.config.security.SecurityConfig.dataSource; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] 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)}

Here is my security config class. As you can see I'm working around the problem by explicitly looking up my data source:

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//  @Autowired
//  private DataSource dataSource;

//  @Autowired
//  public PasswordEncoder passwordEncoder;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        InitialContext ctx = new InitialContext();
        DataSource dataSource = (DataSource) ctx.lookup("java:/comp/env/jdbc/TyedArtDB");

        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        auth
            .jdbcAuthentication()
                .dataSource(dataSource)
                .passwordEncoder(passwordEncoder);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/manage/**").hasRole("ADMIN")
                .and()  
            .formLogin();
    }
}

And here is my very simple root-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       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
                           http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">

       <!-- Root Context: defines shared resources visible to all other web components -->

       <jee:jndi-lookup id="dataSource" jndi-name="jdbc/TyedArtDB"/>

       <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

</beans>

What am I doing wrong?

Community
  • 1
  • 1
Rob Johansen
  • 5,076
  • 10
  • 40
  • 72

3 Answers3

1

Add <context:component-scan base-package="your.package.where.your.bean.is"/> in root-context.xml

You can uncomment the @Autowired in field declaration and remove it from the constractor. You can find more info here

If you @Autowired a bean, don't forget to remove the initialization using new.

Nabarun
  • 711
  • 1
  • 13
  • 23
  • Unfortunately, adding the context:component-scan element didn't make a difference. – Rob Johansen Feb 19 '14 at 03:41
  • Have you defined the bean dataSource? Please provide how you defined this bean. – Nabarun Feb 19 '14 at 06:30
  • Do I need something more than how I already defined it? This is in my root-context.xml (see my original post): – Rob Johansen Feb 19 '14 at 06:34
  • I missed that. I don't see anything wrong here. I now suspect the container is failing to create the DataSource bean due to one of many possible reasons like DB Id/Password wrong, DB Url wrong, Database down etc. You can try to ensure that dataSource bean is instantiated successfully. – Nabarun Feb 19 '14 at 06:45
  • I know the DB username, password, and URL are correct because the code as shown in my original post actually works. It _only_ stops working if I uncomment the two @Autowired lines and fields, then comment the explicit instantiation/lookup of `InitialContext`, `DataSource`, and `BCryptPasswordEncoder`. As I told user "rhinds" in my comment on his answer, I think Spring is initializing my security config class before my root-context.xml (and I'm not sure how to make that happen in the reverse order). – Rob Johansen Feb 20 '14 at 04:48
  • How do you define `SecurityConfig` bean? Since it has `@Configuration` annotation, I suppose you are doing `component-scan` in a spring config file? The 2nd question is where do you use `SecurityConfig` bean? How do you inject the bean? Please note that mixing Annotation base config and xml based config is not a good idea. If not careful, it can lead to many problems. – Nabarun Feb 20 '14 at 08:22
0

I'm not 100% sure, but it looks like the problem is you are trying to Autowire DataSource, which has not actually been defined as a Spring bean:

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/TyedArtDB"/>

Creates an object that can be referenced in XML, but don't think it actually creates a bean? (could be wrong, don't really do much with JNDI lookups in spring).

Is it possible to switch over to pure java config and drop the XML references?

@Bean
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("jdbc/TyedArtDB");
}

(taken from this answer: https://stackoverflow.com/a/15440797/258813)

And you can move the password encoder like this:

@Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){
    return new BCryptPasswordEncoder();
}

taken from: http://automateddeveloper.blogspot.co.uk/2014/02/spring-4-xml-to-annotation-configuration.html

Community
  • 1
  • 1
rhinds
  • 9,976
  • 13
  • 68
  • 111
  • 1
    Based on the before and after of [this example](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/xsd-config.html#xsd-config-body-schemas-jee-jndi-lookup) in the Spring documentation, my JNDI lookup is creating a Spring bean. In fact I've successfully @Autowired it in other classes – it's _only_ a problem in my security config class. I could just use pure Java config, but then my security config class would be dependent on the `InitialContext` and `BCryptPasswordEncoder` classes. I think Spring is initializing my security config class before my root-context.xml. – Rob Johansen Feb 20 '14 at 03:40
0

Here's an example I used to autowire BCryptPasswordEncoder:

UserServiceImpl.java

@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public User findUserByEmail(String email) {
        return null;
    }
}

WebMvcConfig.java

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }
}

If you notice, WebMvcConfig.java has @Bean annotation just before passwordEncoder() method. It indicates that a method produces a bean to be managed by the Spring container.

https://docs.spring.io/autorepo/docs/spring/4.2.4.RELEASE/javadoc-api/org/springframework/context/annotation/Bean.html

Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70