springboot实现简单自定义权限控制

框架选择
权限控制是管理系统必不可少的模块,当前成熟的权限控制框架有shiro,spring security等,利用框架开发的好处就是简洁方便快捷。

本文权限思路——注解方式
本文实现权限的方式是没有使用框架,而是通过自定义注解方式实现简单的权限管理控制。

关于表设计
一般权限表的设计都可以通过5张表来实现,用户表,角色表,权限表,用户角色关系表,角色权限关系表,其中用户与角色是多对多关系,角色与权限也是多对多关系,下图是实现思路举例,具体要根据各自业务逻辑灵活设计。在这里插入图片描述

本文思路是通过自定义注解进行权限控制,表设计如下
在这里插入图片描述
其中know_bg_user是用户表,know_bg_user_per_relation是用户权限关系表,与用户表是多对多关系,know_bg_permission是系统权限表,与用户权限关系表是多对多关系,know_bg_sys_module是系统模块表,与系统权限表是一对多关系。
这里是不是有疑问呢,为啥没有角色表月用户角色关系表?表设计是根据业务逻辑来设计的,这里是设定了权限只针对个人用户,不针对角色,也就是说权限与个人绑定,不再与角色绑定。(这种需求虽然说与正常的权限控制看起来很变扭,但是我在实际项目中就有这种需求,所以这里就以这种需求记录)

权限控制实现思路
首先从系统模块表开始,比如这张表里有一些数据,来表示模块,也可认为是菜单
在这里插入图片描述
现在需要把这些数据处理成树形结构,因为know_bg_permission系统权限表中有一个模块id,它们是一对多关系,处理成树形结构后,根据模块id去设置权限即可。

具体实现过程

项目结构图如何
在这里插入图片描述
1:自定义一个权限注解,这个注解用于标注在controller层方法上


@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {

    //黑名单 0:是 1:不是
    int blacklist() default 0;

    //权限名称
    String permissionName() default "";

    //排序
    int sort();

    //模块名称
    String moduleName();

    //模块主键,是know_bg_sys_module表的主键,用于权限表和
    //模块表建立关联关系
    long moduleId();
}

例如添加在添加部门的方法上:

 @Autowired
    private KnowBgDepartmentService knowBgDepartmentService;

    @ApiOperation("新增部门")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "departmentName", value = "部门名称", required = true, dataType = "string", paramType = "query"),
    })
    @Permission(permissionName = "添加",sort = 1, moduleName = "部门设置" ,moduleId = 90020)
    @RequestMapping(value = "insertDept",method = RequestMethod.POST)
    public JsonResult<Integer> insertDept(@RequestParam String departmentName){
        return new JsonResult<Integer>(knowBgDepartmentService.insertDept(departmentName),JsonResult.SUCCESS_MESSAGE);
    }


2:注解定义好后,定义一个项目启动即执行的方法,预热权限到数据库,这样就不用手动插入

2-1:通过Java反射获取注解方法和注解值
注意:这里是项目启动先删除表里面的权限,再批量新增,这样每次启动权限的主键发生变化,也要同时修改用户关权限关系表里的权限主键!!!这一步至关重要,不然重启后所有用户都没有权限!!!

反射信息实体类

/**
 * Copyright (C), 2018-2020
 * FileName: ReflectionInfo
 * Author:   yu.gui.ming
 * Date:     2020-06-30 11:54
 * Description: 反射信息实体
 */
@Data
public class ReflectionInfo extends SerializeEntity {

    //servlet路径
    private String url;

    //类名
    private String className;

    //方法
    private String method;

    //类型
    private String type;
}


通过反射获取controller类信息的逻辑类


/**
 * Copyright (C), 2018-2020
 * FileName: ProjectReflectionController
 * Author:   yu.gui.ming
 * Date:     2020-06-30 11:53
 * Description: 权限反射控制器
 */
@RestController
public class ProjectReflectionController {

    @Autowired
    WebApplicationContext applicationContext;

    @Autowired
    private KnowBgPermissionMapper knowBgPermissionMapper;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private KnowBgUserPerRelationMapper knowBgUserPerRelationMapper;

    //控制器类信息list
    public static List<ReflectionInfo> reflectInfoList = new ArrayList<ReflectionInfo>();

