我正在与一个Apple最近在OS X中遇到的“bug”:)一个应用程序验证用户将其密码字段不仅视为bcrypt哈希而且还作为明文,因此它允许特殊的公用事业帐户记录用空密码。
数据库中有许多用户记录,而且几乎所有用户记录都使用bcrypt进行了密码散列。但是有一些特殊的实用程序帐户,密码哈希字段故意留空(使BcryptPasswordEncoder#matches
总是拒绝他们的登录尝试。)
在ProviderManager
遍布断点我可以看到spring初始化的多个身份验证提供程序:
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
也无济于事,在这种情况下,全局管理器包含所有三个提供者。
答案 0 :(得分:1)
配置明确地在多个地方提供userDetailsService而不提供PasswordEncoder
。最简单的解决方案是将UserDetaisService
和PasswordEncoder
公开为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);
}
}
失败的原因是因为有两次使用UserDetailsService的显式配置:
@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);
}
}