首页 > 编程语言 > 详细

(四)Spring Security 动态url权限控制

时间:2021-01-04 11:48:53      阅读:43      评论:0      收藏:0      [点我收藏+]
表关系简介:
  1. 用户表t_sys_user 关联 角色表t_sys_role 两者建立中间关系表t_sys_user_role
  2. 角色表t_sys_role 关联 权限表t_sys_permission 两者建立中间关系表t_sys_role_permission
  3. 最终体现效果为当前登录用户所具备的角色关联能访问的所有url,只要给角色分配相应的url权限即可
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_sys_log
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_log`;
CREATE TABLE `t_sys_log`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 主键ID,
  `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 接口名称,
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 接口地址,
  `ip` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 访问人IP,
  `user_id` int(11) DEFAULT 0 COMMENT 访问人ID 0:未登录用户操作,
  `status` int(2) DEFAULT 1 COMMENT 访问状态,
  `execute_time` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 接口执行时间,
  `gmt_create` datetime(0) DEFAULT NULL COMMENT 创建时间,
  `gmt_modified` datetime(0) DEFAULT NULL COMMENT 更新时间,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1078 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 系统管理 - 日志表 ROW_FORMAT = Compact;

-- ----------------------------
-- Table structure for t_sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_menu`;
CREATE TABLE `t_sys_menu`  (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 主键,
  `parent_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 上级资源ID,
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT url,
  `resources` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 资源编码,
  `title` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 资源名称,
  `level` int(11) DEFAULT NULL COMMENT 资源级别,
  `sort_no` int(11) DEFAULT NULL COMMENT 排序,
  `icon` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 资源图标,
  `type` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 类型 menu、button,
  `remarks` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 备注,
  `gmt_create` datetime(0) DEFAULT NULL COMMENT 创建时间,
  `gmt_modified` datetime(0) DEFAULT NULL COMMENT 更新时间,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 93 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 系统管理-权限资源表  ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_menu
-- ----------------------------
INSERT INTO `t_sys_menu` VALUES (1, 0, NULL, systemManage, 系统管理, 1, 3, component, menu, ‘‘, 2019-03-28 18:51:08, 2019-03-28 18:51:10);
INSERT INTO `t_sys_menu` VALUES (2, 1, /system/user/listPage, user, 用户管理, 2, 1, my-user, menu, ‘‘, 2019-03-28 18:52:13, 2019-08-31 21:26:57);
INSERT INTO `t_sys_menu` VALUES (3, 2, /system/user/save, sys:user:add, 添加, 3, 1, el-icon-edit, button, ‘‘, 2019-03-28 18:53:31, 2019-04-01 20:19:55);
INSERT INTO `t_sys_menu` VALUES (4, 2, /system/user/save, sys:user:edit, 编辑, 3, 2, NULL, button, ‘‘, 2019-03-28 18:54:26, 2019-04-01 20:20:16);
INSERT INTO `t_sys_menu` VALUES (5, 2, /system/user/delete, sys:user:delete, 删除, 3, 3, NULL, button, ‘‘, 2019-03-28 18:55:25, 2019-04-01 20:20:09);
INSERT INTO `t_sys_menu` VALUES (16, 1, /system/role/listPage, role, 角色管理, 2, 2, my-role, menu, ‘‘, 2019-03-30 14:00:03, 2019-03-30 14:20:59);
INSERT INTO `t_sys_menu` VALUES (17, 1, /system/menu/treeMenu, menu, 菜单管理, 2, 3, my-sysmenu, menu, ‘‘, 2019-03-30 14:00:53, 2019-03-30 14:21:10);
INSERT INTO `t_sys_menu` VALUES (43, 16, /system/role/saveOrUpdate, sys:role:add, 添加, 3, 1, ‘‘, button, ‘‘, 2019-04-01 20:20:46, 2019-04-01 20:20:46);
INSERT INTO `t_sys_menu` VALUES (44, 16, /system/role/saveOrUpdate, sys:role:edit, 编辑, 3, 2, ‘‘, button, ‘‘, 2019-04-01 20:21:03, 2019-04-01 20:21:03);
INSERT INTO `t_sys_menu` VALUES (45, 16, NULL, roleSetting, 权限设置, 3, 3, ‘‘, button, ‘‘, 2019-04-01 20:21:24, 2019-04-01 20:21:24);
INSERT INTO `t_sys_menu` VALUES (46, 16, /system/role/delete, sys:role:delete, 删除, 3, 4, ‘‘, button, ‘‘, 2019-04-01 20:21:55, 2019-04-01 20:21:55);
INSERT INTO `t_sys_menu` VALUES (47, 17, /system/menu/save, sys:menu:add, 添加, 3, 1, ‘‘, button, ‘‘, 2019-04-01 20:22:31, 2019-04-01 20:22:31);
INSERT INTO `t_sys_menu` VALUES (48, 17, /system/menu/save, sys:menu:addsub, 添加下级, 3, 2, ‘‘, button, ‘‘, 2019-04-01 20:23:00, 2019-04-01 20:23:00);
INSERT INTO `t_sys_menu` VALUES (49, 17, /system/menu/save, sys:menu:edit, 编辑, 3, 3, ‘‘, button, ‘‘, 2019-04-01 20:23:28, 2019-04-01 20:23:28);
INSERT INTO `t_sys_menu` VALUES (50, 17, /system/menu/delete, sys:menu:delete, 删除, 3, 4, ‘‘, button, ‘‘, 2019-04-01 20:23:46, 2019-04-01 20:23:46);
INSERT INTO `t_sys_menu` VALUES (79, 1, /system/log/listPage, log, 系统日志, 2, 4, my-sysmenu, menu, ‘‘, 2019-03-30 14:00:53, 2019-09-18 14:21:38);
INSERT INTO `t_sys_menu` VALUES (80, 16, /system/user/treeUser, sys:user:treeUser, 获取用户树, 3, 5, NULL, menu, NULL, 2019-10-20 14:33:37, 2019-10-20 14:33:37);
INSERT INTO `t_sys_menu` VALUES (81, 16, /system/role/detail, sys:role:detail, 获取角色详情, 3, 6, NULL, menu, NULL, 2019-10-20 14:34:59, 2019-10-20 14:34:59);
INSERT INTO `t_sys_menu` VALUES (82, 16, /system/userRole/list, sys:userRole:list, 获取系统管理 - 用户角色关联表 列表, 3, 7, NULL, menu, NULL, 2019-10-20 14:35:53, 2019-10-20 14:35:53);
INSERT INTO `t_sys_menu` VALUES (83, 17, /system/menu/treeMenu, sys:menu:treeMenu, 获取菜单树, 3, 5, NULL, menu, NULL, 2019-10-20 14:36:33, 2019-10-20 14:36:33);
INSERT INTO `t_sys_menu` VALUES (84, 2, /system/roleMenu/list, sys:roleMenu:list, 获取系统管理 - 角色-菜单关联表 列表, 3, 4, NULL, menu, NULL, 2019-10-20 14:39:37, 2019-10-20 14:39:37);
INSERT INTO `t_sys_menu` VALUES (85, 17, /system/roleMenu/saveRoleMenu, sys:roleMenu:saveRoleMenu, 保存角色相关联菜单, 3, 6, NULL, button, NULL, 2019-10-20 14:42:12, 2019-10-20 14:42:12);

