目录

1、前言

2、实现思路

3、编码实战

3.1、准备

3.2、数据库表准备

3.3、自定义注解

3.4、拦截器

3.5、接口使用

3.6、测试

3.7、结论

4、结束语


1、前言

学过Spring Security的小伙伴都知道,SpringBoot项目可以集成Spring Security做权限校验框架,然后在Controller接口上直接使用@PreAuthorize注解来校验权限,但是如果我不想引入像Security、Shiro等第三方框架,也要实现权限校验的效果,该怎么做呢?

接下来就给大家介绍一种方案:拦截器+自定义注解做基于RBAC模型的权限校验

2、实现思路

  1. 首先数据库需要有基于RBAC模型的表,我们这里用最简单的5张表做示例:用户表、角色表、权限表、用户角色关联表、角色权限关联表
  2. 用户每次登录成功后,都需要联表查询该用户拥有的权限集合字符串,加密到token串中(此处可以使用JWT),返回给前端,前端存储到浏览器中,以后每次请求后台接口时,都将此token取出,放到请求头信息中,带到后台
  3. 然后新建自定义注解,该注解用于接口上面,表示该接口需要校验访问者的权限
  4. 新建拦截器,对所有请求方法的请求进行拦截,然后判断是否有自定义注解,如果有的话,取出权限字符串,和请求头中token的权限字符串集合进行比对,如果包含其中,则说明有权限访问该方法,如果不包含其中,就说明该用户没有权限访问该方法

带有用户权限的springboot项目 springboot权限管理rbac_java

3、编码实战

3.1、准备

这里我准备了一个SpringBoot基础环境代码,我会在此基础上进行集成,代码我已上传,地址如下:

SpringBootBase: SpringBoot基础项目框架

基础薄弱的同学,可以下载下来,跟我下面的步骤一步一步走,就可以出来效果了,再集成到你自己的项目中

3.2、数据库表准备

5张表准备:

用户表:

CREATE TABLE `sys_user` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
  `position_id` varchar(5) DEFAULT NULL COMMENT '职位ID',
  `class_id` bigint(20) DEFAULT NULL COMMENT '班级ID',
  `user_name` varchar(30) NOT NULL COMMENT '用户账号',
  `nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
  `user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户)',
  `email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
  `phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
  `sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
  `avatar` varchar(300) DEFAULT '' COMMENT '头像地址',
  `password` varchar(100) DEFAULT '' COMMENT '密码',
  `status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
  `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
  `login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
  `login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `un_user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';

角色表:

