springboot3 security 从始至终--04 UserDetailsService

先放一张图

一、UserDetailsService在架构中的地位

上面的图揭示 Spring 安全性体系结构中的主要参与者以及它们之间的关系。此体系结构是使用 Spring 安全性实现身份验证的核心概念。
AuthenticationFilter 将身份验证请求委托给 AuthenticationManager,后者使用 AuthenticationProvider 来处理身份验证。
AuthenticationProvider使用UserDetailsService实现用户管理。它的主要职责是通过用户名从缓存或基础存储中查找用户。

//UserDetailsService.java
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

二、UserDetailsService的默认实现

AuthenticationProvider的默认实现由 UserDetailsService 和 PasswordEncoder 两者提供的默认实现构成。

UserDetailsService 默认实现仅在应用程序的内存中注册默认凭据。这些默认凭据使用默认密码,该密码是在加载 Spring 上下文时写入应用程序控制台的随机生成的通用唯一标识符 (UUID)。

Using generated security password: 78nh23h-sd56-4b98-86ef-dfas8f8asf8

请注意,UserDetailsService 始终与密码编码器相关联,该编码器对提供的密码进行编码,并验证密码是否与现有编码匹配。当我们替换UserDetailsService的默认实现时,我们还必须指定一个PasswordEncoder。

三、基于内存的UserDetailsService

UserDetailsService 的第一个非常基本的例子是 InMemoryUserDetailsManager。这个实现类将凭据存储在内存中,然后 Spring Security可以使用它来验证传入的请求。

UserDetailsManager 扩展了 UserDetailsService 合约。除了继承的行为之外,它还提供了创建用户以及修改或删除用户密码的方法。用户详细信息服务仅负责按用户名检索用户。

@Configuration
public class AppConfig {

  @Bean
  public UserDetailsService userDetailsService() {
    var userDetailsService =
        new InMemoryUserDetailsManager();

    var user = User.withUsername("user")
            .password("password")
            .authorities("USER_ROLE")
            .build();

    userDetailsService.createUser(user); 

    return userDetailsService;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }
}

上述配置将 UserDetailsService 和 PasswordEncoder 类型的 bean 注册到 spring 上下文中,AuthenticationProvider会自动使用它们。Spring 允许我们直接将UserDetailsService 和PasswordEncoder 设置为身份验证管理器,如果我们愿意这样做,则以前的配置可以重写如下:

@Configuration
class AppConfig  {

  protected void configure(AuthenticationManagerBuilder auth) throws  Exception {

    var userDetailsService = new InMemoryUserDetailsManager();

    var user = User.withUsername("user")
        .password("password")
        .authorities("USER_ROLE")
        .build();

    userDetailsService.createUser(user);

    auth.userDetailsService(userDetailsService)
        .passwordEncoder(NoOpPasswordEncoder.getInstance());
  }
}

我们可以通过一个简单的测试来验证上述配置。

@Test
public void expectOKResponse_WhenAuthenticaionManagerIsTestedWithCorrectDetails() {
  AuthenticationManager authenticationManager = this.spring.getContext()
    .getBean(AuthenticationManager.class);

  Authentication authentication = authenticationManager
    .authenticate(UsernamePasswordAuthenticationToken
      .unauthenticated("user", "password"));

  assertThat(authentication.isAuthenticated()).isTrue();
}

请注意,InMemoryUserDetailsManager 不适用于生产就绪的应用程序。此类仅用于示例或概念证明。

四. 数据库支持的userDetailsService

为了存储和检索 SQL 数据库中的用户名和密码,我们使用JdbcUserDetailsManager 类。它通过 JDBC 直接连接到数据库。

默认情况下,它会在数据库中创建两个表:

  • USERS
  • AUTHORITIES

请注意,JdbcUserDetailsManager 需要一个数据源来连接到数据库,因此我们还需要定义它。

@EnableWebSecurity
public class AppSecurityConfig {

  @Bean
  public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
      .setType(EmbeddedDatabaseType.H2)
      .addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
      .build();
  }

  @Bean
  public UserDetailsService jdbcUserDetailsService(DataSource dataSource) {

    UserDetails user = User
      .withUsername("user")
      .password("password")
      .roles("USER_ROLE")
      .build();

    JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
    users.createUser(user);
    return users;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }
}

如果我们使用的是使用不同表或列名称的自定义 DDL 架构,我们还可以编写自定义 SQL 查询来获取用户和权限的详细信息。

@Bean
public UserDetailsService jdbcUserDetailsService(DataSource dataSource) {
  String usersByUsernameQuery = "select username, password, enabled from tbl_users where username = ?";
  String authsByUserQuery = "select username, authority from tbl_authorities where username = ?";
      
  JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);

  userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
  userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);

  return users;
}

参考https://howtodoinjava.com/spring-security/inmemory-jdbc-userdetails-service/

posted @ 2023-02-16 00:17  周XX  阅读(786)  评论(0编辑  收藏  举报