我在使用SpringBoot和SpringBoot-Security实现自定义登录身份验证时遇到此问题。我为这个帖子做了Bitbucket repository作为参考(在CustomSecuringWeb branch内)。除此之外,这里的大部分评论都遵循Securing a Web Application教程。
问题是,我很好奇,现在验证数据是如何从数据库而不仅仅是内存数据(这在生产线应用程序中非常常见)。
在整个过程中,我做了两次尝试(虽然两次尝试都位于同一个分支上 - 我对此不好)。
UserDetailsService
实施AbstractUserDetailsAuthentictionProvider
实施我不知道问题出在哪里,但在检查控制台日志时,两者都返回每个自定义类的持久性(甚至存储库)DI为空。
问题是我怎样才能使这两种尝试都起作用。并且(可能)两次尝试中的哪一次比另一次更好。
答案 0 :(得分:11)
首先,这两种方法用于不同目的,不可互换。
案例1:
UserDetailsService
纯粹用作DAO,通过您的身份验证来定位用户信息,并根据该信息身份验证用户,不应在UserDetailsService
内进行身份验证,只需进行数据访问。
规格明确提到。这就是你要找的东西。
情况2:
另一方面, AuthentictionProvider
用于提供自定义身份验证方法,例如,如果您要在登录名和密码以外的字段上进行自定义身份验证,则可以通过实施AuthentictionProvider
并提供此信息来实现此目的反对你的AuthenticationManagerBuilder
。我不认为这是你想要在你的项目中做的事情。您只是希望使用登录名和密码来实现基于存储在数据库中的用户的身份验证,这是默认方式。
在上面案例1 中,您只实施了UserDetailsService
,AuthentictionProvider
的实例是由容器在AuthenticationManager
创建的,它是DaoAuthenticationProvider
,因为你提供了UserDetailsService,它只是系统中用于检索用户进行身份验证的DAO。
现在你的实施, 在您的配置中而不是:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(new AdminSecurityService());
auth.authenticationProvider(new AdminSecurityAuthenticationProvider());
}
做这样的事情
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
并且您的CustomUserDetailsService
必须实施org.springframework.security.core.userdetails.UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final AdminRepository userRepository;
@Autowired
public CustomUserDetailsService(AdminRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Admin user = userRepository.findByLogin(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
}
return new UserRepositoryUserDetails(user);
}
private final static class UserRepositoryUserDetails extends Admin implements UserDetails {
private static final long serialVersionUID = 1L;
private UserRepositoryUserDetails(User user) {
super(user);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.createAuthorityList("ROLE_USER");
}
@Override
public String getUsername() {
return getLogin();//inherited from user
}
@Override
public boolean isAccountNonExpired() {
return true;//not for production just to show concept
}
@Override
public boolean isAccountNonLocked() {
return true;//not for production just to show concept
}
@Override
public boolean isCredentialsNonExpired() {
return true;//not for production just to show concept
}
@Override
public boolean isEnabled() {
return true;//not for production just to show concept
}
//getPassword() is already implemented in User.class
}
}
当然实现取决于您,但您必须能够根据检索到的用户(在您的案例中为Admin.class)提供用户密码和该接口中的其余方法。希望能帮助到你。我没有运行这个例子,所以如果我做了一些拼写错误继续询问是否有什么不起作用。我也会摆脱那种认证提供者&#39;如果您不需要它,请从您的项目中获取。
评论后:
您可以在配置方法中设置PasswordEncoder
,而不必过多麻烦:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
您可以这样做是因为您可以访问从AbstractDaoAuthenticationConfigurer
返回的auth.userDetailsService(userDetailsService)
,并且您可以配置DaoAuthenticationProvider
,当您选择使用{{1}时,它是您选择的提供商}}。
你是对的UserDetailsService
是PasswordEncoder
设置的,但你不必这样做
实现AuthenticationProvider
只需使用从AuthenticationProvider
返回的convineince对象,并在该对象上设置编码器,将其传递给已为您创建的案例auth.userDetailsService(userDetailsService)
中的AuthenticationPriovider
。
就像评论中提到的走鹃一样,您很少需要实现自己的DaoAuthenticationProvider
,通常大多数身份验证配置调整都可以通过使用AuthenticationProvider
完成,如上所述从AbstractDaoAuthenticationConfigurer
返回
&#34;如果我想添加密码加密。如果我想进行其他身份验证(例如检查用户是否已锁定,活动,用户是否仍然登录等[不包括密码哈希])将使用AuthenticationProvider。&#34;
作为标准身份验证机制的一部分,不会为您完成此操作
http://docs.spring.io/autorepo/docs/spring-security/3.2.0.RELEASE/apidocs/org/springframework/security/core/userdetails/UserDetails.html
如果查看接口auth.userDetailsService(userDetailsService)
,您将看到如果上述任何方法返回false,则认证将失败。
非常非标准的情况下确实需要实施UserDetails
。框架几乎涵盖了所有标准内容。
答案 1 :(得分:1)
基本上,在JDBC方式http://www.mkyong.com/spring-security/spring-security-form-login-using-database/中,您必须指定查询以检索用户。
答案 2 :(得分:1)
首先,我建议您阅读String Security Core Services。
在这种情况下,关键的一个是AuthenticationManager
,它负责决定用户是否经过身份验证。这是您使用AuthenticationManagerBuilder
配置的内容。它在Spring中的主要实现是ProviderManager
,它允许在单个应用程序中定义多个身份验证机制。最常见的用例是有一个,但它仍然由这个类处理。这些多种身份验证机制中的每一种都由不同的AuthenticationProvider
表示。 ProviderManager
会获取AunthenticationProviders
的列表并对其进行迭代,以查看是否有任何人可以对用户进行身份验证。
您感兴趣的是DaoAuthenticationProvider
。顾名思义,它允许使用数据访问对象来验证用户。它为这样的DAO使用标准接口 - UserDetailsService
。 Spring Security中有一个默认实现,但通常这是您想要自己实现的。所有其余的都提供了。
此外,您需要的配置位完全独立于Spring Boot。这就是你用XML做的方式:
<sec:authentication-manager >
<sec:authentication-provider user-service-ref="myUserDetailsService" />
</sec:authentication-manager>
在Java中它将是:
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService myUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
}
根据UserDetails
实施,通常the one provided by Spring Security就足够了。但如果需要,你也可以实现自己的。
通常您还需要PasswordEncoder
。一个好的,如BCryptPasswordEncoder
:
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
请注意,它是@Bean
,因此您可以在@Autowire
中UserRepository
对用户密码进行编码,然后将其保存在数据库中。