我啥时候应该在 Spring Boot 应用程序中覆盖 Spring Security 的 configure(AuthenticationManagerBuilder auth)?

Posted

技术标签:

【中文标题】我啥时候应该在 Spring Boot 应用程序中覆盖 Spring Security 的 configure(AuthenticationManagerBuilder auth)?【英文标题】:When should I override the configure(AuthenticationManagerBuilder auth) from Spring Security in a Spring Boot app?我什么时候应该在 Spring Boot 应用程序中覆盖 Spring Security 的 configure(AuthenticationManagerBuilder auth)? 【发布时间】:2021-02-08 01:48:25 【问题描述】:

我正在 Spring Boot 应用程序中学习 Spring Security,我有一个非常简单的示例。我发现如果我评论 configure(AuthenticationManagerBuilder auth) 没有区别。无论我是否使用它,我都有相同的输出,我需要使用硬编码的凭据登录。

@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter 

//    private final MyUserDetailsService myUserDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
                http
                .csrf().disable()
                        .authorizeRequests().anyRequest().authenticated()
                .and()
                        .httpBasic();
    

//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
//        auth.userDetailsService(myUserDetailsService);
//    

    @Bean
    public PasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

MyUserDetailsS​​ervice 类:

@Service
public class MyUserDetailsService implements UserDetailsService 

    private static final String USERNAME = "john";
    private static final String PASSWORD = "$2a$10$fDDUFA8rHAraWnHAERMAv.4ReqKIi7mz8wrl7.Fpjcl1uEb6sIHGu";

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException 

        if (!userName.equals(USERNAME)) 
            throw new UsernameNotFoundException(userName);
        

        return new User(USERNAME, PASSWORD, new ArrayList<>());
    

休息控制器:

@RestController
public class HelloController 

    @GetMapping("/hello")
    public String hello() 
        return "Hello World!";
    

我想知道实现UserDetailsService 接口是否等同于覆盖configure(AuthenticationManagerBuilder auth)。谢谢!

【问题讨论】:

【参考方案1】:

UserDetailsService

DaoAuthenticationProvider 使用 UserDetailsS​​ervice 来检索 用户名、密码和其他用于验证的属性 用户名和密码。 Spring Security 提供in-memory 和JDBC UserDetailsS​​ervice 的实现。

您可以通过公开自定义身份验证来定义自定义身份验证 UserDetailsS​​ervice 作为一个 bean。例如,以下将 假设 CustomUserDetailsS​​ervice 自定义身份验证 实现 UserDetailsS​​ervice

UserDetailsS​​ervice 接口用于检索用户相关数据。它有一个名为 loadUserByUsername() 的方法,可以覆盖来自定义查找用户的过程。为了提供我们自己的用户服务,我们需要实现 UserDetailsS​​ervice 接口。

loadUserByUsername(String username) 返回 UserDetails,它是 org.springframework.security.core.userdetails 的一部分,org.springframework.security.core.userdetailsgetUsername(), getPassword(), getAuthorities() 组成进一步用于弹簧安全性的方法。

我们还可以通过实现UserDetails接口自定义org.springframework.security.core.userdetails.User(这里用作new User(USERNAME, PASSWORD, new ArrayList&lt;&gt;()))。

在这里,我分享使用 UserDetailsS​​ervice 服务的理想方式

@Component("userDetailsService")
public class DomainUserDetailsService implements UserDetailsService 

    private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class);

    private final UserRepository userRepository;

    public DomainUserDetailsService(UserRepository userRepository) 
        this.userRepository = userRepository;
    

    @Override
    @Transactional
    public UserDetails loadUserByUsername(final String login) 
        log.debug("Authenticating ", login);

        if (new EmailValidator().isValid(login, null)) 
            return userRepository.findOneWithAuthoritiesByEmailIgnoreCase(login)
                .map(user -> createSpringSecurityUser(login, user))
                .orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database"));
        

        String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
        return userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin)
            .map(user -> createSpringSecurityUser(lowercaseLogin, user))
            .orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));

    

    private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) 
        if (!user.getActivated()) 
            throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
        
        List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
            .map(authority -> new SimpleGrantedAuthority(authority.getName()))
            .collect(Collectors.toList());
        return new org.springframework.security.core.userdetails.User(user.getLogin(),
            user.getPassword(),
            grantedAuthorities);
    

