UserDetailsService

 前文已经认识了 UserDetails,但是 Spring Securit 是从哪获取的用户呢?以及如何添加用户、修改用户?这就是 UserDetailsService 的职责所在了。

先看一下 UserDetailsService 的定义,它只有一个方法,通过 username 来获取用户,返回值是 UserDetails 类型对象。当找不到对应的用户时,会抛出 UsernameNotFoundException。

深入 UserDetailsService 2_UserDetailsService

我们要做的就是实现 loadUserByUsername() 方法,通过读取数据库等方法获取到用户信息。

先实现一个功能简单的 UserDetailsService,功能类似于前面的 InMemoryUserDetailsManager。在实现 UserDetailsService 之前,还需要先实现 UserDetails。

实现 UserDetails,用于创建 UserDetails 对象。

// User.java
public class User implements UserDetails {

  private final String username;
  private final String password;
  private final String authority;

  public User(String username, String password, String authority) {
    this.username = username;
    this.password = password;
    this.authority = authority;
  }

  @Override
  public String getUsername() {
    return username;
  }

  @Override
  public String getPassword() {
    return password;
  }

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return List.of(() -> authority);
  }

}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

实现 UserDetailsService,用于保存、获取用户。

// InMemoryUserDetailsService.java
public class InMemoryUserDetailsService implements UserDetailsService {

  // 把用户保存在对象属性中
  private final List<UserDetails> users;

  public InMemoryUserDetailsService(List<UserDetails> users) {
    this.users = users;
  }

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 从对象的 users 属性中查找、比对用户
    return users.stream()
        .filter(user -> user.getUsername().equals(username))
        .findFirst()
        .orElseThrow(() -> new UsernameNotFoundException("User not found"));

  }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

配置自定义的 InMemoryUserDetailsService,一个简单但完整的示例就完成了。

// SecurityConfig.java
@Configuration
public class SecurityConfig {

  @Bean
  UserDetailsService userDetailsService() {

    // 创建一个用户
    UserDetails user = new User("user2", "123456", "read");

    UserDetailsService service = new InMemoryUserDetailsService(List.of(user));

    return service;
  }

  @Bean
  PasswordEncoder passwordEncoder() {
    // 使用 NoOpPasswordEncoder 作为密码编码器
    return NoOpPasswordEncoder.getInstance();
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

UserDetailsManager

很多时候,应用中还需要创建、修改、删除用户的功能,Spring Security 就提供了这样一个接口 UserDetailsManager,通过方法名能很清除地明白各个方法的作用。

深入 UserDetailsService 2_Spring Security_02

实际工作中,大部分情况用户都是保存在数据中的,Spring Security 提供了一个通过数据库实现 UserDetailsManager 的类 JdbcUserDetailsManager。它默认(可以自定义)需要两个表:users 表和 authorities 表,users 表至少有 username、password、enabled 三个列,用来保存用户的信息,authorities 表至少有 username、authority 两个列来保存授权,并与用户关联。在 这里可以查看 Spring Boot 如何连接 MySQL 数据库。

users 表

深入 UserDetailsService 2_UserDetailsManager_03

authorities 表

深入 UserDetailsService 2_Spring Security_04

数据库创建完毕后,添加一个用户,然后在 authorities 表中添加一条与用户对应的 authority 数据。这些准备工作完成后,就可以配置 JdbcUserDetailsManager 了。

// SecurityConfig.java
@Configuration
public class SecurityConfig {

  // 注入 DataSource
  @Resource
  private DataSource dataSource;

  @Bean
  UserDetailsService userDetailsService() {
    // 实例化 
    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
    return manager;
  }

  @Bean
  PasswordEncoder passwordEncoder() {
    // 使用 NoOpPasswordEncoder 作为密码编码器
    return NoOpPasswordEncoder.getInstance();
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

深入 UserDetailsService 2_UserDetailsService_05

深入 UserDetailsService 2_Spring Security_06

这里创建了两个用户,user1 和 user2,在 authorities 表中给 user1 添加了授权,没有给 user2 添加授权。user1 可以正常访问,user2 因为没有授权,返回了401。

深入 UserDetailsService 2_Spring Security_07

至此 JdbcUserDetailsManager 就可以工作了,如果数据库表名或列和 JdbcUserDetailsManager 默认的不一致,对象提供了一些方法来修改默认的 SQL 语句,这样就可以灵活发挥了。

深入 UserDetailsService 2_UserDetailsManager_08