表關系簡介:
- 用戶表
t_sys_user
關聯 角色表t_sys_role
兩者建立中間關系表t_sys_user_role
- 角色表
t_sys_role
關聯 權限表t_sys_permission
兩者建立中間關系表t_sys_role_permission
- 最終體現效果為當前登錄用戶所具備的角色關聯能訪問的所有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.output.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) {
// 未登錄 或 token過期
if (e!=null){
ResponseUtils.out(response, ApiResult.expired(e.getMessage()));
} else {
ResponseUtils.out(response, ApiResult.expired("jwtToken過期!"));
}
}
}
注:ResponseUtils
和ApiResult,在
這里是模擬前后端分離情況下返回json格式數據所使用工具類,在上一篇文章中有記錄
2、自定義過濾器MyAuthenticationFilter
繼承OncePerRequestFilter
實現訪問鑒權
每次訪問接口都會經過此,我們可以在這里記錄請求參數、響應內容,或者處理前后端分離情況下,以token換用戶權限信息,token是否過期,請求頭類型是否正確,防止非法請求等等
logRequestBody()
方法:記錄請求消息體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.output.ApiResult;
import cn.com.sercurity.cyy.common.enumeration.ResultCode;
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(ResultCode.UNAUTHORIZED.getCode(), 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 cn.com.sercurity.cyy.config.swagger.MyProperties;
import org.springframework.beans.factory.annotation.Autowired;
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.config.http.SessionCreationPolicy;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
/**
* @author
* @date 2020/12/30 14:14
* @EnableWebSecurity :啟用Spring Security的Web安全支持
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyProperties myProperties;
/**
* 訪問鑒權 - 認證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);
// 登錄處理 - 前后端一體的情況下
// registry.and().formLogin().loginPage("/login").defaultSuccessUrl("/").permitAll()
// // 自定義登陸用戶名和密碼屬性名,默認為 username和password
// .usernameParameter("username").passwordParameter("password")
// // 異常處理
// .failureUrl("/login/error").permitAll()
// // 退出登錄
// .and().logout().permitAll();
// 標識訪問 `/home` 這個接口,需要具備`ADMIN`角色
// registry.antMatchers("/home").hasRole("ADMIN");
// 標識只能在 服務器本地ip[127.0.0.1或localhost] 訪問 `/home` 這個接口,其他ip地址無法訪問
registry.antMatchers("/home").hasIpAddress("127.0.0.1");
// 允許匿名的url - 可理解為放行接口 - 除配置文件忽略url以外,其它所有請求都需經過認證和授權
for (String url : myProperties.getAuth().getIgnoreUrls()) {
registry.antMatchers(url).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",
"/**/*.png",
"/**/*.ttf",
"/*.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、登錄過后如果有權限則正常訪問
總結
- 自定義未登錄權限處理器
AdminAuthenticationEntryPoint
- 自定義未登錄時訪問無權限url響應內容 - 自定義訪問鑒權過濾器
MyAuthenticationFilter
- 記錄請求響應日志、是否合法訪問,驗證token過期等 - 自定義
UrlFilterInvocationSecurityMetadataSource
- 獲取訪問該url所需要的角色權限 - 自定義
UrlAccessDecisionManager
- 對訪問url進行權限認證處理 - 自定義
UrlAccessDeniedHandler
- 登錄過后訪問無權限url失敗處理器 - 自定義403無權限響應內容 - 在
Security核心配置類
中配置以上處理器和過濾器