    /**
     * 初始化controller类信息
     * RequestMappingHandlerMapping在controller中使用
     * @return
     */
    public  void setReflectInfoList(){
        try{
            RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
            // 获取url与类和方法的对应信息
            Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
            for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
                RequestMappingInfo info = m.getKey();
                HandlerMethod method = m.getValue();
                PatternsRequestCondition p = info.getPatternsCondition();
                ReflectionInfo ref = new ReflectionInfo();
                for (String url : p.getPatterns()) {
                    ref.setUrl(url);
                }
                ref.setClassName(method.getMethod().getDeclaringClass().getName()); // 类名
                ref.setMethod(method.getMethod().getName()); // 方法名
                RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
                for (RequestMethod requestMethod : methodsCondition.getMethods()) {
                    ref.setType( requestMethod.toString());
                }
                //去除swagger相关方法
                if(!ParamUtils.isNullForParam(ref.getType())){
                    reflectInfoList.add(ref);
                }
            }
        }finally {
            insReqIdentifying();
        }
    }



    /**
     * 根据请求url获取方法上的注释名称
     * @param url 路径标识
     * @return
     */
    public Object[] getMethodApiOperation(String url){
        Class cls = null;
        if(null == reflectInfoList || reflectInfoList.isEmpty() || reflectInfoList.size() <= 0)
            throw new BusinessException("init reflectInfoList error");
        for (ReflectionInfo entity:reflectInfoList) {
            if(entity.getUrl().equals(url)){
                try {
                    cls = Class.forName(entity.getClassName());
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                try {
                    Method[] ms = cls.getDeclaredMethods();
                    Object[] array = new Object[5];
                    for (Method m:ms) {
                        if(m.getName().equals(entity.getMethod())){
                            Permission p = m.getAnnotation(Permission.class);
                            if(ParamUtils.isNullForParam(p)){
                                return null;
                            }else{
                                array[0] = p.blacklist();
                                array[1] = p.permissionName();
                                array[2] = p.sort();
                                array[3] = p.moduleName();
                                array[4] = p.moduleId();
                                return array;
                            }
                        }
                    }
                } catch (Exception e){
                    throw new BusinessException("url 有误");
                }
            }
        }
        return null;
    }


    /**
     * 预热权限
     */
    public  void insReqIdentifying(){
        if(null == reflectInfoList || reflectInfoList.isEmpty() || reflectInfoList.size() <= 0){
            this.setReflectInfoList();
        }
        reflectInfoList = reflectInfoList.stream().filter(reflectionInfo -> !ParamUtils.isNullForParam(this.getMethodApiOperation(reflectionInfo.getUrl()))).collect(Collectors.toList());
        List<KnowBgPermission> list = new ArrayList<>();
        for (ReflectionInfo reflectionInfo :reflectInfoList) {
            KnowBgPermission p = new KnowBgPermission();
            p.setPermissionId(BaseEntity.getUUID()+ RandomUtils.getRandomNum(8));
            Object[] array = this.getMethodApiOperation(reflectionInfo.getUrl());
            if(!ParamUtils.isNullForArray(array)){
                p.setBlackStatus((Integer)array[0]);
                p.setPermissionName((String)array[1]);
                p.setSequence((Integer)array[2]);
                p.setModuleName((String)array[3]);
                p.setModuleId((Long)array[4]);
            }
            p.setPermissionUrl(reflectionInfo.getUrl());
            list.add(p);
        }
		 //更新用户关系表
        List<KnowBgPermission> sourcePerList = knowBgPermissionMapper.getAllPermission();
        List<KnowBgUserPerRelation> relationList = knowBgUserPerRelationMapper.selectAll();
        Map<Long,Long> map = new HashMap<>();
        sourcePerList.forEach(s->{
            list.forEach(n->{
                if(s.getPermissionUrl().equals(n.getPermissionUrl())){
                    map.put(s.getPermissionId(),n.getPermissionId());
                }
            });
        });
        relationList.forEach(re->{
            map.forEach((key,value)->{
                if(re.getPermissionId().longValue() == key){
                    re.setPermissionId(value);
                }
            });
        });
        knowBgUserPerRelationMapper.delete(null);
        knowBgUserPerRelationMapper.batchInsUserPerRelation(relationList);
        //更新权限表
        knowBgPermissionMapper.delete(null);
        knowBgPermissionMapper.batchInsertPermission(list);
        //把权限存到缓存
        redisUtils.setValue(Constant.SYS_PER_IDENTIFYING, JSON.toJSONString(list));
        //给默认用户添加所有权限(一般是超级管理员)
        addPerForUser(4616465135465464164L);
        addPerForUser(416545654164536545L);
    }

    /**
     * 给用户添加权限
     * @param userId
     */
    private void addPerForUser(Long userId){
        List<KnowBgPermission> list = knowBgPermissionMapper.getAllPermission();
        list = list.stream().filter(knowBgPermission -> knowBgPermission.getBlackStatus().intValue() == 0).collect(Collectors.toList());
        List<KnowBgUserPerRelation> l = new ArrayList<KnowBgUserPerRelation>();
        list.forEach(p->{
            KnowBgUserPerRelation r = new KnowBgUserPerRelation();
            r.setRelationId(BaseEntity.getUUID()+RandomUtils.getRandomNum(7));
            r.setUserId(userId);
            r.setPermissionId(p.getPermissionId());
            l.add(r);
        });
        knowBgUserPerRelationMapper.delUserPerRelation(userId);
        knowBgUserPerRelationMapper.batchInsUserPerRelation(l);
    }
}



