『Spring Security』(四) 自定义用户数据储存方式

在实际开发过程中,我们的用户数据肯定不能存储在内存中。业务数据,一般都需要进行持久化处理。所以我们应该将用户数据放到数据库中存储。

在《重要概念及部分流程》中,提到过 Spring Security 支持三种用户数据存储方式:

  • 基于内存的简单存储 (In-Memory Authentication)
  • 关系型数据库存储 (JDBC Authentication)
  • 自定义数据存储 (UserDetailsService)

并且已经了解到,DaoAuthenticationProviderUserDetailsService 调用方法获取 UserDetails

所以,我们需要扩展UserDetailServiceUserDetails

创建项目

pom.xml

 
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <optional>true</optional>
</dependency>
复制代码

UserDetails

我们首先来实现UserDetails。在实际场景中,我们不可能只存储用户名和密码,总会有一些其他的扩展字段。

@Data
public class MyUserDetails implements UserDetails {
    private Long id;
    private String phone;
    private String username;
    private String password;
    /** 状态 (1:正常;0:禁用) */
    private Integer accountStatus;
    /** 角色集合 */
    private List<AuthRole> roleList;
 
    /** 修改为 Security 可识别 的GrantedAuthority */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roleList.stream()
                .map(authRole -> new SimpleGrantedAuthority(authRole.getName()))
                .collect(Collectors.toList());
    }
 
    @Override
    public String getPassword() { return this.password;}
 
    @Override
    public String getUsername() { return this.username;}
 
    /** 账户是否未过期 */
    @Override
    public boolean isAccountNonExpired() { return true; }
 
    /** 是否未被锁定 */
    @Override
    public boolean isAccountNonLocked() { return true; }
 
    /** 密码是否未过期 */
    @Override
    public boolean isCredentialsNonExpired() { return true;m }
 
    /** 账户是否可用 */
    @Override
    public boolean isEnabled() { return accountStatus.equals(1); }
复制代码

UserDetailService

Spring Security 通过 UserDetailService#loadUserByUsername获取UserDetails

此处的UserRepositoryRoleRepository均是模拟持久层操作。

模拟配置的两个用户:

AuthUser zhangsan = new AuthUser()
        .setId(1L)
        .setUsername("张三")
        .setPassword(bCryptPasswordEncoder.encode("123456"))
        .setAccountStatus(1);
AuthUser lisi = new AuthUser()
        .setId(2L)
        .setUsername("李四")
        .setPassword(bCryptPasswordEncoder.encode("654321"))
        .setAccountStatus(1);
复制代码

UserDetailsService

@Service
public class MyUserDetailServiceImpl implements UserDetailsService {
 
    @Autowired
    UserRepository userRepository;
    @Autowired
    RoleRepository roleRepository;
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
        AuthUser authUser = userRepository.getUserByUsername(username);
 
        List<AuthRole> roleList = roleRepository.getRoleListByUserId(authUser.getId());
 
        MyUserDetails userDetails = new MyUserDetails();
        BeanUtils.copyProperties(authUser,userDetails);
        userDetails.setRoleList(roleList);
 
        return userDetails;
    }
}
复制代码

对了,因为在做认证时,会用PasswordEncoder验证密码。所以直接先注入了一个Bean,用来加密密码。

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
复制代码

登陆

访问http://localhost:3344/actuator,被拦截到登陆界面

image-20201111164259630

我们先随便输入用户名密码,其验证不通过。

image-20201111164339797

接着,我们输入预配置的用户名密码。

image-20201111164617094

登陆成功,并且请求接口有数据返回了。

image-20201111164736158

但这还有一点,不符合我们的日常的用法。这个登陆请求用的form-data格式,而且我们登陆的时候一般都有验证码之类附加验证。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享