when loadUserByUsername is invoked?

如上所述,它通常由 DaoAuthenticationProvide 实例调用以对用户进行身份验证。例如,当提交用户名和密码时,会调用 UserdetailsS​​ervice 来查找该用户的密码以查看其是否正确。它通常还会提供有关用户的一些其他信息,例如权限和您可能希望为登录用户访问的任何自定义字段(例如电子邮件)

In-Memory Authentication

这里您使用了用户名和密码的静态值,可以使用In-Memory Authentication 进行理想配置,如下所示。

Spring Security 的 InMemoryUserDetailsManager 实现 UserDetailsService 以支持在内存中检索的基于 用户名/密码 的身份验证。 InMemoryUserDetailsManager 通过实现UserDetailsManager 接口提供对UserDetails 的管理。当 Spring Security 配置为接受 用户名/密码 进行身份验证时,基于UserDetails 的身份验证被使用。

@Bean
public UserDetailsService users() 
    UserDetails user = User.builder()
        .username("user")
        .password("bcrypt$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
        .roles("USER")
        .build();
    UserDetails admin = User.builder()
        .username("admin")
        .password("bcrypt$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
        .roles("USER", "ADMIN")
        .build();
    return new InMemoryUserDetailsManager(user, admin);

配置(AuthenticationManagerBuilder 身份验证)

这个方法使用AuthenticationManagerBuilder,它在内部使用SecurityBuilder来创建一个AuthenticationManager。允许轻松构建内存身份验证、LDAP 身份验证、基于 JDBC 的身份验证、添加 UserDetailsS​​ervice 和添加 身份验证提供者。

How Spring Security add/configure AuthenticationManagerBuilder?

UserDetailsS​​ervice 接口等效于重写 配置(AuthenticationManagerBuilder 身份验证)

【讨论】:

感谢您的回复!所以在我的情况下,如果 MyUserDetailsS​​ervice 用 Service 注释,我不需要覆盖 configure(AuthenticationManagerBuilder auth)? 您好 elvis,您可以使用 @Component@Service 注释 MyUserDetailsService,并且可以使用上面共享的 UserDetailsS​​ervice 并从中删除静态用户名和密码。 不需要覆盖configure(AuthenticationManagerBuilder auth)【参考方案2】:

不,不一样。

应用程序中作为 bean 提供的用户详细信息服务已向 global 身份验证管理器 (details) 注册,并且是所有本地身份验证管理器的后备。

根据应用程序设置可以有多个本地身份验证管理器。每个本地身份验证管理器将使用配置为configure(AuthenticationManagerBuilder auth) 的默认用户详细信息服务。

我什么时候应该覆盖配置(AuthenticationManagerBuilder auth) 来自 Spring Boot 应用程序中的 Spring Security?

如果您有不同的授权/身份验证要求,并且您想插入自己的身份验证提供程序以满足要求或添加任何内置提供程序(如 ldap 和内存提供程序),则应覆盖。您也可以直接使用如下所示的 http 安全 bean 来完成。

所有身份验证提供程序都添加到Provider Manager 并尝试直到找到一个。

默认情况下不提供任何东西(即没有用户详细信息服务或不覆盖身份验证管理器),您将拥有默认全局身份验证管理器和自动配置的用户详细信息管理器(即用户密码InMemoryUserDetailsManager 实现,如UserDetailsServiceAutoConfiguration 自动配置中配置的那样) )。

因此,当您提供用户详细信息服务应用程序 bean 时,自动配置会退出,现在您的全局身份验证管理器现在已使用提供的 bean 进行配置。

更多详情here

Here 很好地解释了这一切是如何结合在一起的。

我还想对 Spring Security 身份验证管理器进行更多扩展,这很容易被忽略。

正如我之前提到的,有全局身份验证管理器和本地身份验证管理器。如果需要,在配置每个时要特别小心。

这在全局身份验证管理器注释的 java 文档中进行了解释。

EnableGlobalAuthentication 注解表明被注解的 类可用于配置的全局实例 身份验证管理器生成器。 例如:

@Configuration 
@EnableGlobalAuthentication  
public class MyGlobalAuthenticationConfiguration 

   @Autowired
   public void configureGlobal(AuthenticationManagerBuilder auth) 
            auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
                            .and().withUser("admin").password("password").roles("USER", "ADMIN"); 

使用 EnableGlobalAuthentication 注解的注解也表明被注解的类可以 用于配置 AuthenticationManagerBuilder 的全局实例。 例如:

 @Configuration  
 @EnableWebSecurity  
 public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter 

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) 
            auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
                            .and().withUser("admin").password("password").roles("USER", "ADMIN");
    

    // Possibly overridden methods ...     

