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