源本科技 | 码上会

集成认证授权 - Spring Security & JWT

2026/05/04
115
0

Spring Security & JWT

概念与分工

Spring Security 是安全框架(负责认证与授权),JWT 是无状态令牌(负责身份传递),二者结合是前后端分离 / 微服务最主流的安全方案。

组件

核心职责

关键能力

Spring Security

安全规则引擎

账号密码校验、权限控制 (RBAC)、请求拦截、异常处理

JWT

身份凭证传递

生成加密令牌、自包含用户信息、服务器无状态

工作流程概述

登录认证流程

接口访问流程

JWT 结构解析

JWT(JSON Web Token)由三部分组成,用.分隔:Header.Payload.Signature

结构示意图

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.  // Header(头部)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.  // Payload(载荷)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c  // Signature(签名)

部分

作用

内容示例

Header

令牌类型 + 算法

{"alg":"HS256","typ":"JWT"}

Payload

用户信息 + 声明

{"sub":"1234567890","name":"John Doe","roles":["USER","ADMIN"],"exp":1717363200}

Signature

防篡改签名

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

核心优势

优势

说明

无状态

服务器不存储 Session,完美适配分布式 / 微服务 / 跨域场景

自包含

JWT 携带用户信息,减少数据库查询,提升性能

灵活扩展

支持 RBAC 权限模型,可通过注解精细控制接口权限

跨平台

支持浏览器、APP、小程序等多终端认证

防篡改

签名机制确保令牌不被篡改,安全性高

最佳实践

  1. 密钥安全

    • 密钥长度 ≥256 位,生产环境使用环境变量 / 配置中心存储,避免硬编码

    • 定期轮换密钥,可使用 RSA 非对称加密增强安全性

  2. 令牌管理

    • 设置合理过期时间(如 1 小时),使用刷新令牌机制延长会话

    • 前端存储 JWT 在 HttpOnly Cookie 中,防止 XSS 攻击

    • 敏感操作可使用短期令牌,降低泄露风险

  3. 密码安全

    • 强制使用 BCrypt/Argon2 等自适应哈希算法加密存储密码

    • 实施密码复杂度策略(≥12 位,大小写 + 数字 + 特殊字符)

  4. 权限控制

    • 遵循最小权限原则,角色设计清晰,避免权限冗余

    • 同时启用 URL 级和方法级权限控制,双重保障

总结

Spring Security + JWT 组合实现了 认证与授权分离无状态设计 灵活权限控制,是现代 Web 应用的安全标配。通过 JWT 过滤器拦截请求、验证令牌、设置安全上下文,Spring Security 负责权限校验和安全防护,二者协同工作,为前后端分离和微服务架构提供高效、安全的认证解决方案

准备工作

引入依赖

我们基于之前的项目代码继续追加缺失的依赖

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

设计表结构

  • 表结构