2-2:项目启动执行插入方法


/**
 * Copyright (C), 2018-2019
 * FileName: MyApplicationRunner
 * Author:   yu.gui.ming
 * Date:     219-4-14 17:07
 * Description:
 */
@Component
public class MyApplicationRunner implements CommandLineRunner {


    @Autowired
    private ProjectReflectionController projectReflectionController;

    @Override
    @Transactional
    public void run(String... args) throws Exception {
        projectReflectionController.setReflectInfoList();
    }
}



插入效果图
在这里插入图片描述

3:把权限模块(菜单)和权限处理成树形结构返回前端即可
思路:利用mybatis的一对多映射查询,查询所有的权限模块同时根据模块id顺便查询出权限子列表,然后在业务层处理成树形结构即可。切记:递归查询数据库是大忌!!!正确方式应该是一次查询出所有数据在业务层处理成树形结构。

xml 映射查询代码参考:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="net.lersin.backstage.business.systemSetup.modules.dao.KnowBgSysModuleMapper" >
  <resultMap id="BaseResultMap" type="net.lersin.backstage.business.systemSetup.modules.entity.KnowBgSysModule" >
    <id column="module_id" property="moduleId" jdbcType="BIGINT" />
    <result column="module_name" property="moduleName" jdbcType="VARCHAR" />
    <result column="parent_module_id" property="parentModuleId" jdbcType="BIGINT" />
    <result column="sequence" property="sequence" jdbcType="INTEGER" />
  </resultMap>
  <sql id="Base_Column_List" >
    module_id, module_name, parent_module_id, sequence
  </sql>

  <!-- 模块权限 -->
  <resultMap id="SecondBaseResultMap" type="net.lersin.backstage.business.systemSetup.modules.vo.ModuleVo">
    <id column="module_id" property="moduleId" jdbcType="BIGINT" />
    <result column="module_name" property="moduleName" jdbcType="VARCHAR" />
    <result column="parent_module_id" property="parentModuleId" jdbcType="BIGINT" />
    <result column="permission_name" property="permissionName" jdbcType="VARCHAR" />
    <collection property="perVoList" column="module_id" select="net.lersin.backstage.business.systemSetup.permission.dao.KnowBgPermissionMapper.getPerVoListByModuleId"></collection>
  </resultMap>

  <!-- 获取所有模块 -->
  <select id="queryAllModules" resultMap="SecondBaseResultMap" >
    select
    module_id,
    module_name,
    parent_module_id
    from know_bg_sys_module
    order by module_id
  </select>
</mapper>



递归处理数据结构参考我另一篇博客(表结构也一样的):

https://blog.csdn.net/Ming13416908424/article/details/107171119

最终swagger数据结构效果:
其中childList 是子模块列表,perVoList是当前模块下的权限列表

