Spring Boot初始化了比预期更多的DaoAuthenticationProvide

时间:2018-02-13 12:46:30

标签: java authentication spring-boot spring-security

我正在与一个Apple最近在OS X中遇到的“bug”:)一个应用程序验证用户将其密码字段不仅视为bcrypt哈希而且还作为明文,因此它允许特殊的公用事业帐户记录用空密码。

数据库中有许多用户记录,而且几乎所有用户记录都使用bcrypt进行了密码散列。但是有一些特殊的实用程序帐户,密码哈希字段故意留空(使BcryptPasswordEncoder#matches总是拒绝他们的登录尝试。)

ProviderManager遍布断点我可以看到spring初始化的多个身份验证提供程序:

  • 使用bcrypt encoder
  • 的“正确”DaoAuthenticationProvider
  • AnonymousAuthenticationProvider,没人配置,但至少我猜它来自permitAll()或类似的东西。
  • 带有DaoAuthenticationProvider的不受欢迎的PlaintextPasswordEncoder会破坏所有乐趣

我们有另一个项目,我们不使用Spring Boot,并且几乎完全相同的配置,它按预期工作(密码永远不会被视为纯文本,只能作为bcrypt哈希)。所以我的猜测是:这个“问题”与Spring Boot“按惯例配置”有关,我找不到如何覆盖它的行为。

在这个项目中,我使用以下配置:

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    AuthenticationProvider authenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.userDetailsService(userDetailsService)
                .authorizeRequests()
                .antMatchers("/js/**", "/css/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login").permitAll()
                .loginProcessingUrl("/j_spring_security_check").permitAll()
                .successHandler(new SuccessHandler())
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll()
                .logoutSuccessUrl("/login");


        http.csrf().disable();
        http.headers().frameOptions().sameOrigin();
    }

    @Autowired
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
        auth.authenticationProvider(authenticationProvider);
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(15);
    }

}

编辑:如果我弄错了,有办法配置全局和本地AuthenticationManagerBuilder

// Inject and configure global:
/*
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
*/

// Override method and configure the local one:
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

这样做,我现在有两个构建器实例:一个 - 本地 - 只有正确的管理器:bcrypt,另一个 - 全局 - 有另外两个提供者:匿名和明文。身份验证行为仍然存在,应用程序仍然使用两者,并允许用户使用明文密码登录。取消注释configureGlobal也无济于事,在这种情况下,全局管理器包含所有三个提供者。

2 个答案:

答案 0 :(得分:1)

配置明确地在多个地方提供userDetailsS​​ervice而不提供PasswordEncoder。最简单的解决方案是将UserDetaisServicePasswordEncoder公开为Bean并删除所有显式配置。这是有效的,因为如果没有显式配置,Spring Security将发现Bean并从中创建身份验证。

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http // Don't forget to remove userDetailsService
                .authorizeRequests()
                .antMatchers("/js/**", "/css/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login").permitAll()
                .loginProcessingUrl("/j_spring_security_check").permitAll()
                .successHandler(new SuccessHandler())
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll()
                .logoutSuccessUrl("/login");


        http.csrf().disable();
        http.headers().frameOptions().sameOrigin();
    }
    // UserDetailsService appears to be a Bean somewhere else, but make sure you have one defined as a Bean
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(15);
    }

}

失败的原因是因为有两次使用UserDetailsS​​ervice的显式配置:

@Autowired
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    // below Configures UserDetailsService with no PasswordEncoder
    auth.userDetailsService(userDetailsService); 
    // configures the same UserDetailsService (it was used to create the authenticationProvider) with a PasswordEncoder (it was provided to the authenticationProvider)
    auth.authenticationProvider(authenticationProvider);
}

如果您想要显式配置,可以使用以下

@Override
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder()); 
}

并删除authenticationProvider bean以及@Autowired AuthenticationProvider。或者,您可以使用AuthenticationProvider,但不能同时使用两者。

通常,只有当多个WebSecurityConfigurerAdapter具有不同的身份验证机制时,才需要显式配置AuthenticationManagerBuilder。如果您不需要这个,我建议只将UserDetailsService和(可选)PasswordEncoder作为Bean公开。

请注意,如果您将AuthenticationProvider公开为Bean,则会在UserDetailsService上使用它。同样,如果您将AuthenticationManager公开为Bean,则会使用AuthenticationProvider。最后,如果您明确提供AuthenticationManagerBuilder配置,则它将用于任何Bean定义。

答案 1 :(得分:0)

事实证明,protected void configure(HttpSecurity http)触发了第二次AuthenticationManagerBuilder创作。所以我提供了我的AuthenticationProvider bean并将其添加到httpsecurity配置中。现在一切似乎都按预期工作了。但它可能不是正确的解决方案。 新配置(适合我):

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    AuthenticationProvider authenticationProvider;
    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authenticationProvider(authenticationProvider) // <== Important
                //.anonymous().disable() // <== This part is OK. If enabled, adds an anonymousprovider; if disabled, it is impossible to login due to "unauthenticated<->authenticate" endless loop.
                .httpBasic().disable()
                .rememberMe().disable()
                .authorizeRequests()
                .antMatchers("/js/**", "/css/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login").permitAll()
                .loginProcessingUrl("/j_spring_security_check").permitAll()
                .successHandler(new SuccessHandler())
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll()
                .logoutSuccessUrl("/login");    
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(15);
    }
}