以下注解使用EnableGlobalAuthentication注解 EnableWebSecurity EnableWebMvcSecurity EnableGlobalMethodSecurity

在没有 EnableGlobalAuthentication 注解有不可预知的结果。

EnableGlobalAuthentication 导入配置 AuthenticationConfiguration 负责设置全局认证管理器的默认配置。

AuthenticationConfiguration 配置两个关键部分来制作身份验证管理器 - 用户详细信息和身份验证提供者。

用户详细信息使用InitializeUserDetailsBeanManagerConfigurer 配置,身份验证提供程序使用InitializeAuthenticationProviderBeanManagerConfigurer 配置。两个必需的 bean 都在应用程序上下文中查找 - 这就是您的用户详细信息服务向全局身份验证管理器注册的方式。

GlobalMethodSecurityConfigurationWebSecurityConfigurerAdapter 是全局身份验证管理器的消费者。

WebSecurityConfigurerAdapter 可用于创建和配置本地身份验证管理器(添加新的身份验证提供程序),通常也用于在 mvc vs rest 和 public vs admin 端点等应用程序中具有不同的身份验证/授权要求。

单独使用 spring security @EnableWebSecurity 触发上述流程作为 spring security 过滤器链设置的一部分。使用 spring boot 时,spring security 自动配置会触发相同的流程。

在 spring security 5.4 版本中,您可以将 http 安全定义为 bean,而无需扩展 WebSecurityConfigurerAdapter 类。 Spring Boot 将在 2.4.0 版本中支持此功能。更多详情here

    @Bean
    SecurityFilterChain configure(HttpSecurity http) throws Exception 
      
         http
            .authenticationProvider(custom authentication provider)
            .userDetailsService( custom user details service)
            .csrf().disable()
                    .authorizeRequests().anyRequest().authenticated()
            .and()
                    .httpBasic();
        return http.build();
      

【讨论】:

【参考方案3】:

您正在使用@Service 注释,它在组件扫描时创建UserDetailsService 的bean。无需在AuthenticationManagerBuilder中再次指定。

如果您不使用@Service 注解,那么您可以通过覆盖AuthenticationManagerBuilderWebSecurityConfigurerAdapter 中手动配置它。

【讨论】:

【参考方案4】:

要完全关闭默认的 Web 应用程序安全配置,您可以添加一个带有 @EnableWebSecurity 的 bean,如 spring boot documentation(第 4.10.1.MVC 安全部分)中所述,

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter 
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
            auth.authenticationProvider(MyAuthenticationProvider);
        
    

@EnableWebSecurity 是一个标记注释。它允许 Spring 找到(它是 @Configuration,因此是 @Component)并自动将类应用到全局 WebSecurity

