【问题标题】:Spring Boot initializes more DaoAuthenticationProviders than expectedSpring Boot 初始化了比预期更多的 DaoAuthenticationProvider
【发布时间】:2018-02-15 03:03:08
【问题描述】:

我正在与 Apple 最近在 OS X 中遇到的一个“错误”作斗争 :) 一个应用程序对用户进行身份验证,不仅将其密码字段视为 bcrypt 散列,而且还视为明文,因此它允许特殊实用程序帐户登录密码为空。

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

ProviderManager 各处放置断点我可以看到spring 初始化了多个身份验证提供程序:

  • 带有 bcrypt 编码器的“正确”DaoAuthenticationProvider
  • AnonymousAuthenticationProvider,没有人配置,但至少我可以猜到它来自 permitAll() 或类似的东西。
  • 不受欢迎的 DaoAuthenticationProviderPlaintextPasswordEncoder 破坏了所有乐趣

我们有另一个项目,我们不使用 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);
    }

}

编辑:如果我理解正确,有一种方法可以配置全局和本地 AuthenticationManagerBuilders:

// 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,另一个 - 全局 - 有其他 2 个提供者:匿名和纯文本。身份验证行为仍然存在,应用程序仍然使用 both 并允许用户使用明文密码登录。取消注释 configureGlobal 也无济于事,在这种情况下,全局管理器包含所有三个提供程序。

【问题讨论】:

  • 这是不工作的。我会在一分钟内尝试...
  • 不,不走运。出于某种原因,仍然存在所有 3 个身份验证提供程序。 this.authenticationProviders.add(authenticationProvider); 被多次调用,仍然可以使用明文密码登录。
  • 我现在很困惑。我在AuthenticationManagerBuilder#authenticationProvider 方法中设置了一个断点,我清楚地看到在应用程序启动期间配置了两个AuthenticationManagerBuilders:它们是,例如AuthenticationManagerBuilder@7702 和WebSecurityConfigurerAdapter@7745。第一个单独获得正确的提供者。后一个添加了所有 3 个身份验证提供程序。另外,我已按照您的建议删除了 @Autowired
  • 请不要在标题中添加“[已解决]”或在问题中添加答案。如果现有答案解决了问题,则通过选择答案分数下方的勾号将其标记为“已接受”。如果您的解决方案与现有答案显着不同,请将其发布为您自己的答案并接受。

标签: java authentication spring-boot spring-security


【解决方案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 定义。

【讨论】:

  • 我尝试了您的建议但没有运气(由于我缺乏弹簧知识,我很可能误解了某些东西)。但是,一个小时后我解决了一个问题。原来protected void configure(HttpSecurity http) 触发了第二个AuthenticationManagerBuilder 创建。所以我提供了我的 AuthenticationProvider bean 并将 http.authenticationProvider(authenticationProvider) 添加到 httpsecurity 配置中。现在一切似乎都按预期工作。不过,这可能不是正确的解决方案。
【解决方案2】:

原来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);
    }
}

【讨论】:

    猜你喜欢
    • 2020-10-15
    • 2018-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-10
    • 2017-03-28
    • 2018-03-22
    • 1970-01-01
    相关资源
    最近更新 更多