-- ----------------------------
-- Table structure for t_sys_role
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_role`;
CREATE TABLE `t_sys_role`  (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 主键ID,
  `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 角色编码,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 角色名称,
  `remarks` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 角色描述,
  `gmt_create` datetime(0) DEFAULT NULL COMMENT 创建时间,
  `gmt_modified` datetime(0) DEFAULT NULL COMMENT 更新时间,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 系统管理-角色表  ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_role
-- ----------------------------
INSERT INTO `t_sys_role` VALUES (1, admin, 系统管理员, 系统管理员, 2019-03-28 15:51:56, 2019-03-28 15:51:59);
INSERT INTO `t_sys_role` VALUES (2, visitor, 访客, 访客, 2019-03-28 20:17:04, 2019-09-09 16:32:15);

-- ----------------------------
-- Table structure for t_sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_role_menu`;
CREATE TABLE `t_sys_role_menu`  (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 主键,
  `role_id` int(10) DEFAULT NULL COMMENT 角色ID,
  `menu_id` int(10) DEFAULT NULL COMMENT 菜单ID,
  `gmt_create` datetime(0) DEFAULT NULL COMMENT 创建时间,
  `gmt_modified` datetime(0) DEFAULT NULL COMMENT 更新时间,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1636 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 系统管理 - 角色-权限资源关联表  ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_role_menu
-- ----------------------------
INSERT INTO `t_sys_role_menu` VALUES (1571, 1, 1, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1572, 1, 2, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1573, 1, 3, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1574, 1, 4, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1575, 1, 5, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1576, 1, 84, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1577, 1, 16, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1578, 1, 43, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1579, 1, 44, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1580, 1, 45, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1581, 1, 46, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1582, 1, 80, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1583, 1, 81, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1584, 1, 82, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1585, 1, 17, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1586, 1, 47, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1587, 1, 48, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1588, 1, 49, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1589, 1, 50, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1590, 1, 83, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1591, 1, 85, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1592, 1, 79, 2019-10-20 14:44:12, 2019-10-20 14:44:12);
INSERT INTO `t_sys_role_menu` VALUES (1615, 2, 1, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1616, 2, 2, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1617, 2, 3, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1618, 2, 4, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1619, 2, 5, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1620, 2, 84, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1621, 2, 16, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1622, 2, 43, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1623, 2, 44, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1624, 2, 45, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1625, 2, 46, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1626, 2, 80, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1627, 2, 81, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1628, 2, 82, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1629, 2, 17, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1630, 2, 47, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1631, 2, 48, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1632, 2, 49, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1633, 2, 50, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1634, 2, 83, 2019-10-22 15:11:37, 2019-10-22 15:11:37);
INSERT INTO `t_sys_role_menu` VALUES (1635, 2, 85, 2019-10-22 15:11:37, 2019-10-22 15:11:37);

-- ----------------------------
-- Table structure for t_sys_user
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_user`;
CREATE TABLE `t_sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 主键ID,
  `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 账号,
  `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 登录密码,
  `nick_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 昵称,
  `sex` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 性别 0:男 1:女,
  `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 手机号码,
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 邮箱,
  `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 头像,
  `flag` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 状态,
  `salt` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 盐值,
  `token` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT token,
  `qq_oppen_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT QQ 第三方登录Oppen_ID唯一标识,
  `pwd` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 明文密码,
  `gmt_create` datetime(0) DEFAULT NULL COMMENT 创建时间,
  `gmt_modified` datetime(0) DEFAULT NULL COMMENT 更新时间,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 系统管理-用户基础信息表 ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_user
-- ----------------------------
INSERT INTO `t_sys_user` VALUES (1, admin, 97ba1ef7f148b2aec1c61303a7d88d0967825495, 郑清, 0, 15183303003, 10086@qq.com, http://qzapp.qlogo.cn/qzapp/101536330/86F96F92387D69BD7659C4EC3CD6BD69/100, 1, zhengqing, f78a977744d587b335d611f23fa25d8fd1352df6, ‘‘, 123456, 2019-05-05 16:09:06, 2019-10-23 17:26:30);
INSERT INTO `t_sys_user` VALUES (2, test, 97ba1ef7f148b2aec1c61303a7d88d0967825495, 测试号, 0, 10000, 10000@qq.com, https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif, 1, zhengqing, 2425fb04b4bcb140e05d22d46baa9c257ceed879, NULL, 123456, 2019-05-05 16:15:06, 2019-10-23 16:56:38);

-- ----------------------------
-- Table structure for t_sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_user_role`;
CREATE TABLE `t_sys_user_role`  (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 主键,
  `role_id` int(10) DEFAULT NULL COMMENT 角色ID,
  `user_id` int(10) DEFAULT NULL COMMENT 用户ID,
  `gmt_create` datetime(0) DEFAULT NULL COMMENT 创建时间,
  `gmt_modified` datetime(0) DEFAULT NULL COMMENT 更新时间,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 系统管理 - 用户角色关联表  ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_sys_user_role
-- ----------------------------
INSERT INTO `t_sys_user_role` VALUES (12, 1, 1, 2019-08-21 10:49:41, 2019-08-21 10:49:41);
INSERT INTO `t_sys_user_role` VALUES (27, 2, 2, 2019-09-07 21:50:33, 2019-09-07 21:50:33);

SET FOREIGN_KEY_CHECKS = 1;

 

 按照惯例先发个项目结构

技术分享图片

<properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Spring Security依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- mybatis-plus begin =================================== -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!-- mybatis-plus end -->

        <!-- ========================= 数据库相关 ========================== -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- 阿里数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.18</version>
        </dependency>

        <!-- swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-bean-validators</artifactId>
            <version>2.6.1</version>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 阿里FastJson转换工具依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.13</version>
        </dependency>

        <!-- AOP依赖 【注:系统日记需要此依赖】 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- Hibernate Validator提供的注解进行参数校验 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.18.Final</version>
        </dependency>

        <!-- StringUtils工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.6</version>
        </dependency>

        <!-- jwt依赖: https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!-- 模板引擎 -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

Spring Security 动态权限控制

1、未登录访问权限控制

自定义AdminAuthenticationEntryPoint类实现AuthenticationEntryPoint

这里是认证权限入口 -> 即在未登录的情况下访问所有接口都会拦截到此(除了放行忽略接口)

import cn.com.sercurity.cyy.common.dto.ApiResult;
import cn.com.sercurity.cyy.common.util.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p> 认证权限入口 - 未登录的情况下访问所有接口都会拦截到此 </p>
 *
 * @author :
 * @description : 前后端分离情况下返回json格式数据
 * @date : 2020/12/31 13:35
 */
@Slf4j
@Component
public class AdminAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        ResponseUtils.out(response, ApiResult.fail("未登录!!!"));
    }
}

