技术栈:
SpringBoot: 2.1.9
Mybatis Plus: 3.3.0

概述

接上一篇文章,之前给出的用户名和密码的信息都是在配置文件里固定写死的,这种做法并不适合实际的开发,在实际的项目系统中,用户名和密码都应该交由数据库进行管理。

用户登录时,系统会根据用户名,从存储设备查找该用户的密码及权限等,将其组装成一个UserDetails对象。并用UserDetails中的数据对用户进行认证,决定其输入的用户名/密码是否正确。

下面介绍这部分最重要的两个接口:UserDetailsService和UserDetails。

一、新增一个自定义的类来实现UserDetailsService接口,并注入我们自己的service。

UserDetailsService用来加载用户的信息,并没有其他功能。

默认实现:

UserDetailsService => UserDetailsManager => InMemoryUserDetailsManager => 存储于内存
 UserDetailsService => JdbcDaoImpl => 存储于数据库(磁盘)
/**
 * @program sweet-dream
 * @description:
 * @author: zhangchao
 * @date: 2020/03/15 15:41
 * @since: 1.0.0
 */
@Slf4j
@Component
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private IGyUserService userService;
    @Autowired
    private ISysRoleService roleService;
    @Autowired
    private ISysUserRoleService userRoleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("对用户 [{}] 进行信息加载...",username);
        GyUser user = (GyUser) userService.getOne(new QueryWrapper<GyUser>().lambda().eq(GyUser::getUserCode,username)).getData();
        if(user==null){
            log.info("用户 [{}] 未找到",username);
            throw new UsernameNotFoundException("Username:["+username+"] not found");
        }
        log.info("user:" + user);
        List<SysRole> rolesList = new ArrayList<>();
        List<SysUserRole> userRoleList = (List<SysUserRole>) userRoleService.list(new QueryWrapper<SysUserRole>().lambda().eq(SysUserRole::getUserAccount,username)).getData();
        if (isNotEmpty(userRoleList)){
            userRoleList.forEach(sysUserRole -> {
                rolesList.add((SysRole) roleService.getOne(new QueryWrapper<SysRole>().lambda().eq(SysRole::getCode,sysUserRole.getRoleCode())).getData());
            });
        }
        //设置权限
        user.setUserRoles(CollectionUtil.removeNull(rolesList));

        log.info("用户 [{}] 信息加载完成",username);
        // SecurityUser 实现UserDetails
        return new SecurityUser(user);
    }
//
//    public static void main(String[] args) {
//        System.out.println(new BCryptPasswordEncoder().encode("666666"));
//    }
}

UserDetailsService接口只提供了一个方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

该方法很容易理解:通过用户名来加载用户 。这个方法主要用于从系统数据中查询并加载具体的用户到Spring Security中。

CustomUserDetailsService类实现了UserDetailsService接口,里面有一个loadUserByUsername(String s)方法,这个方法返回一个UserDetails ,UserDetails 是一个接口,而org.springframework.security.core.userdetails.User实现了UserDetails,因此这里我们可以直接使用SpringSecurity提供的User对象,当然如果不想使用SpringSecurity提供的User对象,我们也可以自己编写一个实现UserDetails接口的对象。

二、新增SecurityUser实现UserDetails接口

* @program sweet-dream
 * @description:
 * @author: zhangchao
 * @date: 2020/03/15 14:55
 * @since: 1.0.0
 */
@Slf4j
public class SecurityUser implements UserDetails {

    private String uuid;
    private String userCode;
    private String username;
    private String password;
    private String sex;
    private List<SysRole> roles;

    public SecurityUser(GyUser user) {
        if (null != user){
            this.uuid = user.getUuid();
            this.userCode = user.getUserCode();
            this.username = user.getUserCode();
            this.password = user.getPassWord();
            this.sex = user.getSex();
            this.roles = user.getUserRoles();
        }
    }

    /**
     * 权限集合
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        List<SysRole> roles = this.getRoles();
        if(roles != null){
            for (SysRole role : roles) {
                System.out.println("role: "+role);
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+role.getName());
                authorities.add(authority);
            }
        }
        log.info("获取登录用户已具有的权限:{}", authorities.toString());
        return authorities;
    }


    /**
     * 指示用户的账户是否已过期。无法验证过期的账户。
     * @return 如果用户的账户有效(即未过期),则返回true,如果不在有效就返回false
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 指示用户是锁定还是解锁。无法对锁定的用户进行身份验证。
     * @return 如果用户未被锁定,则返回true,否则返回false
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示用户的凭证(密码)是否已过期。过期的凭证阻止身份验证
     * @return 如果用户的凭证有效(即未过期),则返回true
     *         如果不在有效(即过期),则返回false
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 指示用户是启用还是禁用。无法对禁用的用户进行身份验证
     * @return 如果启用了用户,则返回true,否则返回false
     */
    @Override
    public boolean isEnabled() {
        return true;
    }

  /*********************set\get\toString方法***********************/
    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public String getUserCode() {
        return userCode;
    }

    public void setUserCode(String userCode) {
        this.userCode = userCode;
    }

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

