前言

对于一个有实用价值的系统,都会有很多的使用群体,这些群体之间,由于分工合作或其他安全原因,对同一个系统的资源(各个模块的操作)会拥有不同的操作,由此引申出了一个概念,权限管理。此文将对权限管理进行解读、阐明权限管理的一种方法。


表结构设计

t_user

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT
  `user_name` varchar(20) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  `true_name` varchar(20) DEFAULT NULL,
  `email` varchar(30) DEFAULT NULL,
  `phone` varchar(20) DEFAULT NULL,
  `is_valid` int(4) DEFAULT '1',
  `create_date` datetime DEFAULT NULL,
  `update_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;

t_role

CREATE TABLE `t_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(255) DEFAULT NULL,
  `role_remark` varchar(255) DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  `update_date` datetime DEFAULT NULL,
  `is_valid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8

t_user_role

CREATE TABLE `t_user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  `is_valid` tinyint(4) DEFAULT NULL,
  `create_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `update_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;

t_module

CREATE TABLE `t_module` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `module_name` varchar(255) DEFAULT NULL,
  `module_style` varchar(255) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL,
  `parent_opt_value` varchar(255) DEFAULT NULL,
  `grade` int(255) DEFAULT NULL,
  `opt_value` varchar(255) DEFAULT NULL,
  `orders` int(11) DEFAULT NULL,
  `tree_path` varchar(255) DEFAULT NULL,
  `is_valid` tinyint(4) DEFAULT NULL,
  `create_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `update_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4;

t_permission

CREATE TABLE `t_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `module_id` int(11) DEFAULT NULL,
  `acl_value` varchar(255) DEFAULT NULL COMMENT '权限值',
  `create_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `update_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_valid` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=74 DEFAULT CHARSET=utf8mb4;

tips:由于存在多级操作:设计一个tree_path字段方便处理 内容为各级的 module_id 前后用逗号分隔。


概念理解

权限管理分为三部分:
    基础表维护CRUD
    授权
    认证

一、基础表的CRUD

user/role/module

二、给角色绑定资源

  • 加载权限树通过角色ID 采用z-tree:权限树操作
select id, module_name, from module where is_valid = 1
select count(1) from permission where module_id = ? and role_id = ? and is_valid =1(有就勾选)
  • 授权(勾选) 思路:为保证保证权限树展现和数据库记录同步:需要同步父子级的权限观察权限树的操作行为,在勾选权限树时:该节点(权限)下的子级全部勾选,该节点(权限)下的父级(祖先们)需全部勾选(若没有勾选:则勾选)
sql:
1.对于本身:插入
insert into t_permission() values()
2.对于子级:查找--》统一删除--》统一插入
select module_id from t_module where tree_path like ("tree_path%")
delete from t_permission where module_id in (?) and role_id = ?
insert into t_permission() values(),(),()...
3.对于父级:查找--》判断--》插入
select module_id from t_module where id in (tree_path)  此处需要处理tree_path前后的逗号
select count(1) from t_permission where id = ?
insert into t_permission() values(),(),()...
tips:此处可将所有待插入的数据放入List,最后统一执行一条批量插入语句,提升性能
  • 撤销权限(取消勾选) 观察权限树的操作行为:取消勾选时: 该节点(权限)下的子级全部取消勾选 该节点(权限)下的父级(祖先们)做判定(没有其他子级勾选时,取消勾选)
sql:
1.对于本身:删除
delete from t_permission where role_id = ? and module_id = 
2.对于子级:查找--》统一删除
select module_id from t_module where tree_path like ("tree_path%")
delete from t_permission where module_id in (?) and role_id = ?
3.对于父级:查找--》判断--》删除
3.1 此处需要处理tree_path前后的逗号,还需要排序
select module_id from t_module where id in (tree_path) order by id
select count(1) from t_permission where module_id = ?
3.2 如果count == 0,执行删除: 
delete from t_permission where module_id in (?) and role_id = ?
tips:此处可将所有待删除的数据ID放入List中,最后统一执行一条批量删除语句,提升性能。

三、给用户赋予角色

页面展示:easyui中通过combobox多选完成 roleIds

1.统一删除
delete from t_user_role where user_id = ?
2.统一插入
insert into t_user_role(user_id, role_id) values(?, ?),(),()...

四、认证

前台页面认证

freemarker:
<#if test="${permissions.contains('1010')}">html元素(操作组件)</#if> (从session中取权限数据)

基于SpringAOP+自定义注解实现后台权限认证

核心sql

1.根据用户ID查角色ID
select role_id from user_role where user_id = ? and is_valid=1

2.根据角色ID查操作码(授权码)
select optValue from permission where role_id in (?) and is_valid=1

3.将得到的数据放入list中,将list放入session中
session.setAttribute("permissions", list);

认证步骤:

1.定义注解:

@Target({ElementType.METHOD}) // 作用域
@Retention(RetentionPolicy.RUNTIME) // 生命周期
@Documented // 生成java doc
@Inherited // 继承
public @interface RequirePermissions {
    public String permission() default "";
}

2.引入aop的namespace、启用aop注解。

<!-- 启用@Aspect注解 -->
<aop:aspectj-autoproxy />

3.编写切面类。

@Component
@Aspect
public class PermissionProxyByAnnotation {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private UserService userService;
    @Autowired
    private PermissionService permissionService;
    //@Pointcut("execution(* com.lbh.controller.*.*(..))")
    @Pointcut("@annotation(com.lbh.annotation.RequirePermissions)")
    public void pointcut(){}

    @Around(value = "pointcut()")
    public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
        Integer userId = LoginUserUtil.getUserIdFromCookie(request);
        String uri = request.getRequestURI();
        //部分资源放行
        if("/index/toLogin".equals(uri)||"/user/login".equals(uri)){
            return pjp.proceed();
        }
        AssertUtil.intIsNotEmpty(userId, "请先登录");
        List<UserRole> userRoles = userService.findUserRoles(userId);
        AssertUtil.isTrue(userRoles==null, "您无权访问此系统");
        StringBuffer roleIds = new StringBuffer();
        for(UserRole userRole: userRoles){
            roleIds.append(userRole.getRoleId()).append(",");
        }
        List<String> permissions = Collections.emptyList();
        if(roleIds.length()>0){
            permissions = permissionService.findPermissionByRoleIds(roleIds.substring(0, roleIds.length()-1));
        }

        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        RequirePermissions requirePermissions = method.getAnnotation(RequirePermissions.class);

        if(requirePermissions != null){
            String permission = requirePermissions.permission();
            if(!permissions.contains(permission)){
                throw new UnAuthPermissionException(permission, "您无权操作此模块:此模块的需要权限值:"+permission);
            }
        }
        request.getSession().setAttribute(Constant.USER_PERMISSION_SESSION_KEY,permissions);
        Object result = pjp.proceed();
        return result;
    }
}

五、权限认证框架

apache-shiro实现权限认证

  • 关键类 Subject/SecurityManager/Realm/Credentials
  • 关键方法 SecurityUtil.getSubject().login()/logout()