sys_config 系统配置表
sys_dept 部门表
sys_dict 字典表
sys_dict_data 字典数据表
sys_menu 菜单表
sys_role 角色表
sys_role_menu 角色菜单关系表
sys_user 用户表
sys_user_role 用户角色关系表
  • 对应的 SQL 语句,MySQL 版本

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_config
-- ----------------------------
DROP TABLE IF EXISTS `sys_config`;
CREATE TABLE `sys_config`  (
  `config_id` bigint NOT NULL AUTO_INCREMENT COMMENT '配置ID',
  `config_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '系统内置: Y N ',
  `config_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '键',
  `config_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '值',
  `config_name` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`config_id`) USING BTREE,
  UNIQUE INDEX `param_key_idx`(`config_key` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统配置表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_config
-- ----------------------------

-- ----------------------------
-- Table structure for sys_dept
-- ----------------------------
DROP TABLE IF EXISTS `sys_dept`;
CREATE TABLE `sys_dept`  (
  `dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门ID',
  `pid` bigint NOT NULL COMMENT '上级部门',
  `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
  `sort` int NULL DEFAULT 0 COMMENT '排序',
  `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人',
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',
  `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`dept_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_dept
-- ----------------------------
INSERT INTO `sys_dept` VALUES (1, 0, '0', '总部', 0, 'zongbu', '18888888888', '18888888888@163.com', 1, NULL, 'N', '2024-12-02 15:35:36', NULL, '2024-12-02 15:36:08', NULL);
INSERT INTO `sys_dept` VALUES (2, 1, '0,1', '研发部', 0, 'yanfa', '18888888888', '18888888888@163.com', 1, NULL, 'N', '2024-12-02 15:35:36', NULL, '2024-12-02 15:35:59', NULL);
INSERT INTO `sys_dept` VALUES (3, 1, '0,1', '营销部', 1, 'xiaoshou', '18888888888', NULL, 1, NULL, 'N', '2024-12-02 15:35:36', NULL, '2024-12-02 15:35:57', NULL);
INSERT INTO `sys_dept` VALUES (4, 1, '0,1', '产品部', 1, 'chanpin', NULL, NULL, 1, NULL, 'N', '2024-12-02 15:35:36', NULL, '2024-12-02 15:35:36', NULL);

-- ----------------------------
-- Table structure for sys_dict
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict`;
CREATE TABLE `sys_dict`  (
  `dict_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典名称',
  `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '数据字典' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_dict
-- ----------------------------
INSERT INTO `sys_dict` VALUES (1, '用户性别', 'sys_user_sex', '用户性别', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:12', NULL);
INSERT INTO `sys_dict` VALUES (2, '菜单状态', 'sys_show_hide', '菜单状态', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:14', NULL);
INSERT INTO `sys_dict` VALUES (3, '系统开关', 'sys_normal_disable', '系统开关', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:15', NULL);
INSERT INTO `sys_dict` VALUES (4, '任务状态', 'sys_job_status', '任务状态', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:16', NULL);
INSERT INTO `sys_dict` VALUES (5, '任务分组', 'sys_job_group', '任务分组', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:17', NULL);
INSERT INTO `sys_dict` VALUES (6, '系统是否', 'sys_yes_no', '系统是否', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:18', NULL);
INSERT INTO `sys_dict` VALUES (7, '通知类型', 'sys_notice_type', '通知类型', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:19', NULL);
INSERT INTO `sys_dict` VALUES (8, '通知状态', 'sys_notice_status', '通知状态', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:21', NULL);
INSERT INTO `sys_dict` VALUES (9, '操作类型', 'sys_oper_type', '操作类型', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:22', NULL);
INSERT INTO `sys_dict` VALUES (10, '系统状态', 'sys_common_status', '系统状态', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:24', NULL);
INSERT INTO `sys_dict` VALUES (11, '用户岗位', 'sys_user_post', '用户岗位', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:25', NULL);
INSERT INTO `sys_dict` VALUES (12, '菜单类型', 'sys_menu_type', '权限类型:0->目录;1->菜单;2->按钮', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:27', NULL);

-- ----------------------------
-- Table structure for sys_dict_data
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict_data`;
CREATE TABLE `sys_dict_data`  (
  `dict_data_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `dict_sort` int NULL DEFAULT 0 COMMENT '字典排序',
  `dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典标签',
  `dict_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典键值',
  `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型',
  `css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)',
  `list_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表格回显样式',
  `is_default` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否默认,Y:是 N:否',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`dict_data_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_dict_data
-- ----------------------------
INSERT INTO `sys_dict_data` VALUES (1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:51', NULL);
INSERT INTO `sys_dict_data` VALUES (2, 2, '女', '1', 'sys_user_sex', '', '', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:52', NULL);
INSERT INTO `sys_dict_data` VALUES (3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:52', NULL);
INSERT INTO `sys_dict_data` VALUES (4, 1, '显示', '1', 'sys_show_hide', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:53', NULL);
INSERT INTO `sys_dict_data` VALUES (5, 2, '隐藏', '0', 'sys_show_hide', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:54', NULL);
INSERT INTO `sys_dict_data` VALUES (6, 1, '正常', '1', 'sys_normal_disable', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:55', NULL);
INSERT INTO `sys_dict_data` VALUES (7, 2, '停用', '0', 'sys_normal_disable', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:55', NULL);
INSERT INTO `sys_dict_data` VALUES (8, 1, '正常', '1', 'sys_job_status', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:56', NULL);
INSERT INTO `sys_dict_data` VALUES (9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:58', NULL);
INSERT INTO `sys_dict_data` VALUES (10, 1, '默认', 'DEFAULT', 'sys_job_group', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:00', NULL);
INSERT INTO `sys_dict_data` VALUES (11, 2, '系统', 'SYSTEM', 'sys_job_group', '', 'primary', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:02', NULL);
INSERT INTO `sys_dict_data` VALUES (12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:05', NULL);
INSERT INTO `sys_dict_data` VALUES (13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:07', NULL);
INSERT INTO `sys_dict_data` VALUES (14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:08', NULL);
INSERT INTO `sys_dict_data` VALUES (15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:10', NULL);
INSERT INTO `sys_dict_data` VALUES (16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:12', NULL);
INSERT INTO `sys_dict_data` VALUES (17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:13', NULL);
INSERT INTO `sys_dict_data` VALUES (18, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:16', NULL);
INSERT INTO `sys_dict_data` VALUES (19, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:18', NULL);
INSERT INTO `sys_dict_data` VALUES (20, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:21', NULL);
INSERT INTO `sys_dict_data` VALUES (21, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:22', NULL);
INSERT INTO `sys_dict_data` VALUES (22, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:23', NULL);
INSERT INTO `sys_dict_data` VALUES (23, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:24', NULL);
INSERT INTO `sys_dict_data` VALUES (24, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:26', NULL);
INSERT INTO `sys_dict_data` VALUES (25, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:27', NULL);
INSERT INTO `sys_dict_data` VALUES (26, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:28', NULL);
INSERT INTO `sys_dict_data` VALUES (27, 1, '成功', '1', 'sys_common_status', '', 'primary', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:30', NULL);
INSERT INTO `sys_dict_data` VALUES (28, 2, '失败', '0', 'sys_common_status', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:32', NULL);
INSERT INTO `sys_dict_data` VALUES (29, 1, '董事长', '1', 'sys_user_post', NULL, NULL, 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:33', NULL);
INSERT INTO `sys_dict_data` VALUES (30, 2, '项目经理', '2', 'sys_user_post', NULL, NULL, 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:35', NULL);
INSERT INTO `sys_dict_data` VALUES (31, 3, '人力资源', '3', 'sys_user_post', NULL, NULL, 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:36', NULL);
INSERT INTO `sys_dict_data` VALUES (32, 4, '产品经理', '4', 'sys_user_post', NULL, NULL, 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:38', NULL);
INSERT INTO `sys_dict_data` VALUES (33, 5, '普通员工', '5', 'sys_user_post', NULL, NULL, 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:39', NULL);
INSERT INTO `sys_dict_data` VALUES (34, 1, '目录', '0', 'sys_menu_type', NULL, 'primary', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:41', NULL);
INSERT INTO `sys_dict_data` VALUES (35, 2, '菜单', '1', 'sys_menu_type', NULL, 'success', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:54:55', NULL);
INSERT INTO `sys_dict_data` VALUES (36, 3, '按钮', '2', 'sys_menu_type', NULL, 'warning', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:54:57', NULL);

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `pid` bigint NULL DEFAULT NULL COMMENT '父级菜单id',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
  `permission` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标',
  `type` int NULL DEFAULT NULL COMMENT '权限类型,0:目录 1:菜单 2:按钮(接口绑定权限)',
  `uri` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '前端资源路径',
  `sort` int NULL DEFAULT NULL COMMENT '排序',
  `outer_link` int NULL DEFAULT 0 COMMENT '是否为外链, 0:否 1:是',
  `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1036 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户权限表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, 0, '系统管理', '', 'system', 0, '', 1, 0, 'system', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (100, 1, '用户管理', 'sys_user_page', 'user', 1, 'system/user/index', 1, 0, 'user', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (101, 1, '角色管理', 'sys_role_page', 'peoples', 1, 'system/role/index', 2, 0, 'role', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (102, 1, '菜单管理', 'sys_menu_page', 'tree-table', 1, 'system/menu/index', 3, 0, 'menu', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (103, 1, '部门管理', 'sys_dept_page', 'tree', 1, 'system/dept/index', 4, 0, 'dept', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (105, 1, '字典管理', 'sys_dict_page', 'dict', 1, 'system/dict/index', 6, 0, 'dict', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (106, 1, '参数设置', 'sys_config_page', 'edit', 1, 'system/config/index', 7, 0, 'config', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1001, 100, '用户查询', 'sys_user_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1002, 100, '用户新增', 'sys_user_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1003, 100, '用户修改', 'sys_user_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1004, 100, '用户删除', 'sys_user_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1005, 100, '用户导出', 'sys_user_export', '', 2, '', 5, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1006, 100, '用户导入', 'sys_user_import', '', 2, '', 6, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1007, 100, '重置密码', 'sys_user_reset', '', 2, '', 7, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1008, 101, '角色查询', 'sys_role_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1009, 101, '角色新增', 'sys_role_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1010, 101, '角色修改', 'sys_role_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1011, 101, '角色删除', 'sys_role_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1012, 101, '角色导出', 'sys_role_export', '', 2, '', 5, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1013, 102, '菜单查询', 'sys_menu_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1014, 102, '菜单新增', 'sys_menu_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1015, 102, '菜单修改', 'sys_menu_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1016, 102, '菜单删除', 'sys_menu_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1017, 103, '部门查询', 'sys_dept_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1018, 103, '部门新增', 'sys_dept_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1019, 103, '部门修改', 'sys_dept_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1020, 103, '部门删除', 'sys_dept_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1026, 105, '字典查询', 'sys_dict_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1027, 105, '字典新增', 'sys_dict_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1028, 105, '字典修改', 'sys_dict_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1029, 105, '字典删除', 'sys_dict_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1030, 105, '字典导出', 'sys_dict_export', '', 2, '', 5, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1031, 106, '参数查询', 'sys_config_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1032, 106, '参数新增', 'sys_config_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1033, 106, '参数修改', 'sys_config_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1034, 106, '参数删除', 'sys_config_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1035, 106, '参数导出', 'sys_config_export', '', 2, '', 5, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
  `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色权限字符',
  `data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 )',
  `description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
  `user_count` int NULL DEFAULT NULL COMMENT '后台用户数量',
  `sort` int NULL DEFAULT 0 COMMENT '排序',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '管理员', 'ROOT', '1', '超级管理员', 1, 0, 1, NULL, 'N', '2024-12-02 10:57:02', NULL, NULL, NULL);
INSERT INTO `sys_role` VALUES (2, '测试', 'TEST', '1', '测试角色', 1, 0, 1, NULL, 'N', '2024-12-02 15:21:49', NULL, NULL, NULL);

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (
  `role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
  `menu_id` bigint NULL DEFAULT NULL COMMENT '菜单ID',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和菜单关系表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (2, 1, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 100, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 101, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 102, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 103, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 106, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 500, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 501, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1001, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1002, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1003, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1004, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1005, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1006, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1007, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1008, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1009, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1010, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1011, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1012, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1013, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1014, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1015, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1016, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1017, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1018, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1019, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1020, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1031, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1032, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1033, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1034, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1035, 1, NULL, 'N', NULL, NULL, NULL, NULL);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  `dept_id` bigint NULL DEFAULT NULL COMMENT '所属部门',
  `dept_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所属部门名称',
  `post_ids` json NULL COMMENT '岗位组',
  `icon` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
  `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
  `nick_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `sex` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '性别,0:男 1:女 2:未知',
  `note` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注信息',
  `login_time` datetime NULL DEFAULT NULL COMMENT '最后登录时间',
  `login_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最后登陆IP',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 102 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$Wr0wX8Ueiox9ndNXsrx8JOfe3sc6QIsTW4JW/rPR6.7iAzrdKLYSe', 1, '总部', '[]', 'https://cdn-icons-png.flaticon.com/512/3135/3135715.png', 'test@qq.com', '17777777777', 'Admin', '0', NULL, '2024-11-20 17:25:53', '192.168.31.7', 1, NULL, 'N', '2019-09-29 13:55:30', NULL, '2024-12-02 10:59:46', 1);
INSERT INTO `sys_user` VALUES (101, 'guest', '$2a$10$Wr0wX8Ueiox9ndNXsrx8JOfe3sc6QIsTW4JW/rPR6.7iAzrdKLYSe', 2, '研发部', '[\"3\"]', '', 'test@qq.com', '17777777777', 'test', '1', NULL, '2024-11-20 17:32:24', '192.168.31.7', 1, NULL, 'N', '2019-09-29 13:55:30', NULL, '2024-12-02 10:59:49', 101);

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `user_id` bigint NULL DEFAULT NULL COMMENT '用户ID',
  `role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
  `status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
  `version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `update_user` bigint NULL DEFAULT NULL COMMENT '修改人'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户和角色关系表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (101, 2, 1, NULL, 'N', NULL, NULL, NULL, NULL);

SET FOREIGN_KEY_CHECKS = 1;

生成基本代码

代码生成器

  • 我们使用 MyBatis-Plus Generator UI 代码生成器

  • 创建一个名为 generator 的目录专门用于启动代码生成器,pom.xml 配置如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.lusifer</groupId>
    <artifactId>generator</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>generator</name>

    <properties>
        <java.version>17</java.version>
    </properties>

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

        <dependency>
            <groupId>com.github.davidfantasy</groupId>
            <artifactId>mybatis-plus-generator-ui</artifactId>
            <version>2.0.5</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project>
  • src/test/java 创建 com.lusifer.generator.GeneratorUIServer 用于启动服务器,因为依赖范围是 test

package com.lusifer.generator;

import com.github.davidfantasy.mybatisplus.generatorui.GeneratorConfig;
import com.github.davidfantasy.mybatisplus.generatorui.MybatisPlusToolsApplication;
import com.github.davidfantasy.mybatisplus.generatorui.mbp.NameConverter;

public class GeneratorUIServer {
  public static void main(String[] args) {
    // 构建代码生成器核心配置
    GeneratorConfig config =
        GeneratorConfig.builder()
            // 数据库连接 URL,推荐追加编码、时区等参数
            .jdbcUrl(
                "jdbc:mysql://192.168.203.128:3306/mycrmeb?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8")
            // 数据库登录用户名
            .userName("root")
            // 数据库登录密码
            .password("123456")
            // MySQL 8.0 + 版本驱动类
            .driverClassName("com.mysql.cj.jdbc.Driver")
            // 数据库 schema(仅 MSSQL、PGSQL、ORACLE、DB2 需要配置)
            // .schemaName("myBusiness")
            // 数据库表前缀,生成实体类时自动去除(如 t_user → User)
            // .tablePrefix("t_")
            // 自定义命名规则,可重写方法修改类名生成规范
            .nameConverter(
                new NameConverter() {
                  /**
                   * 自定义 Service 类命名规则
                   *
                   * @param entityName 处理后的实体类名称
                   * @return 最终生成的 Service 类名
                   */
                  @Override
                  public String serviceNameConvert(String entityName) {
                    return entityName + "Service";
                  }

                  /**
                   * 自定义 Controller 类命名规则
                   *
                   * @param entityName 处理后的实体类名称
                   * @return 最终生成的 Controller 类名
                   */
                  @Override
                  public String controllerNameConvert(String entityName) {
                    return entityName + "Controller";
                  }
                })
            // 生成所有 Java 文件的根包名
            .basePackage("com.lusifer.crmeb")
            // 可视化界面启动端口
            .port(8068)
            .build();

    // 启动代码生成器服务
    MybatisPlusToolsApplication.run(config);
  }
}
  • 访问 http://localhost:8068 地址

输出配置参考

  • Entity

Entity 超类名称:com.lusifer.crmeb.rule.pojo.entity.BaseBusinessEntity
Entity 公共字段:version_flag del_flag create_time create_user update_time update_user
  • Mapper.xml

  • Mapper.java

com.baomidou.mybatisplus.core.mapper.BaseMapper
  • Service

com.baomidou.mybatisplus.extension.service.IService
  • ServiceImpl

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
  • Controller

注意:生成后将代码复制到项目中

代码结构参考

由于在配置文件中设置了 mapping 路径的位置,注意观察自己的配置文件,所以将 Mapper.xml 放在 mapper/mapping

输出代码修改

代码需要进行简单的修改,下面给出修改后的案例

  • Entity

特别注意:statusFlag 字段的数据类型应该是【Character】,而不是生成的【byte】

package com.lusifer.crmeb.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lusifer.crmeb.rule.pojo.entity.BaseBusinessEntity;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 系统配置表
 *
 * @author Lusifer
 * @since 2026-05-04
 */
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("sys_config")
public class SysConfig extends BaseBusinessEntity {

  @Serial private static final long serialVersionUID = 1L;

  /** 配置ID */
  @TableId(value = "config_id", type = IdType.ASSIGN_ID)
  private Long configId;

  /** 系统内置: Y N */
  private String configType;

  /** 键 */
  private String configKey;

  /** 值 */
  private String configValue;

  /** 名称 */
  private String configName;

  /** 备注 */
  private String remark;

  /** 状态:1-启用,2-禁用 */
  private Character statusFlag;
}
  • Mapper

package com.lusifer.crmeb.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lusifer.crmeb.entity.SysConfig;

/**
 * 系统配置表 Mapper 接口
 *
 * @author Lusifer
 * @since 2026-05-04
 */
public interface SysConfigMapper extends BaseMapper<SysConfig> {}
  • Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.crmeb.mapper.SysConfigMapper">

</mapper>
  • Service

package com.lusifer.crmeb.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.lusifer.crmeb.entity.SysConfig;

/**
 * 系统配置表 服务类
 *
 * @author Lusifer
 * @since 2026-05-04
 */
public interface SysConfigService extends IService<SysConfig> {}
  • ServiceImpl

package com.lusifer.crmeb.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lusifer.crmeb.entity.SysConfig;
import com.lusifer.crmeb.mapper.SysConfigMapper;
import com.lusifer.crmeb.service.SysConfigService;
import org.springframework.stereotype.Service;

/**
 * 系统配置表 服务实现类
 *
 * @author Lusifer
 * @since 2026-05-04
 */
@Service
public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig>
    implements SysConfigService {}
  • Controller

package com.lusifer.crmeb.controller;

import com.lusifer.crmeb.service.SysConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

/**
 * 系统配置表 前端控制器
 *
 * @author Lusifer
 * @since 2026-05-04
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/sys/config")
public class SysConfigController {

  private final SysConfigService sysConfigService;
}

配置 JWT

修改配置

  • 修改 application.yml 配置文件

security:
  jwt:
    # JWT 秘钥
    # 可以使用密码生成器生成
    key: ZajKtj8qZudAxojmvUsTNWV4FQgkSOkY0oReC2g7MC5zm1qelsv0VCH1gYfIZEdx
    # JWT 有效期(单位:秒)
    ttl: 7200
  # 白名单列表(因为集成了 swagger 需要将下列路劲配置到白名单)
  ignore-urls:
    - /v3/api-docs/**
    - /doc.html
    - /swagger-resources/**
    - /webjars/**
    - /swagger-ui/**
    - /swagger-ui.html
    - /auth/login
    - /auth/captcha
    - /ws/**

相关配置类

  • com.lusifer.crmeb.config.properties.SecurityProperties

package com.lusifer.crmeb.config.properties;

import java.util.List;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/** Security Properties */
@Data
@Component
@ConfigurationProperties(prefix = "security")
public class SecurityProperties {

  /** 白名单 URL 集合 */
  private List<String> ignoreUrls;

  /** JWT 配置 */
  private JwtProperty jwt;

  /** JWT 配置 */
  @Data
  public static class JwtProperty {

    /** JWT 密钥 */
    private String key;

    /** JWT 过期时间 */
    private Long ttl;
  }
}
  • com.lusifer.crmeb.rule.constants.SecurityConstants

package com.lusifer.crmeb.rule.constants;

/** 安全模块常量 */
public interface SecurityConstants {

  /** 验证码缓存前缀 */
  String CAPTCHA_CODE_PREFIX = "captcha_code:";

  /** 角色和权限缓存前缀 */
  String ROLE_PERMS_PREFIX = "role_perms:";

  /** 黑名单Token缓存前缀 */
  String BLACKLIST_TOKEN_PREFIX = "token:blacklist:";

  /** 登录路径 */
  String LOGIN_PATH = "/auth/login";

  /** JWT Token 前缀 */
  String JWT_TOKEN_PREFIX = "Bearer ";
}
  • com.lusifer.crmeb.exception.enums.SecurityExceptionEnum

package com.lusifer.crmeb.exception.enums;

import com.lusifer.crmeb.exception.AbstractExceptionEnum;
import lombok.Getter;

/** 安全相关错误 */
@Getter
public enum SecurityExceptionEnum implements AbstractExceptionEnum {

  /** 未登录 */
  UNAUTHORIZED("401", "暂未登录或 token 已失效"),

  /** 未授权 */
  FORBIDDEN("403", "没有相关权限");

  /** 错误编码 */
  private final String errorCode;

  /** 提示用户信息 */
  private final String userTip;

  SecurityExceptionEnum(String errorCode, String userTip) {
    this.errorCode = errorCode;
    this.userTip = userTip;
  }
}
  • com.lusifer.crmeb.exception.enums.SysExceptionEnum

package com.lusifer.crmeb.exception.enums;

import com.lusifer.crmeb.exception.AbstractExceptionEnum;
import lombok.Getter;

/** 系统模块异常枚举 */
@Getter
public enum SysExceptionEnum implements AbstractExceptionEnum {
  ERROR_USERNAME_OR_PASSWORD("1001", "用户名或密码错误"),
  USER_DISABLED("1002", "您的账号已被停用"),
  USER_NOT_EXIST("1003", "用户不存在");

  /** 错误编码 */
  private final String errorCode;

  /** 提示用户信息 */
  private final String userTip;

  SysExceptionEnum(String errorCode, String userTip) {
    this.errorCode = errorCode;
    this.userTip = userTip;
  }
}

处理未登录

  • com.lusifer.crmeb.component.security.handle.RestAuthenticationEntryPoint

package com.lusifer.crmeb.component.security.handle;

import cn.hutool.json.JSONUtil;
import com.lusifer.crmeb.rule.pojo.response.ErrorResponseData;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

/** 当未登录或者 token 失效访问接口时,自定义的返回结果 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

  @Override
  public void commence(
      HttpServletRequest request,
      HttpServletResponse response,
      AuthenticationException authException)
      throws IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json");
    response
        .getWriter()
        .println(
            JSONUtil.toJsonStr(
                new ErrorResponseData<>("401", "暂未登录或 token 已失效", authException.getMessage())));

    response.getWriter().flush();
  }
}

处理未授权

  • com.lusifer.crmeb.component.security.handle.RestfulAccessDeniedHandler

package com.lusifer.crmeb.component.security.handle;

import cn.hutool.json.JSONUtil;
import com.lusifer.crmeb.rule.pojo.response.ErrorResponseData;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

/** 当访问接口没有权限时,自定义的返回结果 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {

  @Override
  public void handle(
      HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
      throws IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json");
    response
        .getWriter()
        .println(JSONUtil.toJsonStr(new ErrorResponseData<>("403", "没有相关权限", e.getMessage())));
    response.getWriter().flush();
  }
}

登录授权过滤器

  • 处理枚举 com.lusifer.crmeb.rule.enums.StatusEnum

package com.lusifer.crmeb.rule.enums;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.lusifer.crmeb.rule.base.ReadableEnum;
import lombok.Getter;

/** 公共状态,一般用来表示开启和关闭 */
@Getter
public enum StatusEnum implements ReadableEnum<StatusEnum> {

  /** 启用 */
  ENABLE('1', "启用"),

  /** 禁用 */
  DISABLE('2', "禁用");

  @EnumValue @JsonValue private final Character code;

  private final String message;

  StatusEnum(Character code, String message) {
    this.code = code;
    this.message = message;
  }

  /** 根据 code 获取枚举 */
  @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
  public static StatusEnum codeToEnum(Character code) {
    if (null != code) {
      for (StatusEnum item : StatusEnum.values()) {
        if (item.getCode().equals(code)) {
          return item;
        }
      }
    }
    return null;
  }

  @Override
  public Object getKey() {
    return this.code;
  }

  @Override
  public Object getName() {
    return this.message;
  }

  @Override
  public StatusEnum parseToEnum(String originValue) {
    if (ObjectUtil.isEmpty(originValue)) {
      return null;
    }
    for (StatusEnum value : StatusEnum.values()) {
      if (value.code.equals(originValue.charAt(0))) {
        return value;
      }
    }
    return null;
  }
}
  • com.lusifer.crmeb.component.security.model.SysUserDetails

package com.lusifer.crmeb.component.security.model;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.lusifer.crmeb.entity.SysUser;
import com.lusifer.crmeb.rule.enums.StatusEnum;
import java.util.Collection;
import java.util.Set;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/** SpringSecurity 需要的用户详情 */
@Data
@NoArgsConstructor
@JsonIgnoreProperties({
  "enabled",
  "accountNonExpired",
  "accountNonLocked",
  "credentialsNonExpired",
  "authorities",
  "password"
})
public class SysUserDetails implements UserDetails {
  private String username;

  private SysUser sysUser;

  private Set<String> permissions;

  private Set<String> roles;

  private Integer dataScope;

  public SysUserDetails(
      SysUser user,
      Set<String> permissions,
      Set<String> roles,
      String username,
      Integer dataScope) {
    this.sysUser = user;
    this.permissions = permissions;
    this.roles = roles;
    this.username = username;
    this.dataScope = dataScope;
  }

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    // 返回当前用户的权限
    return permissions.stream()
        .filter(StrUtil::isNotEmpty)
        .map(SimpleGrantedAuthority::new)
        .toList();
  }

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

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

  /**
   * 是否可用,禁用的用户不能身份验证
   *
   * @return 是否可用
   */
  @Override
  public boolean isEnabled() {
    return StatusEnum.ENABLE.getKey().equals(sysUser.getStatusFlag());
  }
}
  • com.lusifer.crmeb.component.security.filter.JwtValidationFilter

package com.lusifer.crmeb.component.security.filter;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.RegisteredPayload;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.exception.base.ServiceException;
import com.lusifer.crmeb.exception.enums.SecurityExceptionEnum;
import com.lusifer.crmeb.rule.constants.SecurityConstants;
import jakarta.annotation.Nonnull;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

/** JWT 登录授权过滤器 */
@Slf4j
public class JwtValidationFilter extends OncePerRequestFilter {

  private final UserDetailsService userDetailsService;

  // 密钥
  private final byte[] secretKey;

  public JwtValidationFilter(UserDetailsService userDetailsService, String secretKey) {
    this.userDetailsService = userDetailsService;
    this.secretKey = secretKey.getBytes();
  }

  @Override
  protected void doFilterInternal(
      HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain chain)
      throws ServletException, IOException {
    // 获取请求 token
    String token = request.getHeader(HttpHeaders.AUTHORIZATION);
    try {
      // 如果请求头中没有 Authorization 信息,或者 Authorization 以 Bearer 开头,则认为是匿名用户
      if (CharSequenceUtil.isBlank(token)
          || !token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
        chain.doFilter(request, response);
        return;
      }

      // 去除 Bearer 前缀
      token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
      // 解析 Token
      JWT jwt = JWTUtil.parseToken(token);

      // 检查 Token 是否有效(验签 + 是否过期)
      boolean isValidate = jwt.setKey(secretKey).validate(0);
      if (!isValidate) {
        log.error("JwtValidationFilter error: token is invalid");
        throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
      }
      JSONObject payloads = jwt.getPayloads();
      String username = payloads.getStr(RegisteredPayload.SUBJECT);
      SysUserDetails userDetails =
          (SysUserDetails) this.userDetailsService.loadUserByUsername(username);

      UsernamePasswordAuthenticationToken authentication =
          new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
      authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
      SecurityContextHolder.getContext().setAuthentication(authentication);
    } catch (Exception e) {
      log.error("JwtValidationFilter error: {}", e.getMessage());
      SecurityContextHolder.clearContext();
      throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
    }
    // Token 有效或无 Token 时继续执行过滤链
    chain.doFilter(request, response);
  }
}

自动装配

  • com.lusifer.crmeb.config.ProjectSecurityAutoConfiguration

package com.lusifer.crmeb.config;

import com.lusifer.crmeb.component.security.filter.JwtValidationFilter;
import com.lusifer.crmeb.component.security.handle.RestAuthenticationEntryPoint;
import com.lusifer.crmeb.component.security.handle.RestfulAccessDeniedHandler;
import com.lusifer.crmeb.config.properties.SecurityProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/** Spring Security 权限配置 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true) // 开启方法级别的权限控制
@RequiredArgsConstructor
public class ProjectSecurityAutoConfiguration {

  private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
  private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
  private final SecurityProperties securityProperties;
  private final UserDetailsService userDetailsService;

  @Bean
  protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

    // 忽略的路径
    http.authorizeHttpRequests(
        requestMatcherRegistry ->
            requestMatcherRegistry
                .requestMatchers(securityProperties.getIgnoreUrls().toArray(new String[0]))
                .permitAll()
                .anyRequest()
                .authenticated());

    http
        // 由于使用的是 JWT,我们这里不需要 CSRF
        .csrf(AbstractHttpConfigurer::disable)
        // 禁用 Session
        .sessionManagement(
            configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

    // 添加自定义未授权和未登录结果返回
    http.exceptionHandling(
        customizer ->
            customizer
                // 处理未授权
                .accessDeniedHandler(restfulAccessDeniedHandler)
                // 处理未登录
                .authenticationEntryPoint(restAuthenticationEntryPoint));
    // JWT 校验过滤器
    http.addFilterBefore(
        new JwtValidationFilter(userDetailsService, securityProperties.getJwt().getKey()),
        UsernamePasswordAuthenticationFilter.class);

    return http.build();
  }

  /**
   * AuthenticationManager 手动注入
   *
   * @param authenticationConfiguration 认证配置
   */
  @Bean
  public AuthenticationManager authenticationManager(
      AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

  /** 强散列哈希加密实现 */
  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
}
  • 添加配置到 org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.lusifer.crmeb.config.ProjectMyBatisPlusAutoConfiguration
com.lusifer.crmeb.config.ProjectOpenApiAutoConfiguration
com.lusifer.crmeb.config.ProjectSecurityAutoConfiguration

自定义用户对象

SysUserMapper

  • Mapper

package com.lusifer.crmeb.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lusifer.crmeb.entity.SysUser;

/**
 * 用户表 Mapper 接口
 *
 * @author Lusifer
 * @since 2026-05-04
 */
public interface SysUserMapper extends BaseMapper<SysUser> {
    SysUser selectByUsername(String username);
}
  • Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.crmeb.mapper.SysUserMapper">
    <select id="selectByUsername" resultType="com.lusifer.crmeb.entity.SysUser">
        select *
        from sys_user
        where username = #{username}
    </select>
</mapper>

SysMenuMapper

  • Mapper

package com.lusifer.crmeb.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lusifer.crmeb.entity.SysMenu;

import java.util.Set;

/**
 * 用户权限表 Mapper 接口
 *
 * @author Lusifer
 * @since 2026-05-04
 */
public interface SysMenuMapper extends BaseMapper<SysMenu> {
  Set<String> getMenuPermission(Long useId);
}
  • Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.crmeb.mapper.SysMenuMapper">
    <select id="getMenuPermission" resultType="java.lang.String">
        SELECT DISTINCT p.`permission`
        FROM sys_user_role ar
                 LEFT JOIN sys_role r ON ar.role_id = r.role_id
                 LEFT JOIN sys_role_menu rp ON r.role_id = rp.role_id
                 LEFT JOIN sys_menu p ON rp.menu_id = p.menu_id
        WHERE ar.user_id = #{userId}
          AND p.`permission` IS NOT NULL
    </select>
</mapper>

SysUserRoleMapper

  • Mapper

package com.lusifer.crmeb.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lusifer.crmeb.entity.SysUserRole;

import java.util.Set;

/**
 * 用户和角色关系表 Mapper 接口
 *
 * @author Lusifer
 * @since 2026-05-04
 */
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
  Set<String> listRoleKeyByUserId(Long userId);

  Integer getMaximumDataScope(Set<String> roles);
}
  • Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.crmeb.mapper.SysUserRoleMapper">
    <select id="listRoleKeyByUserId" resultType="java.lang.String">
        SELECT DISTINCT r.role_key
        FROM sys_user_role ur
                 LEFT JOIN sys_role r ON r.role_id = ur.role_id
        WHERE ur.user_id = #{userId}
    </select>
    <select id="getMaximumDataScope" resultType="java.lang.Integer">
        SELECT
        min(data_scope)
        FROM
        sys_role
        <where>
            <choose>
                <when test="roles!=null and roles.size>0">
                    AND role_key IN
                    <foreach collection="roles" item="role" separator="," open="(" close=")">
                        #{role}
                    </foreach>
                </when>
                <otherwise>
                    role_id = -1
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>

UserDetailsServiceImpl

  • com.lusifer.crmeb.component.security.service.UserDetailsServiceImpl

package com.lusifer.crmeb.component.security.service;

import cn.hutool.core.bean.BeanUtil;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.entity.SysUser;
import com.lusifer.crmeb.exception.base.ServiceException;
import com.lusifer.crmeb.exception.enums.SysExceptionEnum;
import com.lusifer.crmeb.mapper.SysMenuMapper;
import com.lusifer.crmeb.mapper.SysUserMapper;
import com.lusifer.crmeb.mapper.SysUserRoleMapper;
import com.lusifer.crmeb.rule.enums.StatusEnum;
import java.util.HashSet;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/** 用户详情服务 */
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

  private final SysUserMapper sysUserMapper;
  private final SysMenuMapper sysMenuMapper;
  private final SysUserRoleMapper sysUserRoleMapper;

  @Override
  @Cacheable(value = "user_details", key = "#username", unless = "#result == null ")
  public SysUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

    // 获取登录用户信息
    SysUser user = sysUserMapper.selectByUsername(username);

    // 用户不存在
    if (BeanUtil.isEmpty(user)) {
      throw new ServiceException(SysExceptionEnum.USER_NOT_EXIST);
    }
    Long userId = user.getUserId();

    // 用户停用
    if (StatusEnum.DISABLE.getKey().equals(user.getStatusFlag())) {
      throw new ServiceException(SysExceptionEnum.USER_DISABLED);
    }

    // 获取角色
    Set<String> roles = sysUserRoleMapper.listRoleKeyByUserId(userId);

    // 获取数据范围标识
    Integer dataScope = sysUserRoleMapper.getMaximumDataScope(roles);

    Set<String> permissions = new HashSet<>();
    // 如果 roles 包含 root 则拥有所有权限
    if (roles.contains("ROOT")) {
      permissions.add("*:*:*");
    } else {
      // 获取菜单权限标识
      permissions = sysMenuMapper.getMenuPermission(userId);
      // 过滤空字符串
      permissions.remove("");
    }

    return new SysUserDetails(user, permissions, roles, username, dataScope);
  }
}

登录验证

创建包结构

com.lusifer.crmeb.auth
    controller
    request
    response

登录请求对象

  • com.lusifer.crmeb.auth.request.SysUserLoginRequest

package com.lusifer.crmeb.auth.request;

import lombok.Data;

/** 用户登录参数 */
@Data
public class SysUserLoginRequest {

  /** 用户 */
  private String username;

  /** 密码 */
  private String password;

  /** 图形验证码 */
  private String code;

  /** 唯一标识 */
  private String uuid = "";
}

登录响应对象

  • com.lusifer.crmeb.auth.response.SysUserLoginVo

package com.lusifer.crmeb.auth.response;

import lombok.Data;

@Data
public class SysUserLoginVo {

  /** token */
  private String token;

  /** token 类型,如:Bearer */
  private String tokenType;

  /** 过期时间(单位:秒),如:604800 */
  private Long expiration;

  /** 刷新 token */
  private String refreshToken;
}
  • com.lusifer.crmeb.auth.response.SysUserInfoVo

package com.lusifer.crmeb.auth.response;

import cn.hutool.core.date.DatePattern;
import cn.hutool.json.JSONArray;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;

@Data
public class SysUserInfoVo implements Serializable {

  @Serial private static final long serialVersionUID = 1L;

  /** 用户ID */
  private Long userId;

  /** 所属部门 */
  private Long deptId;

  /** 所属部门名称 */
  private String deptName;

  /** 所属岗位 */
  private JSONArray postIds;

  /** 登陆用户名 */
  private String username;

  /** 头像 */
  private String icon;

  /** 邮箱 */
  private String email;

  /** 手机号码 */
  private String phone;

  /** 昵称 */
  private String nickName;

  /** 性别 */
  private String sex;

  /** 备注信息 */
  private String note;

  /** 最后登陆IP */
  private String loginIp;

  /** 最后登录时间 */
  @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8")
  private Date loginTime;

  /** 启用状态,0:禁用 1:启用 */
  private String statusFlag;
}
  • com.lusifer.crmeb.auth.response.UserInfoResult

package com.lusifer.crmeb.auth.response;

import java.util.Set;
import lombok.Data;

@Data
public class UserInfoResult {
  /** 用户名 */
  private String username;

  /** 用户详细信息 */
  private SysUserInfoVo userInfo;

  /** 权限集 */
  private Set<String> permissions;

  /** 角色集 */
  private Set<String> roles;
}

登录相关逻辑

  • com.lusifer.crmeb.component.security.util.JwtUtils

package com.lusifer.crmeb.component.security.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.RegisteredPayload;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.config.properties.SecurityProperties;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

/** JWT 工具类 */
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtils {

  private final SecurityProperties securityProperties;

  /** 权限(角色 Code)集合 */
  private static final String AUTHORITIES = "authorities";
  
  /**
   * 生成 JWT Token
   *
   * @param authentication 用户认证信息
   * @return Token 字符串
   */
  public String createToken(Authentication authentication) {
    SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
    Map<String, Object> payload = new HashMap<>();

    // Claims 中添加角色信息
    Set<String> roles =
        userDetails.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toSet());

    payload.put(AUTHORITIES, roles);

    Date now = new Date();
    // JWT 的签发时间
    payload.put(RegisteredPayload.ISSUED_AT, now);

    // 设置过期时间 -1 表示永不过期
    Long ttl = securityProperties.getJwt().getTtl();
    if (ttl != -1) {
      Date expiration = DateUtil.offsetSecond(now, ttl.intValue());
      // JWT 的过期时间,这个过期时间必须要大于签发时间
      payload.put(RegisteredPayload.EXPIRES_AT, expiration);
    }

    // JWT 所面向的用户
    payload.put(RegisteredPayload.SUBJECT, authentication.getName());
    // JWT 的唯一身份标识
    payload.put(RegisteredPayload.JWT_ID, IdUtil.simpleUUID());

    byte[] key = securityProperties.getJwt().getKey().getBytes();
    return JWTUtil.createToken(payload, key);
  }
}
  • com.lusifer.crmeb.component.security.util.SecurityUtils

package com.lusifer.crmeb.component.security.util;

import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.entity.SysUser;
import com.lusifer.crmeb.exception.base.ServiceException;
import com.lusifer.crmeb.exception.enums.SecurityExceptionEnum;
import java.util.Set;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;

/** 安全服务工具类 */
public class SecurityUtils {

  public static UserDetails getUserDetails() {
    try {
      return (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    } catch (Exception e) {
      throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
    }
  }

  public static SysUserDetails getSysUserDetails() {
    try {
      return (SysUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    } catch (Exception e) {
      throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
    }
  }

  /**
   * 是否超级管理员
   *
   * <p>超级管理员忽视任何权限判断
   */
  public static boolean isRoot() {
    Set<String> roles = getRoles();
    return roles.contains("ROOT");
  }

  /**
   * 获取当前 用户id
   *
   * @return 用户id
   */
  public static Long getUserId() {
    return getSysUser().getUserId();
  }

  private static Set<String> getRoles() {
    return getSysUserDetails().getRoles();
  }

  public static SysUser getSysUser() {
    try {
      SecurityContext ctx = SecurityContextHolder.getContext();
      Authentication auth = ctx.getAuthentication();
      SysUserDetails sysUserDetails = (SysUserDetails) auth.getPrincipal();
      return sysUserDetails.getSysUser();
    } catch (Exception e) {
      throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
    }
  }

  public static Integer getDataScope() {
    return getSysUserDetails().getDataScope();
  }

  public static Long getDeptId() {
    return getSysUserDetails().getSysUser().getDeptId();
  }
}
  • com.lusifer.crmeb.service.SysUserService

package com.lusifer.crmeb.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.lusifer.crmeb.auth.response.SysUserLoginVo;
import com.lusifer.crmeb.auth.response.UserInfoResult;
import com.lusifer.crmeb.entity.SysUser;

/**
 * 用户表 服务类
 *
 * @author Lusifer
 * @since 2026-05-04
 */
public interface SysUserService extends IService<SysUser> {
  /**
   * 登录
   *
   * @param username 账号
   * @param password 密码
   * @return 用户登录视图对象
   */
  SysUserLoginVo login(String username, String password);

  /**
   * 获取用户信息
   *
   * @return 用户信息视图对象
   */
  UserInfoResult getInfo();
}
  • com.lusifer.crmeb.service.impl.SysUserServiceImpl

package com.lusifer.crmeb.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lusifer.crmeb.auth.response.SysUserInfoVo;
import com.lusifer.crmeb.auth.response.SysUserLoginVo;
import com.lusifer.crmeb.auth.response.UserInfoResult;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.component.security.util.JwtUtils;
import com.lusifer.crmeb.component.security.util.SecurityUtils;
import com.lusifer.crmeb.config.properties.SecurityProperties;
import com.lusifer.crmeb.entity.SysUser;
import com.lusifer.crmeb.exception.base.ServiceException;
import com.lusifer.crmeb.exception.enums.SysExceptionEnum;
import com.lusifer.crmeb.mapper.SysUserMapper;
import com.lusifer.crmeb.rule.constants.SecurityConstants;
import com.lusifer.crmeb.service.SysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

/**
 * 用户表 服务实现类
 *
 * @author Lusifer
 * @since 2026-05-04
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
    implements SysUserService {

  private final SecurityProperties securityProperties;
  private final AuthenticationManager authenticationManager;
  private final JwtUtils jwtUtils;

  @Override
  public SysUserLoginVo login(String username, String password) {

    SysUserLoginVo res = new SysUserLoginVo();
    String accessToken;
    Long expiration = securityProperties.getJwt().getTtl();
    // 密码需要客户端加密后传递
    try {
      // 该方法会去调用 UserDetailsServiceImpl.loadUserByUsername
      Authentication authenticate =
          authenticationManager.authenticate(
              new UsernamePasswordAuthenticationToken(username, password));
      SecurityContextHolder.getContext().setAuthentication(authenticate);

      // 认证成功后生成 JWT 令牌
      accessToken = jwtUtils.createToken(authenticate);

      res.setToken(accessToken);
      res.setExpiration(expiration);
      res.setTokenType(CharSequenceUtil.trim(SecurityConstants.JWT_TOKEN_PREFIX));

      return res;
    } catch (Exception e) {
      log.error("登录异常:{}", e.getMessage());
      throw new ServiceException(SysExceptionEnum.ERROR_USERNAME_OR_PASSWORD);
    }
  }

  @Override
  public UserInfoResult getInfo() {
    SysUserDetails sysUserDetails = SecurityUtils.getSysUserDetails();
    SysUser user = sysUserDetails.getSysUser();
    UserInfoResult result = new UserInfoResult();
    result.setUsername(user.getUsername());
    result.setPermissions(sysUserDetails.getPermissions());
    result.setRoles(sysUserDetails.getRoles());
    result.setUserInfo(BeanUtil.copyProperties(user, SysUserInfoVo.class));
    return result;
  }
}

登录控制器

  • com.lusifer.crmeb.auth.controller.LoginController

package com.lusifer.crmeb.auth.controller;

import com.lusifer.crmeb.auth.request.SysUserLoginRequest;
import com.lusifer.crmeb.auth.response.SysUserLoginVo;
import com.lusifer.crmeb.auth.response.UserInfoResult;
import com.lusifer.crmeb.rule.pojo.response.ResponseData;
import com.lusifer.crmeb.rule.pojo.response.SuccessResponseData;
import com.lusifer.crmeb.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

/** 登录控制器 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
@Tag(name = "认证中心")
public class LoginController {

  private final SysUserService sysUserService;

  @Operation(summary = "登录")
  @PostMapping(value = "/login")
  public ResponseData<SysUserLoginVo> login(@RequestBody SysUserLoginRequest request) {
    return new SuccessResponseData<>(
        sysUserService.login(request.getUsername(), request.getPassword()));
  }

  @Operation(summary = "获取当前用户信息")
  @GetMapping(value = "/info")
  public ResponseData<UserInfoResult> getInfo() {
    UserInfoResult result = sysUserService.getInfo();
    return new SuccessResponseData<>(result);
  }

  @Operation(summary = "注销")
  @PostMapping(value = "/logout")
  public ResponseData<?> logout(HttpServletRequest request) {
    // 需要 将当前用户token 设置无效
    SecurityContextHolder.clearContext();
    return new SuccessResponseData<>();
  }
}

解决密码问题

  • 如果忘记密码,可以使用 Spring Security 提供的 PasswordEncoder 工具进行密码加密处理,将生成的新密码复制到数据库

  • 创建 JUnit 测试类 com.lusifer.crmeb.test.PasswordEncoderTests

package com.lusifer.crmeb.test;

import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;

@SpringBootTest
class PasswordEncoderTests {
  @Resource private PasswordEncoder passwordEncoder;

  @Test
  void testEncoderPassword() {
    System.out.println(passwordEncoder.encode("123456"));
  }
}

使用 API Post 请求

登录

获取当前用户

  • 没有权限的效果

  • 要访问该资源需要在请求头上追加访问令牌,在 Header 中追加如下参数

    • 参数名:Authorization

    • 参数值:Bearer + 空格 + 令牌