使用sa-token管理角色操作权限

858 阅读1分钟

配置config类

参考地址:sa-token.cc/doc.html#/u…

该类主要重写了getPermissionList()和getRoleList()方法

其中:

getPermissionList():负责为当前登录的对象给予权限

getRoleList():负责为当前登录的对象绑定角色

例如:

当前用户管理员登录了系统,那么他的角色是admin,拥有的权限为admin:addEmployee

若游客登录,那么他的角色为nologin,只有浏览前台页面的权限(默认为无)

下列代码主要流程:

从方法提供的Object o这个参数(实际为用户id),查询数据库获取权限列表(权限内容存储在数据库),为登录者赋权。

每次请求都会进入该方法需要查询数据库,这里使用了 @Cacheable(value = "xxxx")注解,存储到缓存中,减少数据库查询次数。

Component    // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展
@Slf4j
public  class AuthConfig implements StpInterface {


    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private SysRoleService roleService;
    @Autowired
    private RoleMappingService roleMappingService;

    //添加某些权限可以访问的
    @Override
    @Cacheable(value = "PermissionList")
    public List<String> getPermissionList(Object o, String loginType) {

        List<String> list = new ArrayList<>();
        Long roleId = Long.valueOf(o.toString());
        RoleMapping role = getTypeById(roleId);

        if(role==null){
            // 游客登录
            return list;
        }
        int type = role.getType();
        if(type==1){
            // 管理员
            //添加“新增员工”权限
            list.add("admin:addEmployee");
            //添加“编辑内容”权限
            list.add("admin:edit");
        }
        else if(type==2){
            //员工权限

            // 查询编辑权限
            Integer isEdit = role.getIsEdit();
            if(isEdit==1){
                list.add("admin:edit");
            }
        }

        return list;
    }

    //添加某些角色可以访问的
    @Override
    @Cacheable(value = "RoleList")
    public List<String> getRoleList(Object o, String loginType) {
        List<String> list = new ArrayList<>();

        Long roleId = Long.valueOf(o.toString());
        RoleMapping role = getTypeById(roleId);

        String name ;
        if(role!=null){
            // 已存在的用户
            Integer type = role.getType();
            name = roleService.getById(type).getName();
        }else {
            //游客
            name = roleService.getById(NOLOGIN_TYPE).getName();
        }
      list.add(name);

        return list;
    }

    private RoleMapping getTypeById(Long id){
        LambdaQueryWrapper<RoleMapping> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RoleMapping::getRoleId,id);
        RoleMapping role = roleMappingService.getOne(wrapper);
        return role;
    }

}

看完这段代码,可能会好奇,哪儿传入的用户id的,没看见欸,下面就来介绍login方法

用户注册

参考地址: sa-token.cc/doc.html#/u…

@Override
public Result login(String username, String password) {
    //MD5加密
    password= MD5Util.getMD5(password);
    //通过账户查这个员工对象,这里就不走Service层了
    LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(Employee::getUsername, username);
    Employee empResult = getOne(wrapper);
    //判断用户是否存在
    if (empResult==null){
        return Result.error("账户不存在");
        //密码是否正确
    }else if (!empResult.getPassword().equals(password)){
        return Result.error("账户密码错误");
        //员工账户状态是否正常,1状态正常,0封禁
    }else if (empResult.getStatus()!=1){
        // 获取封禁时间
        long disableTime = StpUtil.getDisableTime(Long.valueOf(empResult.getId()));
        long minutes = Convert.convertTime(disableTime, TimeUnit.SECONDS, TimeUnit.MINUTES);
        return Result.error("当前账户正在封禁,还剩"+minutes+"分钟");
        //状态正常允许登陆
    }else {
        log.info("登陆成功!!!");

     /*    String token = UUID.randomUUID().toString();
        redisUtils.set(EMPLOYEE_TOKEN_PREFIX+token,empResult.getId(),EMPLOYEE_TOKEN_TTL);*/
        //用户登录, 设置三天有效期
        StpUtil.login(empResult.getId(),new SaLoginModel()
                .setDevice("PC")
                .setTimeout(60 * 60 * 24 * 3));
        // 获取token
        String token = StpUtil.getTokenInfo().getTokenValue();
        log.info("登录生成的token为"+token);

        empResult.setToken(token);

        //把员工对象存入localSession作用域
        return Result.success(empResult);
    }
}

逐一分析代码~

开头一段就是简单的用户登录验证,主要需要注意的是,这儿采用了empResult.getStatus()方法,可以获取当前用户封禁状态以及剩余的解放时间,存储在redis内。得益于sa-token的整合,我们并不需要直接操作redis,他会自动存储到redis内

参考地址: sa-token.cc/doc.html#/u…

//MD5加密
password= MD5Util.getMD5(password);
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getUsername, username);
Employee empResult = getOne(wrapper);

