自定义 WebSecurityConfigurerAdapter

Custom WebSecurityConfigurerAdapter

我在使用 SpringBoot 和 SpringBoot-Security 实现自定义登录身份验证时遇到了这个问题。我制作了一个 Bitbucket repository as reference for this thread (within CustomSecuringWeb branch). Before anything else, most of the comments here follows the Securing a Web Application 教程。

问题是,我很好奇身份验证数据现在怎么可能来自数据库,而不仅仅是内存数据(这在生产线应用程序中很常见)。

在整个过程中我进行了两次尝试(尽管两次尝试都位于同一个分支上 - 我不喜欢)。

  1. 创建了自定义 UserDetailsService 实现
  2. 创建了自定义 AbstractUserDetailsAuthentictionProvider 实现

我不知道问题出在哪里,但是在检查控制台日志时,returns 每个自定义 class 上的持久性(甚至存储库)DI 为 null。

问题是我怎样才能让这两种尝试都奏效。并且(可能)这两种尝试中哪一种比另一种更好。

基本上,在 JDBC 方式中 http://www.mkyong.com/spring-security/spring-security-form-login-using-database/,您必须指定检索用户的查询。

首先,这两种方法用于不同的目的,不能互换。

案例 1:

UserDetailsService纯粹用作DAO,通过您的身份验证定位用户信息,并根据该信息对用户进行身份验证,UserDetailsService内不应进行任何身份验证,只是数据访问。 规格清楚地提到了这一点。这就是您要找的。

案例2:

另一方面,

AuthentictionProvider 用于提供自定义身份验证方法,例如,如果您想对登录名和密码以外的字段进行自定义身份验证,您可以通过实施 AuthentictionProvider 和将此对象提供给您的 AuthenticationManagerBuilder。我不认为这是你想在你的项目中做的。您只是希望使用默认方式使用登录名和密码基于存储在数据库中的用户来实施身份验证。 在上面的 案例 1 中,你只实现了 UserDetailsService,容器在 AuthenticationManager 中为你创建了 AuthentictionProvider 的实例,它是 DaoAuthenticationProvider 因为您提供了 UserDetailsS​​ervice,它只是您系统中用于检索用户进行身份验证的 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 在您的情况下)。希望能帮助到你。我没有 运行 这个例子,所以如果我犯了一些拼写错误,请继续询问是否有问题。如果您不需要,我也会从您的项目中删除 'AuthentictionProvider' 。

这里有文档:http://docs.spring.io/spring-security/site/docs/4.0.0.RC1/reference/htmlsingle/#tech-userdetailsservice

评论后: 您可以在您的配置方法中设置 PasswordEncoder 而无需太多麻烦,只需执行以下操作:

 @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);

    }
@Bean
    public PasswordEncoder passwordEncoder(){
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder;
    }

您可以这样做,因为您可以访问从 auth.userDetailsService(userDetailsService) 返回的 AbstractDaoAuthenticationConfigurer 并且它允许您配置 DaoAuthenticationProvider,当您选择使用 UserDetailsService。 你是对的 PasswordEncoder 设置在 AuthenticationProvider 中,但你不必 实施 AuthenticationProvider 只需使用从 auth.userDetailsService(userDetailsService) 返回的 convineince 对象并将编码器设置在该对象上,该对象将把它传递给 AuthenticationPriovider 在您的情况下 DaoAuthenticationProvider 已经为您创建. 就像评论中提到的 road运行ner 你很少需要实现自己的 AuthenticationProvider 通常大多数身份验证配置调整都可以使用 AbstractDaoAuthenticationConfigurer 来完成,如上所述返回来自 auth.userDetailsService(userDetailsService).

"And if I ever wanted to add a password encryption. And if I ever wanted to do other authentication (like checking if the user is locked, active, user is still logged-in, etc. [excluding password hashing]) will use the AuthenticationProvider."

不,这是作为标准身份验证机制的一部分为您完成的 http://docs.spring.io/autorepo/docs/spring-security/3.2.0.RELEASE/apidocs/org/springframework/security/core/userdetails/UserDetails.html 如果您查看接口 UserDetails,您会发现如果上述任何一种方法 returns false 验证都会失败。 在非常不标准的情况下确实需要实施 AuthenticationProvider 。框架几乎涵盖了所有标准内容。

首先,我鼓励您阅读有关 String Security Core Services 的内容。

这种情况下的关键是AuthenticationManager that is responsible for deciding if the user is authenticated or not. This is what you configure with AuthenticationManagerBuilder. It's primary implementation in Spring is ProviderManager that allows to define multiple authentication mechanisms in a single applications. The most common use case is that there is one, but it is still handled by this class. Each of those multiple authentication mechanisms is represented by a different AuthenticationProviderProviderManager 获取 AunthenticationProviders 的列表并遍历它们以查看是否有任何一个可以对用户进行身份验证。

您感兴趣的是DaoAuthenticationProvider. As the name suggests, it allows to use a Data Access Object to authenticate the user. It uses a standard interface for such DAO - a 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 implementation, usually the one provided by Spring Security 就足够了。但如果需要,您也可以实现自己的。

通常你也会想要一个 PasswordEncoder. A good one, like 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 中,以便在您将用户密码保存在数据库中时对其进行编码。