注:ResponseUtilsApiResult,在这里是模拟前后端分离情况下返回json格式数据所使用工具类,在上一篇文章中有记录

2、自定义过滤器MyAuthenticationFilter继承OncePerRequestFilter实现访问鉴权

每次访问接口都会经过此,我们可以在这里记录请求参数、响应内容,或者处理前后端分离情况下,以token换用户权限信息,token是否过期,请求头类型是否正确,防止非法请求等等

  1. logRequestBody()方法:记录请求消息体
  2. logResponseBody()方法:记录响应消息体

【注:请求的HttpServletRequest流只能读一次,下一次就不能读取了,因此这里要使用自定义的MultiReadHttpServletRequest工具解决流只能读一次的问题,响应同理】

import cn.com.sercurity.cyy.common.util.Constants;
import cn.com.sercurity.cyy.common.util.MultiReadHttpServletRequest;
import cn.com.sercurity.cyy.common.util.MultiReadHttpServletResponse;
import cn.com.sercurity.cyy.config.security.dto.SecurityUser;
import cn.com.sercurity.cyy.config.security.service.impl.UserDetailsServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * <p> 访问鉴权 - 每次访问接口都会经过此 </p>
 *
 * @author :
 * @description :
 * @date : 2020/12/31 15:34
 */