{
  "state": 200,
  "message": "请求成功",
  "data": [
    {
      "moduleId": "100",
      "moduleName": "网站概况",
      "parentModuleId": "0",
      "childList": []
    },
    {
      "moduleId": "200",
      "moduleName": "知识产品",
      "parentModuleId": "0",
      "childList": [
        {
          "moduleId": "20040",
          "moduleName": "问题咨询",
          "parentModuleId": "200",
          "childList": [],
          "perVoList": []
        },
        {
          "moduleId": "20050",
          "moduleName": "职场故事",
          "parentModuleId": "200",
          "childList": [],
          "perVoList": []
        },
        {
          "moduleId": "20070",
          "moduleName": "通知公告",
          "parentModuleId": "200",
          "childList": [],
          "perVoList": []
        }
      ]
    },
    {
      "moduleId": "300",
      "moduleName": "企业招聘",
      "parentModuleId": "0",
      "childList": []
    },
    {
      "moduleId": "400",
      "moduleName": "品台互动",
      "parentModuleId": "0",
      "childList": []
    },
    {
      "moduleId": "500",
      "moduleName": "数据分析",
      "parentModuleId": "0",
      "childList": []
    },
    {
      "moduleId": "600",
      "moduleName": "营销管理",
      "parentModuleId": "0",
      "childList": []
    },
    {
      "moduleId": "700",
      "moduleName": "会员管理",
      "parentModuleId": "0",
      "childList": []
    },
    {
      "moduleId": "800",
      "moduleName": "财务数据",
      "parentModuleId": "0",
      "childList": []
    },
    {
      "moduleId": "900",
      "moduleName": "系统设置",
      "parentModuleId": "0",
      "childList": [
        {
          "moduleId": "90010",
          "moduleName": "员工设置",
          "parentModuleId": "900",
          "childList": [],
          "perVoList": [
            {
              "permissionId": "478396832741042564",
              "moduleId": "90010",
              "permissionName": "添加"
            },
            {
              "permissionId": "478396832756723399",
              "moduleId": "90010",
              "permissionName": "删除"
            },
            {
              "permissionId": "478396832708138370",
              "moduleId": "90010",
              "permissionName": "编辑"
            },
            {
              "permissionId": "478396832798881150",
              "moduleId": "90010",
              "permissionName": "启用/禁用"
            },
            {
              "permissionId": "478396832737060932",
              "moduleId": "90010",
              "permissionName": "查看"
            }
          ]
        },
        {
          "moduleId": "90020",
          "moduleName": "部门设置",
          "parentModuleId": "900",
          "childList": [],
          "perVoList": [
            {
              "permissionId": "478396832742846512",
              "moduleId": "90020",
              "permissionName": "添加"
            },
            {
              "permissionId": "478396832721049288",
              "moduleId": "90020",
              "permissionName": "删除"
            },
            {
              "permissionId": "478396832715416994",
              "moduleId": "90020",
              "permissionName": "编辑"
            },
            {
              "permissionId": "478396832796742266",
              "moduleId": "90020",
              "permissionName": "查看"
            }
          ]
        },
        {
          "moduleId": "90030",
          "moduleName": "角色设置",
          "parentModuleId": "900",
          "childList": [],
          "perVoList": [
            {
              "permissionId": "478396832749710159",
              "moduleId": "90030",
              "permissionName": "添加"
            },
            {
              "permissionId": "478396832781272120",
              "moduleId": "90030",
              "permissionName": "删除"
            },
            {
              "permissionId": "478396832727406820",
              "moduleId": "90030",
              "permissionName": "编辑"
            },
            {
              "permissionId": "478396832729774600",
              "moduleId": "90030",
              "permissionName": "查看"
            }
          ]
        }
      ]
    }
  ]
}

用户权限拦截思路
定义一个拦截器,用户访问时判断用户请求的servletPath路径,判断用户的权限是否包含有此访问路径,有则放行,没有则报异常即可。

拦截器主要方法参考:

 /**
     * 校验用户权限
     * @param queUserVo 用户信息对象(包括基本信息,权限等,是校验token的上一个方法传递下来的)
     * @param request 请求对象
     */
    private void checkPer(QueUserVo queUserVo, HttpServletRequest request){
        //获取权限列表
        String servletPath = request.getServletPath();
        //从缓存里面取权限
        List<KnowBgPermission> perList = (List<KnowBgPermission>) JSON.parseArray(redisUtils.get(Constant.SYS_PER_IDENTIFYING),KnowBgPermission.class);
        List<KnowBgPermission> list = new ArrayList<>();
        //缓存为空时去查db
        if(ParamUtils.isNullForCollection(perList))
            knowBgPermissionMapper.getAllPermission().stream().forEach(p->{ if(0 == p.getBlackStatus()) list.add(p); });
        else
        	//这一步是过滤掉白名单,因为一般类似省市区下拉选的接口我们不定义为权限
            perList.stream().forEach(p->{ if(0 == p.getBlackStatus()) list.add(p); });
        //开始校验用户权限
        List<Long> perIds = new ArrayList<Long>();
        list.forEach(knowBgPermission -> { perIds.add(knowBgPermission.getPermissionId()); });
        list.forEach(p->{
            if(p.getPermissionUrl().equals(servletPath)){
               if(!queUserVo.getPerIdList().contains(p.getPermissionId())){
                   throw new BusinessException(BusinessException.PERMISSION_CODE,"没有权限");
               }
            }
        });
    }



  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值