    public void setUsername(String username) {
        this.username = username;
    }

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

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public List<SysRole> getRoles() {
        return roles;
    }

    public void setRoles(List<SysRole> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "SecurityUser{" +
                "uuid='" + uuid + '\'' +
                ", userCode='" + userCode + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex='" + sex + '\'' +
                ", roles=" + roles +
                '}';
    }
}

UserDetails接口是我们自己用来定义用户表的结构的。

SpringSecurity自己的用户信息只包含了Username,password,roles,假如我希望用户的实体类中还有性别sex字段,那么就没有办法了,所以SpringSecurity提供了UserDetails接口,当然,UserDetails实例是通过UserDetailsService接口的loadUserByUsername方法返回的。不过我们需要注意一下getAuthorities方法,这个方法返回的是权限,但是我们返回的权限必须带有“ROLE_”开头才可以,SpringSecurity会自己截取ROLE_后边的字符串,也就是说,比如:我的权限叫ADMIN,那么,我返回告诉SpringSecurity的时候,必须告诉他权限是ROLE_ADMIN,这样SpringSecurity才会认为权限是ADMIN。

从上面UserDetailsService 可以知道最终交给Spring Security的是UserDetails 。该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象Authentication中去。UserDetails 默认提供了:

  • 用户的权限集, 默认需要添加ROLE_ 前缀
  • 用户的加密后的密码, 不加密会使用{noop}前缀
  • 应用内唯一的用户名
  • 账户是否过期
  • 账户是否锁定
  • 凭证是否过期
  • 用户是否可用

三、这我们的SecurityConfig中配置userDetailsService

@Autowired
private CustomUserDetailsService customUserDetailsService;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {       				auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}

四、启动项目测试

springboot角色权限控制_springboot角色权限控制

springboot角色权限控制_ide_02

输入用户名mayun,密码666666,登录成功!可以看到后台已经查询出此用户的所有权限。

springboot角色权限控制_java_03


PS 另外用到的一些其他类:

model类 GyUser

/**
 * <p>
 * 用户表设置
 * </p>
 *
 * @author zhangchao
 * @since 2020-03-02
 */
@Data
@TableName("Gy_User")
public class GyUser implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 唯一标识ID
     */
    @TableId(value = "UUID",type = IdType.ASSIGN_UUID)
    private String uuid;

    /**
     * 用户编码
     */
    @TableField("UserCode")
    private String UserCode;

    /**
     * 用户名称
     */
    @TableField("UserName")
    private String UserName;

    /**
     * 用户密码
     */
    @TableField("PassWord")
    private String PassWord;

    /**
     * 联系方式
     */
    @TableField("Mobile")
    private String Mobile;

    /**
     * 科室编码
     */
    @TableField("DeptCode")
    private String DeptCode;

    /**
     * HIS用户对应编码
     */
    @TableField("HISUserCode")
    private String HISUserCode;

    /**
     * 备注
     */
    @TableField("Remark")
    private String Remark;

    /**
     * 用户身份标识
     */
    @TableField("IdentityFlag")
    private Boolean IdentityFlag;

    /**
     * 创建时间
     */
    @TableField("SysCreateDate")
    private Date SysCreateDate;

    /**
     * 创建者
     */
    @TableField("SysCreateBy")
    private String SysCreateBy;

    /**
     * 更新时间
     */
    @TableField("SysUpdateDate")
    private Date SysUpdateDate;

    /**
     * 更新者
     */
    @TableField("SysUpdateBy")
    private String SysUpdateBy;

    /**
     * 版本号
     */
    @TableField("VersionNum")
    private Integer VersionNum;

    /**
     * 删除标识
     */
    @TableField("SysDelFlag")
    private String SysDelFlag;

    /**
     * 别名
     */
    @TableField("alias")
    private String alias;

    /**
     * 地址
     */
    @TableField("address")
    private String address;

    /**
     * 邮箱
     */
    @TableField("email")
    private String email;

    /**
     * 状态
     */
    @TableField("status")
    private String status;

    /**
     * 开始时间
     */
    @TableField("startDate")
    private Date startDate;

    /**
     * 结束时间
     */
    @TableField("endDate")
    private Date endDate;

    /**
     * 性别
     */
    @TableField("sex")
    private String sex;

    /**
     * 用户角色列表
     */
    @TableField(exist = false)
    private List<SysRole> userRoles;

}

service接口:IGyUserService

/**
 * <p>
 * 用户表设置 服务类
 * </p>
 *
 * @author zhangchao
 * @since 2020-03-02
 */
public interface IGyUserService extends BasicService<GyUser> {

}

service实现类:GyUserServiceImpl

/**
 * <p>
 * 用户表设置 服务实现类
 * </p>
 *
 * @author zhangchao
 * @since 2020-03-02
 */
@Service
public class GyUserServiceImpl extends BasicServiceImpl<GyUserMapper, GyUser> implements IGyUserService {

}

因为用到了mybatis plus,所以基础的CRUD接口就不用写了,mp都给你整合好了。

以上