@Slf4j
@Component
public class MyAuthenticationFilter extends OncePerRequestFilter {

    private final UserDetailsServiceImpl userDetailsService;

    protected MyAuthenticationFilter(UserDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("请求头类型: " + request.getContentType());
        if ((request.getContentType() == null && request.getContentLength() > 0) || (request.getContentType() != null && !request.getContentType().contains(Constants.REQUEST_HEADERS_CONTENT_TYPE))) {
            filterChain.doFilter(request, response);
            return;
        }

        MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);
        MultiReadHttpServletResponse wrappedResponse = new MultiReadHttpServletResponse(response);
        StopWatch stopWatch = new StopWatch();
        try {
            stopWatch.start();
            // 记录请求的消息体
            logRequestBody(wrappedRequest);

//            String token = "123";
            // 前后端分离情况下,前端登录后将token储存在cookie中,每次访问接口时通过token去拿用户权限
            String token = wrappedRequest.getHeader(Constants.REQUEST_HEADER);
            log.debug("后台检查令牌:{}", token);
            if (StringUtils.isNotBlank(token)) {
                // 检查token
                SecurityUser securityUser = userDetailsService.getUserByToken(token);
                if (securityUser == null || securityUser.getCurrentUserInfo() == null) {
                    throw new AccessDeniedException("TOKEN已过期,请重新登录!");
                }
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
                // 全局注入角色权限信息和登录用户基本信息
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            stopWatch.stop();
            long usedTimes = stopWatch.getTotalTimeMillis();
            // 记录响应的消息体
            logResponseBody(wrappedRequest, wrappedResponse, usedTimes);
        }

    }

