Spring Security专栏(一文搞懂Security中的用户对象)

1,314 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

写在前面

今天继续讲Spring Security,前几天我们讲了Spring Security是一种什么样的安全框架以及如何构建用户认证体系

在我看来,用户认证体系分为两部分:一个是用户、一个是认证(我是这样的理解的)。

那么本文先讲其中的一部分:用户对象

Spring Security 中的用户对象

Spring Security 中的用户对象用来描述用户并完成对用户信息的管理,涉及UserDetails、GrantedAuthority、UserDetailsService 和 UserDetailsManager这四个核心对象。

  • UserDetails:指的是 Spring Security 中的用户

  • GrantedAuthority:定义用户的操作权限。

  • UserDetailsService:定义了对 UserDetails 的查询操作。

  • UserDetailsManager:扩展 UserDetailsService,添加了创建用户、修改用户密码等功

看着很简单、很容易理解,接着往下看!

我们先来看承载用户详细信息的 UserDetails 接口:


public interface UserDetails extends Serializable {
 
    //获取该用户的权限信息
    Collection<? extends GrantedAuthority> getAuthorities();
 
    //获取密码
    String getPassword();
	 
	//获取用户名
    String getUsername();
 
	//判断该账户是否已失效
    boolean isAccountNonExpired();
 
    //判断该账户是否已被锁定
    boolean isAccountNonLocked();
 
    //判断该账户的凭证信息是否已失效
    boolean isCredentialsNonExpired();
 
    //判断该用户是否可用
    boolean isEnabled();
}

通过 UserDetails,我们可以获取用户相关的基础信息,并判断其当前状态。同时,我们也可以看到 UserDetails 中保存着一组 GrantedAuthority 对象。而 GrantedAuthority 指定了一个方法用来获取权限信息,如下所示:

public interface GrantedAuthority extends Serializable {
    //获取权限信息
    String getAuthority();
}

UserDetails 还存在一个子接口 MutableUserDetails。我们看下

//MutableUserDetails 从名字上来看是不可变的接口
//而可变的内容就是密码
interface MutableUserDetails extends UserDetails {
    //设置密码
    void setPassword(String password);
}

如何创建用户对象

如果我们想要在应用程序中创建一个 UserDetails 对象,可以使用如下所示的链式语法:

UserDetails user = User.withUsername("admin") 
  .password("123456") 
  .authorities("read", "write") 
  .accountExpired(false) 
  .disabled(true) 
  .build();

Spring Security 还专门提供了一个 UserBuilder 对象来辅助构建 UserDetails,使用方式也类似:

User.UserBuilder builder = 
	User.withUsername("admin");
 
UserDetails user = builder
  .password("123456") 
  .authorities("read", "write")
  .accountExpired(false) 
  .disabled(true) 
  .build();

在 Spring Security 中,针对 UserDetails 专门提供了一个 UserDetailsService,该接口用来管理 UserDetails,定义如下:

public interface UserDetailsService {
 
    //根据用户名获取用户信息
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

而 UserDetailsManager 继承了 UserDetailsService,并提供了一批针对 UserDetails 的操作接口:

public interface UserDetailsManager extends UserDetailsService {
    //创建用户
    void createUser(UserDetails user);
 
    //更新用户
    void updateUser(UserDetails user);
 
    //删除用户
    void deleteUser(String username);
 
    //修改密码
    void changePassword(String oldPassword, String newPassword);
 
    //判断指定用户名的用户是否存在
    boolean userExists(String username);
}

如何存储用户

通过上面的讲解,几个核心用户对象之间的关联关系就很清楚了,接下来我们需要进一步明确具体的实现过程。

我们来看 UserDetailsManager 的两个实现类:

  • 一个是基于内存存储的 InMemoryUserDetailsManager,
  • 一个是基于关系型数据库存储的 JdbcUserDetailsManager。

这里,我们以 JdbcMemoryUserDetailsManager 为例展开分析,它的 createUser 方法如下所示:

public void createUser(final UserDetails user) {
        validateUserDetails(user);
 
        getJdbcTemplate().update(createUserSql, ps -> {
             ps.setString(1, user.getUsername());
             ps.setString(2, user.getPassword());
             ps.setBoolean(3, user.isEnabled());
 
             int paramCount = ps.getParameterMetaData().getParameterCount();
             if (paramCount > 3) {
                 ps.setBoolean(4, !user.isAccountNonLocked());
                 ps.setBoolean(5, !user.isAccountNonExpired());
                 ps.setBoolean(6, !user.isCredentialsNonExpired());
             }
        });
 
        if (getEnableAuthorities()) {
             insertUserAuthorities(user);
        }
}

可以看到,这里直接使用了 Spring 框架中的 JdbcTemplate 模板工具类实现了数据的插入,同时完成了 GrantedAuthority 的存储。

UserDetailsManager 是一条相对独立的代码线,为了完成用户信息的配置,还存在另一条代码支线,即 UserDetailsManagerConfigurer。该类维护了一个 UserDetails 列表,并提供了一组 withUser 方法完成用户信息的初始化,如下所示:

private final List<UserDetails> users = new ArrayList<>();
 
public final C withUser(UserDetails userDetails) {
        this.users.add(userDetails);
        return (C) this;
}

而 withUser 方法返回的是一个 UserDetailsBuilder 对象,该对象内部使用了前面介绍的 UserBuilder 对象。

因此可以实现类似.withUser("spring_user").password("password1").roles("USER") 这样的链式语法,完成用户信息的设置

总结

今天我们剖析了用户对象机制,将几个类之间的关系也梳理了一番。下面大家可以进去源码再跟一遍。 今天就讲到这里,下一期我们聊聊认证体系中的认证对象,有了用户的处理类,肯定也要聊是如何认证的。 加油,一起学习!!!

弦外之音

感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。也欢迎有问题我们下面评论交流

加油! 我们下期再见!

给大家分享几个我前面写的几篇骚操作

聊聊不一样的策略模式(值得收藏)

copy对象,这个操作有点骚!