1
votes

I am trying to use Spring-Security 4.0.0.M1 with Spring-Boot 1.0.2.RELEASE with H2 and Spring-data-jpa.1.5.2. I am able to create a user in the SecurityConfig.configure method, but when I extract this out to its own class, I get a nullpointer exception. I step into JdbcDaoSupport.java and see that the getJdbcTemplate() returns a null jdbcTemplate.

Here is my config:

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

    @Autowired
    UserService userService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                .antMatchers( "/resources/**" ).permitAll()
                .antMatchers( "/css/**" ).permitAll();

        http
                .formLogin().failureUrl( "/login?error" )
                .defaultSuccessUrl( "/" )
                .loginPage( "/login" )
                .permitAll()
                .and()
                .logout().logoutRequestMatcher( new AntPathRequestMatcher( "/logout" ) ).logoutSuccessUrl( "/login" )
                .permitAll();

        http
                .authorizeRequests().anyRequest().authenticated();
    }

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

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

My main configuration class has the datasource defined, which I know to work:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder().setType( EmbeddedDatabaseType.H2 ).setName( "ofac" )
            .addScript( "classpath:h2.sql" ).build();
}

I have a User Service for adding users as such:

@Component
public class UserService {

    @Autowired
    private AuthenticationManagerBuilder authenticationManagerBuilder;

    @Autowired
    private javax.sql.DataSource dataSource;

    @PostConstruct()
    public void initUsers() throws Exception {
        addNewUser( "admin", "password", "ADMIN" );
    }

     public void addNewUser(String username, String plainPassword, String role) {

    List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    authorities.add( new SimpleGrantedAuthority( role ) );
    PasswordEncoder encoder = new BCryptPasswordEncoder();

    UserDetails hashedUser = new User( username, encoder.encode( plainPassword ), authorities );

    JdbcUserDetailsManager userDetailsService = (JdbcUserDetailsManager) authenticationManagerBuilder.getDefaultUserDetailsService();
    try {
        if ( userDetailsService.userExists( hashedUser.getUsername() ) ) {
            userDetailsService.deleteUser( hashedUser.getUsername() );
        }
        // and finally, create the user
        userDetailsService.createUser( hashedUser );
    } catch ( Exception ex ) {
        ex.printStackTrace();
    }
 }
}

I have a breakpoint on the UserService.initUsers and when the line:

authenticationManagerBuilder.jdbcAuthentication().getUserDetailsService().userExists( hashedUser.getUsername() ) 

is invoked, I can see that authenticationManagerBuilder.jdbcAuthentication() returns a null jdbcTemplate. All the online documentation seems to indicate this would work, but it does not seem to wire up everything as I am expecting.

Does anyone know what might be wrong?

UPDATE: I changed the project so I no longer have the SecurityConfig, but instead to have a:

@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers( "/resources/**" ).permitAll()
                .antMatchers( "/css/**" ).permitAll();

        http
                .formLogin().failureUrl( "/login?error" )
                .defaultSuccessUrl( "/" )
                .loginPage( "/login" )
                .permitAll()
                .and()
                .logout().logoutRequestMatcher( new AntPathRequestMatcher( "/logout" ) ).logoutSuccessUrl( "/login" )
                .permitAll();

        http
                .authorizeRequests().anyRequest().authenticated();
    }
}

and a:

@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
public class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {


    @Autowired
    private DataSource dataSource;

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .jdbcAuthentication()
                .dataSource( dataSource );
    }
}

But in my UserService, I get a null userDetailsService here:

JdbcUserDetailsManager userDetailsService = (JdbcUserDetailsManager) authenticationManagerBuilder.getDefaultUserDetailsService();

I have not figured out how to actually create a user after startup. I thought it was with a UserDetailsService, but that doesn't provide the functionality. I thought maybe a JdbcUserDetailsManager is needed, but so far I haven't been able to wire one up that works.

1
I think if you want to share user details service in two places you should define it as a bean. Injecting the auth mgr builder is not intended to work in this way. - Dave Syer

1 Answers

0
votes

The AuthenticationManagerBuilder may not have been fully initialized when it is injected into your object. Spring Security uses various markers to signal the initialization order, e.g. you could try extending GlobalAuthenticationConfigurerAdapter like in the method security sample.