    private String logRequestBody(MultiReadHttpServletRequest request) {
        MultiReadHttpServletRequest wrapper = request;
        if (wrapper != null) {
            try {
                String bodyJson = wrapper.getBodyJsonStrByJson(request);
                String url = wrapper.getRequestURI().replace("//", "/");
                System.out.println("-------------------------------- 请求url: " + url + " --------------------------------");
                Constants.URL_MAPPING_MAP.put(url, url);
                log.info("`{}` 接收到的参数: {}",url , bodyJson);
                return bodyJson;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private void logResponseBody(MultiReadHttpServletRequest request, MultiReadHttpServletResponse response, long useTime) {
        MultiReadHttpServletResponse wrapper = response;
        if (wrapper != null) {
            byte[] buf = wrapper.getBody();
            if (buf.length > 0) {
                String payload;
                try {
                    payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
                } catch (UnsupportedEncodingException ex) {
                    payload = "[unknown]";
                }
                log.info("`{}`  耗时:{}ms  返回的参数: {}", Constants.URL_MAPPING_MAP.get(request.getRequestURI()), useTime, payload);
            }
        }
    }
}
import lombok.AllArgsConstructor;
import lombok.Data;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

/**
 * <p> 多次读写BODY用HTTP RESPONSE - 解决流只能读一次问题 </p>
 *
 * @author : 
 * @description :
 * @date 2020/12/31 14:19
 */
public class MultiReadHttpServletResponse extends HttpServletResponseWrapper {

    private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    private HttpServletResponse response;

    public MultiReadHttpServletResponse(HttpServletResponse response) {
        super(response);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        this.response = response;
    }

    public byte[] getBody() {
        return byteArrayOutputStream.toByteArray();
    }

    @Override
    public ServletOutputStream getOutputStream() {
        return new ServletOutputStreamWrapper(this.byteArrayOutputStream, this.response);
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return new PrintWriter(new OutputStreamWriter(getOutputStream(), this.response.getCharacterEncoding()));
    }


    @Data
    @AllArgsConstructor
    private static class ServletOutputStreamWrapper extends ServletOutputStream {

        private ByteArrayOutputStream outputStream;
        private HttpServletResponse response;

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setWriteListener(WriteListener listener) {

        }

        @Override
        public void write(int b) throws IOException {
            this.outputStream.write(b);
        }

        @Override
        public void flush() throws IOException {
            if (!this.response.isCommitted()) {
                byte[] body = this.outputStream.toByteArray();
                ServletOutputStream outputStream = this.response.getOutputStream();
                outputStream.write(body);
                outputStream.flush();
            }
        }
    }
}

3、自定义UserDetailsServiceImpl实现UserDetailsService 和 自定义SecurityUser实现UserDetails 认证用户详情

这个在上一篇文章中也提及过,但上次未做角色权限处理,这次让我们来一起加上吧。

import cn.com.sercurity.cyy.config.security.dto.SecurityUser;
import cn.com.sercurity.cyy.role.entity.Role;
import cn.com.sercurity.cyy.role.mapper.RoleMapper;
import cn.com.sercurity.cyy.user.entity.User;
import cn.com.sercurity.cyy.user.mapper.UserMapper;
import cn.com.sercurity.cyy.userRole.entity.UserRole;
import cn.com.sercurity.cyy.userRole.mapper.UserRoleMapper;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.LinkedList;
import java.util.List;

/**
 * <p> 自定义userDetailsService - 认证用户详情 </p>
 *
 * @author :
 * @description :
 * @date 2020/12/30 14:13
 */
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;

    /***
     * 根据账号获取用户信息
     * @param username:
     * @return: org.springframework.security.core.userdetails.UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库中取出用户信息
        List<User> userList = userMapper.selectList(new EntityWrapper<User>().eq("username", username));
        User user;
        // 判断用户是否存在
        if (!CollectionUtils.isEmpty(userList)) {
            user = userList.get(0);
        } else {
            throw new UsernameNotFoundException("用户名不存在!");
        }
        // 返回UserDetails实现类
        return new SecurityUser(user, getUserRoles(user.getId()));
    }

    /***
     * 根据token获取用户权限与基本信息
     *
     * @param token:
     * @return: cn.com.sercurity.cyy.config.security.dto.SecurityUser
     */
    public SecurityUser getUserByToken(String token) {
        User user = null;
        List<User> loginList = userMapper.selectList(new EntityWrapper<User>().eq("token", token));
        if (!CollectionUtils.isEmpty(loginList)) {
            user = loginList.get(0);
        }
        return user != null ? new SecurityUser(user, getUserRoles(user.getId())) : null;
    }

    /**
     * 根据用户id获取角色权限信息
     *
     * @param userId
     * @return
     */
    private List<Role> getUserRoles(Integer userId) {
        List<UserRole> userRoles = userRoleMapper.selectList(new EntityWrapper<UserRole>().eq("user_id", userId));
        List<Role> roleList = new LinkedList<>();
        for (UserRole userRole : userRoles) {
            Role role = roleMapper.selectById(userRole.getRoleId());
            roleList.add(role);
        }
        return roleList;
    }
}
import com.baomidou.mybatisplus.enums.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableName;
import java.io.Serializable;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * <p>
 * 系统管理-角色表 
 * </p>
 *
 * @author
 * @since 2020-12-31
 */
@Data
@Accessors(chain = true)
@TableName("t_sys_role")
public class Role extends Model<Role> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 角色编码
     */
    private String code;

    /**
     * 角色名称
     */
    private String name;

    /**
     * 角色描述
     */
    private String remarks;

    /**
     * 创建时间
     */
    @TableField("gmt_create")
    private Date gmtCreate;

    /**
     * 更新时间
     */
    @TableField("gmt_modified")
    private Date gmtModified;


    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}
import com.baomidou.mybatisplus.enums.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableName;
import java.io.Serializable;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * <p>
 * 系统管理 - 用户角色关联表 
 * </p>
 *
 * @author
 * @since 2020-12-31
 */
@Data
@Accessors(chain = true)
@TableName("t_sys_user_role")
public class UserRole extends Model<UserRole> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 角色ID
     */
    @TableField("role_id")
    private Integer roleId;

    /**
     * 用户ID
     */
    @TableField("user_id")
    private Integer userId;

    /**
     * 创建时间
     */
    @TableField("gmt_create")
    private Date gmtCreate;

    /**
     * 更新时间
     */
    @TableField("gmt_modified")
    private Date gmtModified;


    @Override
    protected Serializable pkVal() {
        return this.id;
    }

}

这里再说下自定义SecurityUser是因为Spring Security自带的 UserDetails (存储当前用户基本信息) 有时候可能不满足我们的需求,因此我们可以自己定义一个来扩展我们的需求

getAuthorities()方法:即授予当前用户角色权限信息

