乐趣区

关于权限:终于有篇文章把后管权限系统设计讲清楚了

在罕用的后盾管理系统中,通常都会有权限零碎设计,以用于给对应人员调配不同权限,管制其对后管零碎中的某些菜单、按钮以及列表数据的可见性。

本文将用 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='角色和菜单关联表';

后管权限设计 ER 图

waynboot-mall 我的项目的后盾权限零碎设计相干 er 图如下,

权限调配

当咱们了解分明后权限模型后,就该进行具体的权限调配了。在进行权限调配时,咱们须要遵循以下几个准则:

  • 权限调配要正当:不同的角色应该领有与其职责相符合的权限,不应该给予过多或过少的权限。
  • 权限调配要灵便:不同的场景和需要可能须要调整权限配置,应该提供方便和快捷的形式来进行权限变更。
  • 权限调配要平安:权限变更应该有明确的审批和记录流程,防止因为权限谬误或滥用导致系统危险(也就是权限相干的操作须要有日志记录)。

在 waynboto-mall 我的项目中,咱们采纳了以下几种形式来进行权限调配:

用户角色调配

通过给用户调配角色来实现权限调配:这是最常见和最根本的形式,通过勾选用户领有的角色来管制用户领有的权限。

角色权限调配

通过给角色调配权限来实现权限调配:这是最灵便和最细粒度的形式,通过勾选角色领有的权限来管制角色领有的权限。

超管角色定义

通过设置超级管理员角色(role_key 为 admin)来实现全局权限治理:这是最简略和最高效的形式,通过设置一个超级管理员来管制所有功能模块和数据范畴的拜访和操作。


waynboot-mall 我的项目的访问控制框架是采纳的 Spring Security 3.0 版本。我在这里给大家先介绍 Spring Security 的相干常识以及 3.0 版本中的配置类代码编写。

Spring Security

一、什么是 Spring Security

Spring Security 是一个基于 Spring 框架的开源我的项目,旨在为 Java 应用程序提供弱小和灵便的安全性解决方案。Spring Security 提供了以下个性:

  • 认证:反对多种认证机制,如表单登录、HTTP 根本认证、OAuth2、OpenID 等。
  • 受权:反对基于角色或权限的访问控制,以及基于表达式的细粒度管制。
  • 防护:提供了多种防护措施,如避免会话固定、点击劫持、跨站申请伪造等攻打。
  • 集成:与 Spring 框架和其余第三方库和框架进行无缝集成,如 Spring MVC、Thymeleaf、Hibernate 等。

二、如何引入 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

在 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 配置类,

  • filterChain(HttpSecurity httpSecurity) 办法是访问控制的外围办法,这外面能够针对 url 设置是否须要权限认证、cors 配置、csrf 配置、用户信息加载配置、jwt 过滤器拦挡配置等泛滥性能。
  • authenticationManager(AuthenticationConfiguration authenticationConfiguration) 办法实用于启用认证接口,须要手动申明,否则启动报错。
  • bCryptPasswordEncoder() 办法用户定义用户登录时的明码加密策略,须要手动申明,否则启动报错。

四、如何应用 Spring Security

要应用 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
@AllArgsConstructor
public 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 即可取得。

如果感觉这篇文章写的不错的话,无妨点赞加关注,我会更新更多技术干货、我的项目教学、实战经验分享的文章。

退出移动版