CREATE TABLE `sys_role` (
  `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(30) NOT NULL COMMENT '角色名称',
  `role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串',
  `role_sort` int(4) DEFAULT NULL COMMENT '显示顺序',
  `status` char(1) NOT NULL DEFAULT '0' COMMENT '角色状态(0正常 1停用)',
  `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色信息表';

权限表:

CREATE TABLE `sys_menu` (
  `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `menu_name` varchar(50) NOT NULL COMMENT '菜单名称',
  `parent_id` bigint(20) DEFAULT '0' COMMENT '父菜单ID',
  `order_num` int(4) DEFAULT '0' COMMENT '显示顺序',
  `path` varchar(200) DEFAULT '' COMMENT 'menu_id全路径',
  `component` varchar(255) DEFAULT NULL COMMENT '组件路径',
  `query` varchar(255) DEFAULT NULL COMMENT '路由参数',
  `is_frame` int(1) DEFAULT '1' COMMENT '是否为外链(0是 1否)',
  `is_cache` int(1) DEFAULT '0' COMMENT '是否缓存(0缓存 1不缓存)',
  `menu_type` char(1) DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
  `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  `perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT '' COMMENT '备注',
  PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='菜单权限表';

用户角色关联表:

CREATE TABLE `sys_user_role` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户和角色关联表';

角色权限关联表:

CREATE TABLE `sys_role_menu` (
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  `menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
  PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色和菜单关联表';

3表联查,传入用户ID就可以查询出该用户拥有哪些权限字符串了,SQL如下:  

select * from sys_menu t1
left join sys_role_menu t2 on t1.menu_id = t2.menu_id
left join sys_user_role t3 on t2.role_id = t3.role_id
where t3.user_id = #{userId}

3.3、自定义注解

新建一个自定义注解:

package org.wujiangbo.annotation;

import java.lang.annotation.*;

/**
 * 自定义注解校验权限
 */
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPermission {

    /**
     * 权限字符串
     */
    String per() default "";
}

3.4、拦截器

新建拦截器,代码如下:

package org.wujiangbo.interceptor;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.wujiangbo.annotation.CheckPermission;
import org.wujiangbo.exception.MyException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 权限校验拦截器
 */
@Component
public class CheckPermissionInterceptor implements HandlerInterceptor {

    /**
     * 前置处理
     * 该方法将在请求处理之前进行调用
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
        if (!(handler instanceof HandlerMethod)) {
            //说明拦截到的请求,不是请求方法的,那就直接放行
            return true;
        }
        //指定到这里,说明请求的是方法
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        //获取方法对象
        Method method = handlerMethod.getMethod();
        //获取方法上面的 CheckPermission 注解对象
        CheckPermission methodAnnotation = method.getAnnotation(CheckPermission.class);
        if (methodAnnotation != null) {
            //获取权限字符串
            String per = methodAnnotation.per();
            if(StringUtils.isNotBlank(per)){
                /**
                 * 说明需要校验权限,拥有该权限字符串,才能访问该方法
                 * 此时,需要从request头信息中拿到token,然后解密token,得到用户的权限集合,然后再做判断即可
                 */
                //这里我们造点测试数据,假设从token中解析出当前登录用户的权限集合如下
                List<String> userPermissionList = new ArrayList<>();
                userPermissionList.add("user:addUser");
                userPermissionList.add("user:deleteUser");
                userPermissionList.add("user:updateUser");
                userPermissionList.add("user:pageList");

                //开始判断
                if(!userPermissionList.contains(per)){
                    throw new MyException("您暂无权限进行此操作");
                }
            }
        }
        //必须返回true,否则会拦截掉所有请求,不会执行controller方法中的内容了
        return true;
    }
}

需要将上面的拦截器类加入到web环境中,新建下面配置类:

package org.wujiangbo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.wujiangbo.interceptor.CheckPermissionInterceptor;

/**
 * 统一拦截器配置类
 */
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    @Autowired
    private CheckPermissionInterceptor checkPermissionInterceptor;

    //添加自定义拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(checkPermissionInterceptor).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

注意:

我这里的拦截器中,没有对token进行解析,只是模拟了一下,实际项目中,token肯定是密文,无法直接使用的,需要先解析出用户拥有的权限集合字符串

3.5、接口使用

下面我们再controller接口中就可以直接使用了,如下:

package org.wujiangbo.controller;

import lombok.extern.slf4j.Slf4j;
import org.wujiangbo.annotation.CheckPermission;
import org.wujiangbo.result.JSONResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试接口类
 */
@RestController
@Slf4j
public class TestController {

    //用户新增接口
    @GetMapping("/user/addUser")
    @CheckPermission(per = "user:addUser") //表示访问者需要具备 user:addUser 权限才能访问该接口
    public JSONResult addUser(){
        return JSONResult.success("addUser success");
    }

    //部门新增接口
    @GetMapping("/dept/addDept")
    @CheckPermission(per = "dept:addDept") //表示访问者需要具备 dept:addDept 权限才能访问该接口
    public JSONResult addDept(){
        return JSONResult.success("addDept success");
    }

}

3.6、测试

用postman工具测试,先访问新增用户接口:http://localhost:8001/user/addUser

带有用户权限的springboot项目 springboot权限管理rbac_spring_02

再访问新增部门接口:http://localhost:8001/dept/addDept

带有用户权限的springboot项目 springboot权限管理rbac_java_03

3.7、结论

从测试结果可以看出,确实可以做到接口的权限校验,以后使用的话,可以直接在controller加个注解就可以了,非常的方便

4、结束语

  • 这样的权限校验在实际工作中也有应用场景,希望大家都可以掌握哦
  • 大家如果还有任何疑问,可以留言,我会第一时间回复的