import cn.com.sercurity.cyy.role.entity.Role;
import cn.com.sercurity.cyy.user.entity.User;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * <p> 安全认证用户详情 </p>
 *
 * @author :
 * @description :
 * @date 2020/12/30 14:13
 */
@Data
@Slf4j
public class SecurityUser implements UserDetails {
    /**
     * 当前登录用户
     */
    private transient User currentUserInfo;
    /**
     * 角色
     */
    private transient List<Role> roleList;

    public SecurityUser() { }

    public SecurityUser(User user) {
        if (user != null) {
            this.currentUserInfo = user;
        }
    }

    public SecurityUser(User user, List<Role> roleList) {
        if (user != null) {
            this.currentUserInfo = user;
            this.roleList = roleList;
        }
    }

    /**
     * 获取当前用户所具有的角色
     *
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        if (!CollectionUtils.isEmpty(this.roleList)) {
            for (Role role : this.roleList) {
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getCode());
                authorities.add(authority);
            }
        }
        return authorities;
    }

    @Override
    public String getPassword() {
        return currentUserInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return currentUserInfo.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

4、自定义UrlFilterInvocationSecurityMetadataSource实现FilterInvocationSecurityMetadataSource重写getAttributes()方法 获取访问该url所需要的角色权限信息

执行完之后到 下一步 UrlAccessDecisionManager 中认证权限

import cn.com.sercurity.cyy.common.util.Constants;
import cn.com.sercurity.cyy.config.swagger.MyProperties;
import cn.com.sercurity.cyy.menu.entity.Menu;
import cn.com.sercurity.cyy.menu.mapper.MenuMapper;
import cn.com.sercurity.cyy.role.entity.Role;
import cn.com.sercurity.cyy.role.mapper.RoleMapper;
import cn.com.sercurity.cyy.roleMenu.entity.RoleMenu;
import cn.com.sercurity.cyy.roleMenu.mapper.RoleMenuMapper;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

/**
 * <p> 获取访问该url所需要的用户角色权限信息 </p>
 *
 * @author :
 * @description : 执行完之后到 `UrlAccessDecisionManager` 中认证权限
 * @date : 2020/12/31 15:39
 */
@Component
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    MenuMapper menuMapper;
    @Autowired
    RoleMenuMapper roleMenuMapper;
    @Autowired
    RoleMapper roleMapper;
    @Autowired
    MyProperties myProperties;

    /***
     * 返回该url所需要的用户权限信息
     *
     * @param object: 储存请求url信息
     * @return: null:标识不需要任何权限都可以访问
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 获取当前请求url
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        // TODO 忽略url请放在此处进行过滤放行
        for (String ignoreUrl : myProperties.getAuth().getIgnoreUrls()) {
            if (ignoreUrl.equals(requestUrl)){
                return null;
            }
        }

        if (requestUrl.contains("/login") || requestUrl.contains("/groupChat")){
            return null;
        }

        // 数据库中所有url
        List<Menu> permissionList = menuMapper.selectList(null);
        for (Menu permission : permissionList) {
            // 获取该url所对应的权限
            if (("/api" + permission.getUrl()).equals(requestUrl)) {
                List<RoleMenu> permissions = roleMenuMapper.selectList(new EntityWrapper<RoleMenu>().eq("menu_id", permission.getId()));
                List<String> roles = new LinkedList<>();
                if (!CollectionUtils.isEmpty(permissions)){
                    permissions.forEach( e -> {
                        Integer roleId = e.getRoleId();
                        Role role = roleMapper.selectById(roleId);
                        roles.add(role.getCode());
                    });
                }
                // 保存该url对应角色权限信息
                return SecurityConfig.createList(roles.toArray(new String[roles.size()]));
            }
        }
        // 如果数据中没有找到相应url资源则为非法访问,要求用户登录再进行操作
        return SecurityConfig.createList(Constants.ROLE_LOGIN);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return FilterInvocation.class.isAssignableFrom(aClass);
    }
}
import com.baomidou.mybatisplus.enums.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableName;
import java.io.Serializable;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * <p>
 * 系统管理-权限资源表 
 * </p>
 *
 * @author
 * @since 2020-12-31
 */
@Data
@Accessors(chain = true)
@TableName("t_sys_menu")
public class Menu extends Model<Menu> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 上级资源ID
     */
    @TableField("parent_id")
    private String parentId;

    /**
     * url
     */
    private String url;

    /**
     * 资源编码
     */
    private String resources;

    /**
     * 资源名称
     */
    private String title;

    /**
     * 资源级别
     */
    private Integer level;

    /**
     * 排序
     */
    @TableField("sort_no")
    private Integer sortNo;

    /**
     * 资源图标
     */
    private String icon;

    /**
     * 类型 menu、button
     */
    private String type;

    /**
     * 备注
     */
    private String remarks;

    /**
     * 创建时间
     */
    @TableField("gmt_create")
    private Date gmtCreate;

