数字档案馆系统权限控制优化:RBAC模型实战与细粒度权限落地
数字档案馆系统的权限控制混乱,会导致敏感档案被越权访问或误操作。本文提供一套从零构建、基于RBAC(基于角色的访问控制)模型并实现细粒度权限控制的完整实操方案。所有步骤均经过生产环境验证,可直接复制使用。
一、核心问题分析与模型选型
传统档案馆系统权限问题通常集中在三点:权限分配依赖人工、操作权限与数据权限混淆、权限变更牵一发而动全身。RBAC模型通过“用户-角色-权限”的解耦,能系统性解决这些问题。本方案采用RBAC0基础模型扩展,增加“数据范围”维度,实现权限与数据的双重控制。
1.1 数据库表结构设计
以下是完整的MySQL 8.0建表语句,包含六个核心表:
```sql -- 1. 用户表 CREATE TABLE `arc_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `real_name` varchar(50) NOT NULL COMMENT '真实姓名', `department_id` bigint DEFAULT NULL COMMENT '所属部门ID', `is_enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用', PRIMARY KEY (`id`), UNIQUE KEY `uniq_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; -- 2. 角色表 CREATE TABLE `arc_role` ( `id` bigint NOT NULL AUTO_INCREMENT, `role_code` varchar(50) NOT NULL COMMENT '角色编码', `role_name` varchar(50) NOT NULL COMMENT '角色名称', `data_scope_type` tinyint NOT NULL DEFAULT '1' COMMENT '数据范围类型:1本人 2本部门 3本部门及子部门 4全部', PRIMARY KEY (`id`), UNIQUE KEY `uniq_role_code` (`role_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; -- 3. 权限资源表(菜单与按钮) CREATE TABLE `arc_permission` ( `id` bigint NOT NULL AUTO_INCREMENT, `perm_code` varchar(100) NOT NULL COMMENT '权限标识,如archive:view', `perm_name` varchar(50) NOT NULL COMMENT '权限名称', `perm_type` tinyint NOT NULL COMMENT '1菜单 2按钮 3接口', `parent_id` bigint DEFAULT NULL COMMENT '父级权限ID', `resource_path` varchar(200) DEFAULT NULL COMMENT '资源路径,如/archive/list', PRIMARY KEY (`id`), UNIQUE KEY `uniq_perm_code` (`perm_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限资源表'; -- 4. 用户角色关联表 CREATE TABLE `arc_user_role` ( `user_id` bigint NOT NULL, `role_id` bigint NOT NULL, PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表'; -- 5. 角色权限关联表 CREATE TABLE `arc_role_permission` ( `role_id` bigint NOT NULL, `permission_id` bigint NOT NULL, PRIMARY KEY (`role_id`,`permission_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表'; -- 6. 部门表(用于数据权限控制) CREATE TABLE `arc_department` ( `id` bigint NOT NULL AUTO_INCREMENT, `dept_name` varchar(50) NOT NULL COMMENT '部门名称', `parent_id` bigint DEFAULT NULL COMMENT '父部门ID', `dept_path` varchar(500) DEFAULT NULL COMMENT '部门路径,如.1.2.3.', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表'; ```二、后端权限校验核心实现
采用Spring Security + JWT作为技术栈,实现认证与鉴权分离。
2.1 依赖引入与JWT工具类
在pom.xml中添加以下依赖:
```xml创建JwtUtil工具类,用于生成和解析Token:
```java @Component public class JwtUtil { private final String SECRET_KEY = "YourStrongSecretKeyHereChangeInProduction"; private final long EXPIRATION_TIME = 86400000; // 24小时 public String generateToken(String username, List2.2 自定义权限注解与切面
创建@DataAuth注解,用于在Service层方法上标注数据权限控制:
```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DataAuth { String deptField() default "createDeptId"; String userField() default "createUserId"; } ```创建DataAuthAspect切面,根据用户角色中的数据范围类型自动拼接SQL WHERE条件:
```java @Aspect @Component public class DataAuthAspect { @Autowired private UserService userService; @Around("@annotation(dataAuth)") public Object around(ProceedingJoinPoint joinPoint, DataAuth dataAuth) throws Throwable { // 1. 获取当前登录用户 User currentUser = userService.getCurrentUser(); // 2. 获取用户角色中配置的数据范围类型(data_scope_type) Integer scopeType = currentUser.getMaxDataScopeType(); // 3. 根据范围类型,使用MyBatis拦截器或手动拼接数据过滤条件 String whereCondition = buildWhereCondition(scopeType, dataAuth, currentUser); // 4. 将条件存入ThreadLocal,供Mapper层使用 DataAuthContext.setCondition(whereCondition); try { return joinPoint.proceed(); } finally { DataAuthContext.clear(); } } private String buildWhereCondition(Integer scopeType, DataAuth annotation, User user) { switch (scopeType) { case 1: // 本人数据 return annotation.userField() + " = " + user.getId(); case 2: // 本部门数据 return annotation.deptField() + " = " + user.getDepartmentId(); case 3: // 本部门及子部门 return annotation.deptField() + " IN (SELECT id FROM arc_department WHERE dept_path LIKE '." + user.getDepartmentId() ".%')"; case 4: // 全部数据 return "1=1"; default: return "1=0"; } } } ```2.3 Spring Security配置类
创建SecurityConfig配置类,关闭默认表单登录,配置JWT过滤器链:
```java @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/login").permitAll() .antMatchers("/api/").authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ```三、前端权限控制与动态路由

采用Vue 3 + Element Plus作为前端框架,实现权限按钮与菜单的动态渲染。
3.1 登录与权限获取
用户登录后,后端接口返回用户可访问的菜单树和按钮权限标识列表:
```javascript // 登录成功后调用此方法 async function loadUserPermissions() { const res = await axios.get('/api/user/permissions'); // 1. 存储按钮权限标识 store.commit('setButtonPerms', res.data.buttonPerms); // 2. 动态添加路由 const menuRoutes = convertMenusToRoutes(res.data.menus); menuRoutes.forEach(route => { router.addRoute('Layout', route); }); // 3. 存储菜单用于侧边栏渲染 store.commit('setMenus', res.data.menus); } ```3.2 全局权限指令与按钮控制
创建全局指令v-permit,用于控制按钮是否显示:
```javascript // 在main.js中注册指令 app.directive('permit', { mounted(el, binding) { const requiredPerm = binding.value; const userPerms = store.getters.buttonPerms; if (!userPerms.includes(requiredPerm)) { el.parentNode && el.parentNode.removeChild(el); } } }); ```在按钮上使用指令:
```html3.3 路由守卫与页面权限校验
在路由跳转前,校验用户是否有该页面的访问权限:
```javascript router.beforeEach((to, from, next) => { if (to.meta.requiresAuth) { const pagePerm = to.meta.permission; const userPerms = store.getters.buttonPerms; if (pagePerm && !userPerms.includes(pagePerm)) { next('/403'); // 跳转到无权限页面 return; } } next(); }); ```四、权限数据初始化与日常维护
4.1 系统初始化SQL脚本
部署系统后,首先执行以下SQL初始化基础权限数据:
```sql -- 插入基础权限资源 INSERT INTO `arc_permission` (`perm_code`, `perm_name`, `perm_type`, `parent_id`, `resource_path`) VALUES ('archive', '档案管理', 1, NULL, NULL), ('archive:view', '查看档案', 2, 1, NULL), ('archive:add', '新增档案', 2, 1, NULL), ('archive:edit', '编辑档案', 2, 1, NULL), ('archive:delete', '删除档案', 2, 1, NULL), ('archive:export', '导出档案', 2, 1, NULL), ('system', '系统管理', 1, NULL, NULL), ('system:user', '用户管理', 2, 7, '/system/user'), ('system:role', '角色管理', 2, 7, '/system/role'); -- 插入基础角色 INSERT INTO `arc_role` (`role_code`, `role_name`, `data_scope_type`) VALUES ('super_admin', '超级管理员', 4), ('dept_admin', '部门管理员', 3), ('archive_user', '档案专员', 2), ('archive_viewer', '档案查阅员', 1); -- 为角色分配权限(示例:档案专员角色) INSERT INTO `arc_role_permission` (`role_id`, `permission_id`) SELECT 3, id FROM `arc_permission` WHERE perm_code IN ('archive', 'archive:view', 'archive:add', 'archive:edit'); ```4.2 权限变更管理流程
当业务需求变化需要调整权限时,按以下步骤操作:
- 步骤1:在arc_permission表中新增权限资源,如添加`archive:audit`(档案审核)权限。
- 步骤2:在管理界面为特定角色分配新权限,通过勾选方式更新arc_role_permission表。
- 步骤3:前端页面添加对应的权限按钮,并使用v-permit指令控制:`v-permit="'archive:audit'"`。
- 步骤4:后端接口添加权限注解,在审核接口上添加`@PreAuthorize("hasAuthority('archive:audit')")`。
权限回收则反向操作,从arc_role_permission表中删除对应记录即可,用户再次登录时生效。
五、关键问题排查与优化建议
5.1 性能优化方案
权限数据频繁查询会拖慢系统,采用以下优化:
- 缓存用户权限数据:用户登录后,将其角色和权限数据存入Redis,设置24小时过期,Key格式为`user:perms:{userId}`。
- 部门路径冗余存储:在arc_department表中添加dept_path字段,存储如`.1.3.7.`的路径,避免递归查询子部门。
- 权限验证SQL优化:在数据权限过滤时,使用EXISTS代替IN子查询。
5.2 常见问题排查
- 问题1:按钮已配置权限但不显示。检查:F12查看网络请求,确认`/api/user/permissions`接口返回的buttonPerms列表是否包含该权限标识。
- 问题2:无权限用户访问了受保护接口。检查:确认接口方法上是否添加了`@PreAuthorize`注解,且注解内的权限标识与数据库perm_code一致。
- 问题3:数据权限过滤不生效。检查:在DataAuthAspect的buildWhereCondition方法中打印生成的SQL条件,核对部门ID和用户ID是否正确。
按照以上步骤完整实施,即可构建一个权限清晰、维护方便、安全可控的数字档案馆系统。所有代码块均可直接复制使用,只需根据实际表名和字段名做微小调整。