在常用的后台管理系统中,通常都会有权限系统设计,以用于给对应人员分配不同权限,控制其对后管系统中的某些菜单、按钮以及列表数据的可见性。
本文将用 waynboot-mall 项目举例,给大家介绍常见后管系统的权限控制该如何设计。大纲如下,
要理解权限控制,我们需要先了解什么是权限模型。
权限模型是指用于描述用户、角色和权限之间关系的一种抽象模型。不同的权限模型有不同的优缺点,适用于不同的场景和需求。在本项目中,我们采用了 RBAC(Role-Based Access Control)模型,即基于角色的访问控制模型。
RBAC 模型的基本思想是将用户和权限分离,通过角色作为中间层来连接用户和权限。一个角色可以关联多个权限,一个用户可以拥有多个角色。这样可以实现灵活的权限配置和管理,避免直接给用户分配权限带来的复杂性和冗余性。
RBAC 模型有多个扩展版本,如 RBAC0、RBAC1、RBAC2 等。在本项目中,我们使用了 RBAC0 模型,即最基本的 RBAC 模型。RBAC0 模型包含三个要素:用户(User)、角色(Role)和权限(Permission)。用户是指使用系统的主体,角色是指一组相关的权限的集合,权限是指对系统资源的访问或操作能力。
在 waynboto-mall 项目中,RBAC0 中的权限对应的就是菜单。菜单权限包含菜单页面对用户是否可见、页面按钮对用户是否可见、页面列表数据根据用户进行过滤等。
在 RBAC0 模型中,我们需要对用户、角色和权限进行定义和梳理。具体来说,我们需要确定以下几个方面:
在 waynboto-mall 项目中,我做了以下的定义和梳理:
用户来源于系统内部注册或外部导入,用户有用户名、密码、姓名、手机号、邮箱等属性。表结构如下,
CREATE TABLE `sys_user` ( `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', `dept_id` bigint DEFAULT NULL COMMENT '部门ID', `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户账号', `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户昵称', `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '用户邮箱', `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '手机号码', `sex` tinyint DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', `avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '头像地址', `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '密码', `user_status` tinyint DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', `del_flag` tinyint(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', PRIMARY KEY (`user_id`) USING BTREE, UNIQUE KEY `user_name_uqi` (`user_name`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='用户信息表';
角色按照功能模块进行命名,如商品管理、订单管理、营销管理等。角色可以分为普通角色和超级管理员角色,普通角色可以拥有部分或全部功能模块的权限,超级管理员角色可以拥有所有功能模块的权限,并且可以管理其他用户和角色。表结构如下
CREATE TABLE `sys_role` ( `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', `role_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名称', `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色权限字符串', `sort` int NOT NULL COMMENT '显示顺序', `role_status` tinyint NOT NULL COMMENT '角色状态(0正常 1停用)', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', `del_flag` tinyint(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', PRIMARY KEY (`role_id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='角色信息表';
超级管理员也就是 rule_key 为 admin 角色,赋予了 admin 角色用户拥有系统的绝对控制能力。
权限分为页面权限、操作权限和数据权限。页面权限控制用户可以看到哪些页面或菜单,操作权限控制用户可以在页面上执行哪些操作或按钮,数据权限控制用户可以查看或修改哪些数据或范围。表结构如下,
CREATE TABLE `sys_menu` ( `menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID', `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '菜单名称', `parent_id` bigint DEFAULT '0' COMMENT '父菜单ID', `sort` int DEFAULT '0' COMMENT '显示顺序', `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '路由地址', `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '组件路径', `is_frame` tinyint DEFAULT '1' COMMENT '是否为外链(0是 1否)', `menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', `menu_status` tinyint DEFAULT NULL COMMENT '菜单状态(0启用 1禁用)', `visible` tinyint DEFAULT '0' COMMENT '显示状态(0显示 1隐藏)', `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '权限标识', `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '#' COMMENT '菜单图标', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '备注', PRIMARY KEY (`menu_id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=2055 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='菜单权限表';
用户与角色之间是多对多的关联方式,即一个用户可以拥有多个角色,一个角色可以分配给多个用户。角色与权限之间也是多对多的关联方式,即一个角色可以拥有多个权限,一个权限可以分配给多个角色。用户角色关联表、角色菜单关联表结构如下,
CREATE TABLE `sys_user_role` ( `user_id` bigint NOT NULL COMMENT '用户ID', `role_id` bigint NOT NULL COMMENT '角色ID', PRIMARY KEY (`user_id`,`role_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='用户和角色关联表';CREATE TABLE `sys_role_menu` ( `role_id` bigint NOT NULL COMMENT '角色ID', `menu_id` bigint NOT NULL COMMENT '菜单ID', PRIMARY KEY (`role_id`,`menu_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='角色和菜单关联表';
waynboot-mall 项目的后台权限系统设计相关 er 图如下,
当我们理解清楚后权限模型后,就该进行具体的权限分配了。在进行权限分配时,我们需要遵循以下几个原则:
在 waynboto-mall 项目中,我们采用了以下几种方式来进行权限分配:
通过给用户分配角色来实现权限分配:这是最常见和最基本的方式,通过勾选用户拥有的角色来控制用户拥有的权限。
通过给角色分配权限来实现权限分配:这是最灵活和最细粒度的方式,通过勾选角色拥有的权限来控制角色拥有的权限。
通过设置超级管理员角色(role_key 为 admin)来实现全局权限管理:这是最简单和最高效的方式,通过设置一个超级管理员来控制所有功能模块和数据范围的访问和操作。
waynboot-mall 项目的访问控制框架是采用的 Spring Security 3.0版本。我在这里给大家先介绍 Spring Security 的相关知识以及 3.0 版本中的配置类代码编写。
Spring Security 是一个基于 Spring 框架的开源项目,旨在为 Java 应用程序提供强大和灵活的安全性解决方案。Spring Security 提供了以下特性:
在 waynboot-mall 项目中直接引入 spring-boot-starter-security 依赖,
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>3.1.0</version> </dependency></dependencies>
在 Spring Security 3.0 中要配置 Spring Security 跟以往是有些不同的,比如不在继承 WebSecurityConfigurerAdapter。在 waynboot-mall 项目中,具体配置如下,
@Configuration@EnableWebSecurity@AllArgsConstructor@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)public class SecurityConfig { private UserDetailsServiceImpl userDetailsService; private AuthenticationEntryPointImpl unauthorizedHandler; private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; private LogoutSuccessHandlerImpl logoutSuccessHandler; @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity // cors启用 .cors(httpSecurityCorsConfigurer -> {}) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(httpSecuritySessionManagementConfigurer -> { httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS); }) .exceptionHandling(httpSecurityExceptionHandlingConfigurer -> { httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(unauthorizedHandler); }) // 过滤请求 .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> { authorizationManagerRequestMatcherRegistry .requestMatchers("/favicon.ico", "/login", "/favicon.ico", "/actuator/**").anonymous() .requestMatchers("/slider/**").anonymous() .requestMatchers("/captcha/**").anonymous() .requestMatchers("/upload/**").anonymous() .requestMatchers("/common/download**").anonymous() .requestMatchers("/doc.html").anonymous() .requestMatchers("/swagger-ui/**").anonymous() .requestMatchers("/swagger-resources/**").anonymous() .requestMatchers("/webjars/**").anonymous() .requestMatchers("/*/api-docs").anonymous() .requestMatchers("/druid/**").anonymous() .requestMatchers("/elastic/**").anonymous() .requestMatchers("/message/**").anonymous() .requestMatchers("/ws/**").anonymous() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); }) .headers(httpSecurityHeadersConfigurer -> { httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable); }); // 处理跨域请求中的Preflight请求(cors),设置corsConfigurationSource后无需使用 // .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() // 对于登录login 验证码captchaImage 允许匿名访问 httpSecurity.logout(httpSecurityLogoutConfigurer -> { httpSecurityLogoutConfigurer.logoutUrl("/logout"); httpSecurityLogoutConfigurer.logoutSuccessHandler(logoutSuccessHandler); }); // 添加JWT filter httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 认证用户时用户信息加载配置,注入springAuthUserService httpSecurity.userDetailsService(userDetailsService); return httpSecurity.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } /** * 强散列哈希加密实现 */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); }}
这里详细介绍下 SecurityConfig 配置类,
要使用 Spring Security,只需要在需要控制访问权限的方法或类上添加相应的 @PreAuthorize 注解即可,如下,
@Slf4j@RestController@AllArgsConstructor@RequestMapping("system/role")public class RoleController extends BaseController { private IRoleService iRoleService; @PreAuthorize("@ss.hasPermi('system:role:list')") @GetMapping("/list") public R list(Role role) { Page<Role> page = getPage(); return R.success().add("page", iRoleService.listPage(page, role)); }}
我们在 list 方法上加了 @PreAuthorize("@ss.hasPermi('system:role:list')") 注解表示当前登录用户拥有 system:role:list 权限才能访问 list 方法,否则返回权限错误。
在 SecurityConfig 配置类中我们定义了 UserDetailsServiceImpl 作为我们的用户信息加载的实现类,从而通过读取数据库中用户的账号、密码与前端传入的账号、密码进行比对。代码如下,
@Slf4j@Service@AllArgsConstructorpublic class UserDetailsServiceImpl implements UserDetailsService { private IUserService iUserService; private IDeptService iDeptService; private PermissionService permissionService; public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); System.out.println(bCryptPasswordEncoder.encode("123456")); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1. 读取数据库中当前用户信息 User user = iUserService.getOne(new QueryWrapper<User>().eq("user_name", username)); // 2. 判断该用户是否存在 if (user == null) { log.info("登录用户:{} 不存在.", username); throw new UsernameNotFoundException("登录用户:" + username + " 不存在"); } // 3. 判断是否禁用 if (Objects.equals(UserStatusEnum.DISABLE.getCode(), user.getUserStatus())) { log.info("登录用户:{} 已经被停用.", username); throw new DisabledException("登录用户:" + username + " 不存在"); } user.setDept(iDeptService.getById(user.getDeptId())); // 4. 获取当前用户的角色信息 Set<String> rolePermission = permissionService.getRolePermission(user); // 5. 根据角色获取权限信息 Set<String> menuPermission = permissionService.getMenuPermission(rolePermission); return new LoginUserDetail(user, menuPermission); }}
针对 UserDetailsServiceImpl 的代码逻辑进行一个讲解。
本文给大家讲解了常见后管系统的权限控制系统该如何设计以及 Spring Security 实战,在常用的 RBAC0 权限模型下,权限要素包含用户、角色、权限(菜单)三要素,只要大家能理解用户、角色、权限(菜单)三要素的设计理念以及表结构后,相信就能轻松掌握后管权限控制系统的设计精髓。
想要获取 waynboot-mall 项目源码的同学,可以关注我公众号【程序员wayn】,回复 waynboot-mall 即可获得。
本文链接:http://www.28at.com/showinfo-26-75341-0.html终于有篇文章把后管权限系统设计讲清楚了
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
下一篇: FFmpeg前端视频合成实践