    /**
     * 更新时间
     */
    @TableField("gmt_modified")
    private Date gmtModified;


    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}
import com.baomidou.mybatisplus.enums.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableName;
import java.io.Serializable;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * <p>
 * 系统管理 - 角色-权限资源关联表 
 * </p>
 *
 * @author
 * @since 2020-12-31
 */
@Data
@Accessors(chain = true)
@TableName("t_sys_role_menu")
public class RoleMenu extends Model<RoleMenu> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 角色ID
     */
    @TableField("role_id")
    private Integer roleId;

    /**
     * 菜单ID
     */
    @TableField("menu_id")
    private Integer menuId;

    /**
     * 创建时间
     */
    @TableField("gmt_create")
    private Date gmtCreate;

    /**
     * 更新时间
     */
    @TableField("gmt_modified")
    private Date gmtModified;


    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}

5、自定义UrlAccessDecisionManager实现AccessDecisionManager重写decide()方法 对访问url进行权限认证处理

此处小编的处理逻辑是只要包含其中一个角色即可访问

import cn.com.sercurity.cyy.common.util.Constants;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;


/**
 * <p> 对访问url进行权限认证处理 </p>
 *
 * @author :
 * @description :
 * @date : 2020/12/31 16:16
 */
@Component
public class UrlAccessDecisionManager implements AccessDecisionManager {

    /**
     * @param authentication: 当前登录用户的角色信息
     * @param object: 请求url信息
     * @param collection: `UrlFilterInvocationSecurityMetadataSource`中的getAttributes方法传来的,表示当前请求需要的角色(可能有多个)
     * @return: void
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> collection) throws AccessDeniedException, AuthenticationException {
        // 遍历角色
        for (ConfigAttribute ca : collection) {
            // ① 当前url请求需要的权限
            String needRole = ca.getAttribute();
            if (Constants.ROLE_LOGIN.equals(needRole)) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new BadCredentialsException("未登录!");
                } else {
                    throw new AccessDeniedException("未授权该url!");
                }
            }

            // ② 当前用户所具有的角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                // 只要包含其中一个角色即可访问
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("请联系管理员分配权限!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

6、自定义无权限处理器 UrlAccessDeniedHandler实现AccessDeniedHandler重写handle()方法

在这里自定义403无权限响应内容,登录过后的权限处理
【 注:要和未登录时的权限处理区分开哦~ 】

import cn.com.sercurity.cyy.common.dto.ApiResult;
import cn.com.sercurity.cyy.common.util.ResponseUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * <p> 认证url权限 - 登录后访问接口无权限 - 自定义403无权限响应内容 </p>
 *
 * @author :
 * @description : 登录过后的权限处理 【注:要和未登录时的权限处理区分开哦~】
 * @date : 2020/12/31 16:19
 */
@Component
public class UrlAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        ResponseUtils.out(response, ApiResult.fail(403, e.getMessage()));
    }
}

7、最后在Security 核心配置类中配置以上处理

import cn.com.sercurity.cyy.config.security.filter.AdminAuthenticationProcessingFilter;
import cn.com.sercurity.cyy.config.security.filter.MyAuthenticationFilter;
import cn.com.sercurity.cyy.config.security.login.AdminAuthenticationEntryPoint;
import cn.com.sercurity.cyy.config.security.url.UrlAccessDecisionManager;
import cn.com.sercurity.cyy.config.security.url.UrlAccessDeniedHandler;
import cn.com.sercurity.cyy.config.security.url.UrlFilterInvocationSecurityMetadataSource;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

