【问题标题】:Custom WebSecurityConfigurerAdapter自定义 WebSecurityConfigurerAdapter
【发布时间】:2015-01-14 08:58:24
【问题描述】:

我在使用 SpringBoot 和 SpringBoot-Security 实现自定义登录身份验证时遇到了这个问题。我制作了一个Bitbucket repository 作为该线程的参考(在CustomSecuringWeb branch 内)。首先,这里的大多数 cmets 都遵循Securing a Web Application 教程。

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

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

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

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

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

【问题讨论】:

  • 您的 bitbucket 链接已失效

标签: spring dependency-injection spring-security spring-boot


【解决方案1】:

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

案例一:

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

案例2:

另一方面,AuthentictionProvider 用于提供自定义的身份验证方法,例如,如果您想对登录名和密码以外的字段进行自定义身份验证,您可以通过实现 AuthentictionProvider 并将此对象提供给您的 @ 987654327@。我不认为这是你想在你的项目中做的。您只是希望使用默认方式的登录名和密码基于存储在数据库中的用户来实现您的身份验证。 在上面的 Case 1 中,您仅实现了 UserDetailsServiceAuthentictionProvider 的实例是由容器在 AuthenticationManager 中为您创建的,它是 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

在 cmets 之后: 您可以在您的配置方法中设置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 对象并将您的编码器设置在该对象上,该对象将在您的情况下将其传递给AuthenticationPrioviderDaoAuthenticationProvider 已经为您创建。 就像评论中提到的 roadrunner 一样,您很少需要实现自己的AuthenticationProvider,通常大多数身份验证配置调整都可以使用AbstractDaoAuthenticationConfigurer 来完成,如上所述,auth.userDetailsService(userDetailsService) 是从auth.userDetailsService(userDetailsService) 返回的。

“如果我想添加密码加密。如果我想进行其他身份验证(例如检查用户是否被锁定、活动、用户是否仍在登录等 [不包括密码哈希])将使用 AuthenticationProvider。”

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

【讨论】:

  • 我明白了。基本上使用UserDetailsService 使用username 参数检索用户详细信息。如果我想添加密码加密。如果我想要进行其他身份验证(例如检查用户是否被锁定、活动、用户是否仍在登录等[不包括密码哈希])将使用AuthenticationProvider
  • 如果我想进行密码加密,我只需创建一个自定义 PasswordEncoder 实例,然后在任何一种情况下都包含它,对吧?
  • 没有。 Spring Security 中已经有 AuthenticationProviderPsswordEncoder 接口的非常好的实现。在 99% 的情况下,您应该自行实施它们。此外,这似乎超出了原始问题的范围。请阅读参考文档或提出其他问题。
【解决方案2】:

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

【讨论】:

  • 这可能是一个快速的解决方案。但是,如果我想为所有与密码相关的字段创建自定义密码编码器怎么办。
  • PasswordEncoder 负责对密码进行编码/解码。编码器可以通过这个方法在 DAO 上指定:auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder();
  • 但是如果我想创建一个除 jdbcAuthentication 之外的自定义身份验证(即 SessionAuthentication - 检查用户是否仍然登录等)怎么办?
  • single responsibility principle 之后,您不应在AuthenticationProvider 中处理会话存储。 Spring Security 有其他机制来处理它,它是一个开箱即用的功能,默认启用,如果不需要,可以禁用。
【解决方案3】:

首先,我鼓励您阅读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,因此您可以在UserRepository@Autowire 对用户密码进行编码,同时将它们保存在数据库中。

【讨论】:

  • 有没有办法删除 XML 配置?
  • 这是另一种选择。您可以使用 XML 或 Java 来完成。我展示了两种方式。你不能同时做这两件事——只做其中之一。
猜你喜欢
  • 1970-01-01
  • 2017-05-26
  • 2021-06-01
  • 2020-06-20
  • 2017-01-08
  • 2018-04-26
  • 2020-09-30
  • 2023-03-25
  • 2015-08-05
相关资源
最近更新 更多