//判断用户是否存在
if (empResult==null){
    return Result.error("账户不存在");
}
//密码是否正确
else if (!empResult.getPassword().equals(password)){
    return Result.error("账户密码错误");
}
 //员工账户状态是否正常,1状态正常,0封禁
else if (empResult.getStatus()!=1){
    // 获取封禁时间
    long disableTime = StpUtil.getDisableTime(Long.valueOf(empResult.getId()));
    long minutes = Convert.convertTime(disableTime, TimeUnit.SECONDS, TimeUnit.MINUTES);
    return Result.error("当前账户正在封禁,还剩"+minutes+"分钟");
    //状态正常允许登陆
}

后一段是用户在校验成功后,注册登录信息的代码。

从注释中可以看到,我一开始是使用redis存储token的,使用sa-token后,就直接使用login()方法替代了,它可以自动替我存储到redis和前端请求头中的token。login()方法内的参数就是用户id

log.info("登陆成功!!!");

/*    String token = UUID.randomUUID().toString();
   redisUtils.set(EMPLOYEE_TOKEN_PREFIX+token,empResult.getId(),EMPLOYEE_TOKEN_TTL);*/
   
   
   //用户登录, 设置三天有效期
   StpUtil.login(empResult.getId(),new SaLoginModel()
           .setDevice("PC")
           .setTimeout(60 * 60 * 24 * 3));
   // 获取token
   String token = StpUtil.getTokenInfo().getTokenValue();
   log.info("登录生成的token为"+token);

   empResult.setToken(token);

   //把员工对象存入localSession作用域
   return Result.success(empResult);

撤销权限

上面说到了如何授权,那么再拓展下撤销权限

下面这段代码的作用是:

前端按下封禁用户或者禁止编辑按钮后,会向后台传入修改权限用户的id和修改后的状态代码,1代表正常,0代表封禁

//更新员工状态
@ResponseBody
@PutMapping("/employee")
@SaCheckPermission("admin:addEmployee")
@CacheEvict(value={"employeeCache","RoleList","PermissionList"},allEntries = true)
@Transactional
public Result updateStatus( @RequestBody Employee employee) {
    Long id = employee.getId();
    RoleMapping roleMapping = new RoleMapping();
    //封禁操作
    Integer status = employee.getStatus();
    if(status!=null){
        //封禁操作
        if(status==0){
            // 先踢下线后封禁
            // 先踢下线
            StpUtil.kickout(id);
            // 再封禁账号
            StpUtil.disable(id,EMPLOYEE_DISABLE_LIMIT);
        }else {
            // 解除封禁
            StpUtil.untieDisable(id);
        }
        employee.setStatus(status);
    }else{
        Integer isEdit = employee.getIsEdit();
        roleMapping.setRoleId(id);
        roleMapping.setIsEdit(isEdit);

        LambdaQueryWrapper<RoleMapping> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RoleMapping::getRoleId,id);
        roleMappingService.update(roleMapping,wrapper);

    }

    //拿新的状态值
    log.info("更新的用户信息为:"+employee);
    //员工信息更新后,会自动清除缓存
    employeeService.updateById(employee);
    return Result.success(employee);
}

再次逐端分析~

若status不为空,则为封禁相关操作,否则执行else的禁止编辑操作

这里使用了官方提供的踢人下线和封禁账号方法,若指定封禁了时间,需要配合定时任务解除封禁状态。

例如我指定了封禁该用户1天,这个具体的实现是存储到redis内的,TTL即为封禁事件,若时间一过,那么理论上用户解除封禁。但是!!!你若此时将用户的状态(封禁状态)存入了数据库,一天后sa-token可不会替你修改数据库内容的,所以记得加上定时器任务。

当然,你要你压根没设计将用户状态更新到数据库,而在登录判断时候直接使用StpUtil.isDisable()方法判断用户状态的,当我没说计定时任务这段话.....

定时任务实现参考我这篇:juejin.cn/post/724553…

参考链接:sa-token.cc/doc.html#/u…

if(status!=null){
    //封禁操作
    if(status==0){
        // 先踢下线后封禁
        // 先踢下线
        StpUtil.kickout(id);
        // 再封禁账号
        StpUtil.disable(id,EMPLOYEE_DISABLE_LIMIT);
    }else {
        // 解除封禁
        StpUtil.untieDisable(id);
    }
    employee.setStatus(status);
}

这是禁止编辑操作,直接修改角色数据库中角色状态,待下次请求时,会直接执行config类的if判断,从而做到刷新权限。这里需要注意的是,若在config类两个方法上加了缓存,更新后记得清除缓存,否则会出现数据库字段更新成功,前台选项响应不了的情况,亲身经历.....

else{
    Integer isEdit = employee.getIsEdit();
    
    roleMapping.setRoleId(id);
    roleMapping.setIsEdit(isEdit);
    LambdaQueryWrapper<RoleMapping> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(RoleMapping::getRoleId,id);
    
    roleMappingService.update(roleMapping,wrapper);

}

完整项目参考地址: gitee.com/gitee-enter…