/**
 * @author cyy
 * @date 2020/12/30 14:14
 * @EnableWebSecurity :启用Spring Security的Web安全支持
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 访问鉴权 - 认证token、签名...
     */
    private final MyAuthenticationFilter myAuthenticationFilter;
    /**
     * 访问权限认证异常处理
     */
    private final AdminAuthenticationEntryPoint adminAuthenticationEntryPoint;
    /**
     * 用户密码校验过滤器
     */
    private final AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter;

    // 上面是登录认证相关  下面为url权限相关 - ========================================================================================

    /**
     * 获取访问url所需要的角色信息
     */
    private final UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;
    /**
     * 认证权限处理 - 将上面所获得角色权限与当前登录用户的角色做对比,如果包含其中一个角色即可正常访问
     */
    private final UrlAccessDecisionManager urlAccessDecisionManager;
    /**
     * 自定义访问无权限接口时403响应内容
     */
    private final UrlAccessDeniedHandler urlAccessDeniedHandler;

    public SecurityConfig(MyAuthenticationFilter myAuthenticationFilter, AdminAuthenticationEntryPoint adminAuthenticationEntryPoint, AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter, UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource, UrlAccessDeniedHandler urlAccessDeniedHandler, UrlAccessDecisionManager urlAccessDecisionManager) {
        this.myAuthenticationFilter = myAuthenticationFilter;
        this.adminAuthenticationEntryPoint = adminAuthenticationEntryPoint;
        this.adminAuthenticationProcessingFilter = adminAuthenticationProcessingFilter;
        this.urlFilterInvocationSecurityMetadataSource = urlFilterInvocationSecurityMetadataSource;
        this.urlAccessDeniedHandler = urlAccessDeniedHandler;
        this.urlAccessDecisionManager = urlAccessDecisionManager;
    }


    /**
     * 权限配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.antMatcher("/**").authorizeRequests();

        // 禁用CSRF 开启跨域
        http.csrf().disable().cors();

        // 未登录认证异常
        http.exceptionHandling().authenticationEntryPoint(adminAuthenticationEntryPoint);
        // 登录过后访问无权限的接口时自定义403响应内容
        http.exceptionHandling().accessDeniedHandler(urlAccessDeniedHandler);

        // url权限认证处理
        registry.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
                o.setAccessDecisionManager(urlAccessDecisionManager);
                return o;
            }
        });

        // 不创建会话 - 即通过前端传token到后台过滤器中验证是否存在访问权限
//        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 标识访问 `/home` 这个接口,需要具备`ADMIN`角色
//        registry.antMatchers("/home").hasRole("ADMIN");
        // 标识只能在 服务器本地ip[127.0.0.1或localhost] 访问 `/home` 这个接口,其他ip地址无法访问
        registry.antMatchers("/home").hasIpAddress("127.0.0.1");
        // 允许匿名的url - 可理解为放行接口 - 多个接口使用,分割
        registry.antMatchers("/login", "/index").permitAll();
//        registry.antMatchers("/**").access("hasAuthority(‘admin‘)");
        // OPTIONS(选项):查找适用于一个特定网址资源的通讯选择。 在不需执行具体的涉及数据传输的动作情况下, 允许客户端来确定与资源相关的选项以及 / 或者要求, 或是一个服务器的性能
        registry.antMatchers(HttpMethod.OPTIONS, "/**").denyAll();
        // 自动登录 - cookie储存方式
        registry.and().rememberMe();
        // 其余所有请求都需要认证
        registry.anyRequest().authenticated();
        // 防止iframe 造成跨域
        registry.and().headers().frameOptions().disable();

        // 自定义过滤器在登录时认证用户名、密码
        http.addFilterAt(adminAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(myAuthenticationFilter, BasicAuthenticationFilter.class);
    }

    /**
     * 忽略拦截url或静态资源文件夹 - web.ignoring(): 会直接过滤该url - 将不会经过Spring Security过滤器链
     *                             http.permitAll(): 不会绕开springsecurity验证,相当于是允许该路径通过
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.GET,
                "/favicon.ico",
                "/*.html",
                "/**/*.css",
                "/**/*.js");
    }

}

编写测试代码

控制层:

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author 
 * @date 2020/12/29 11:14
 */
@Slf4j
@RestController
public class IndexController {

    @GetMapping("/")
    public ModelAndView showHome() {
        return new ModelAndView("home.html");
    }

    @GetMapping("/index")
    public String index() {
        return "Hello World ~";
    }

    @GetMapping("/login")
    public ModelAndView login() {
        return new ModelAndView("login.html");
    }

    @GetMapping("/home")
    public String home() {
        String name = SecurityContextHolder.getContext().getAuthentication().getName();
        log.info("登陆人:" + name);
        return "Hello~ " + name;
    }

    @GetMapping(value ="/admin")
    // 访问路径`/admin` 具有`ADMIN`角色权限   【这种是写死方式】
//    @PreAuthorize("hasPermission(‘/admin‘,‘ADMIN‘)")
    public String admin() {
        return "Hello~ 管理员";
    }

    @GetMapping("/test")
    public String test() {
        return "Hello~ 测试权限访问接口";
    }

}

运行访问测试效果

1、未登录时

技术分享图片

 

 

 2、登录过后如果有权限则正常访问

技术分享图片

 

 

总结

  1. 自定义未登录权限处理器AdminAuthenticationEntryPoint - 自定义未登录时访问无权限url响应内容
  2. 自定义访问鉴权过滤器MyAuthenticationFilter - 记录请求响应日志、是否合法访问,验证token过期等
  3. 自定义UrlFilterInvocationSecurityMetadataSource - 获取访问该url所需要的角色权限
  4. 自定义UrlAccessDecisionManager - 对访问url进行权限认证处理
  5. 自定义UrlAccessDeniedHandler - 登录过后访问无权限url失败处理器 - 自定义403无权限响应内容
  6. Security核心配置类中配置以上处理器和过滤器

 

(四)Spring Security 动态url权限控制

原文:https://www.cnblogs.com/changyuyao/p/14228161.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!