要完全关闭默认的 Web 应用程序安全配置,您可以添加一个带有 @EnableWebSecurity 的 bean(这不会禁用身份验证管理器配置或执行器的安全性)。要自定义它,您通常使用WebSecurityConfigurerAdapter 类型的外部属性和bean(例如添加基于表单的登录)。

...

如果您添加 @EnableWebSecurity 并同时禁用 Actuator 安全性,您将获得整个应用程序的默认基于表单的登录,除非您添加自定义 WebSecurityConfigurerAdapter

...

如果您在应用程序的任何位置使用@EnableWebSecurity 定义@Configuration,它将关闭 Spring Boot 中的默认 webapp 安全设置(但保持 Actuator 的安全启用)。要调整默认值,请尝试在security.* 中设置属性(有关可用设置的详细信息,请参阅SecurityProperties)和通用应用程序属性的安全部分。

【讨论】:

【参考方案5】:

不,实现 UserDetailsS​​ervice 接口并不等同于覆盖 configure(AuthenticationManagerBuilder auth)。

如果您覆盖 UserDetailsS​​ervice 并通过覆盖 loadUserByUsername() 来验证用户名和密码,在您的情况下它是静态值(我建议静态用户使用 inMemoryAuthentication)。

您需要 Autowired UserDetailsS​​ervice

@Autowired
UserDetailsService userDetailsService;

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

这将告诉您的 authenticationManager 使用为身份验证实现的 userDetailsS​​ervice。

【讨论】:

【参考方案6】:

我想知道实现 UserDetailsS​​ervice 接口是否等同于覆盖configure(AuthenticationManagerBuilder auth)

不,它们没有可比性。

UserDetailsService 是加载用户特定数据的核心接口。它在整个框架中用作用户 DAO,并且是 DaoAuthenticationProvider 使用的策略。 *

AuthenticationManagerBuilder 允许轻松构建内存身份验证、基于 JDBC 的身份验证、添加 UserDetailsS​​ervice 和添加 AuthenticationProvider。

所以很明显,当您使用UserDetailsService 时,这意味着您正在使用DaoAuthenticationProvider 从您的底层数据库中获取用户详细信息。

注意:AuthenticationProvider 是一种抽象,用于从不同来源/存储库中获取用户信息,并验证检索到的信息是否与用户提供的信息相似。

我们来看一个例子,配置是这样的:

@Autowired
YourUserDetailServiceImpl userDetailsService;
.....

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception 
    auth.authenticationProvider(authenticationProvider());

...
@Bean
public DaoAuthenticationProvider authenticationProvider()
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); //a provider 
    provider.setUserDetailsService(userDetailsService); //user details service
    provider.setPasswordEncoder(encoder()); //you can add password encoders too
    
    return provider;

YourUserDetailServiceImpl 必须覆盖loadUserByUsername() 才能获取使用的详细信息。

@Override
public UserDetails loadUserByUsername(String email) 
    final Account acc = accRepository.findByEmail(email);
    if (acc == null)
        throw new UsernameNotFoundException("Account not found");
    
    //this can be a custom Object of your choice that `extends User`
    return new UserPrincipal(
                acc.getEmail(), 
                acc.getPassword(), 
                acc.isEnabled(), 
                true, true, true, 
                acc.getEpsRoles().stream()
                        .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
                        .collect(Collectors.toList()));

【讨论】:

以上是关于我啥时候应该在 Spring Boot 应用程序中覆盖 Spring Security 的 configure(AuthenticationManagerBuilder auth)?的主要内容,如果未能解决你的问题,请参考以下文章

我啥时候应该考虑使用 ORM 框架?

我啥时候应该在 C 中使用 malloc,啥时候不应该?

我啥时候应该在 C 中使用 malloc,啥时候不应该?

我啥时候应该销毁令牌[关闭]

我啥时候应该在“class”上使用“className”,反之亦然?

我啥时候应该在objective-c中释放这些对象?