mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-08 17:12:28 +08:00
Compare commits
57 Commits
v3.6.3_spr
...
v3.6.3
| Author | SHA1 | Date | |
|---|---|---|---|
| b15e6e0422 | |||
| 8f99a80352 | |||
| 936a87e738 | |||
| 2af165b201 | |||
| 3d5efc07ad | |||
| 21e8d640d2 | |||
| b0ce456909 | |||
| b8e1306955 | |||
| 37a38ad288 | |||
| 9ee05c9510 | |||
| 275a68bb6a | |||
| f3f70e8549 | |||
| e15e9d80c4 | |||
| f7538c1ed8 | |||
| a9dba08a8d | |||
| 4a857680d0 | |||
| a47d0984dc | |||
| e333b126b6 | |||
| 3618842f44 | |||
| fd8c848c9e | |||
| 09614a0239 | |||
| 6fe8f1d81a | |||
| 0bd7f715c4 | |||
| 041d88161e | |||
| 79a62aa056 | |||
| b86b4d9676 | |||
| aeaac80012 | |||
| e0ef20cf08 | |||
| 169a66f5dd | |||
| 7e39b31123 | |||
| 18765450a6 | |||
| dff8c84d9c | |||
| d962c34846 | |||
| cd9794d818 | |||
| 5034b7cf18 | |||
| fdde84c68a | |||
| 4c54ff6f52 | |||
| de3285dc1b | |||
| 7f0c035c4c | |||
| 43593e8def | |||
| 48b0b608d8 | |||
| 69287a772b | |||
| 337d5a9489 | |||
| cfeb81ee1e | |||
| 09f92f01aa | |||
| 6d1094936b | |||
| 8836a2793a | |||
| c36ece8923 | |||
| a82213b90c | |||
| 98facdd2ee | |||
| d080b0b5ea | |||
| 338902ca0c | |||
| 7ae6a11cf0 | |||
| cdbe1cb1a9 | |||
| 090f790df4 | |||
| 82d051f388 | |||
| 5a3631c332 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -10,5 +10,6 @@ rebel.xml
|
|||||||
## front
|
## front
|
||||||
**/*.lock
|
**/*.lock
|
||||||
os_del.cmd
|
os_del.cmd
|
||||||
|
os_del_doc.cmd
|
||||||
*.log
|
.svn
|
||||||
|
derby.log
|
||||||
|
|||||||
@ -7,13 +7,13 @@
|
|||||||
JEECG BOOT Low Code Development Platform
|
JEECG BOOT Low Code Development Platform
|
||||||
===============
|
===============
|
||||||
|
|
||||||
当前最新版本: 3.6.1(发布日期:2023-12-11)
|
当前最新版本: 3.6.3(发布日期:2024-03-11)
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||||
[](http://www.jeecg.com)
|
[](http://www.jeecg.com)
|
||||||
[](https://jeecg.blog.csdn.net)
|
[](https://jeecg.blog.csdn.net)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
|
|
||||||
|
|||||||
@ -7,13 +7,13 @@
|
|||||||
JEECG BOOT 低代码开发平台
|
JEECG BOOT 低代码开发平台
|
||||||
===============
|
===============
|
||||||
|
|
||||||
当前最新版本: 3.6.1(发布日期:2023-12-11)
|
当前最新版本: 3.6.3(发布日期:2024-03-11)
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||||
[](http://jeecg.com/aboutusIndex)
|
[](http://jeecg.com/aboutusIndex)
|
||||||
[](https://jeecg.blog.csdn.net)
|
[](https://jeecg.blog.csdn.net)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||||
|
|
||||||
@ -49,6 +49,7 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||||||
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [jeecg-boot](https://github.com/jeecgboot/jeecg-boot) |
|
| Github | [jeecgboot-vue3](https://github.com/jeecgboot/jeecgboot-vue3) | [jeecg-boot](https://github.com/jeecgboot/jeecg-boot) |
|
||||||
| 码云 | [jeecgboot-vue3](https://gitee.com/jeecg/jeecgboot-vue3) | [jeecg-boot](https://gitee.com/jeecg/jeecg-boot) |
|
| 码云 | [jeecgboot-vue3](https://gitee.com/jeecg/jeecgboot-vue3) | [jeecg-boot](https://gitee.com/jeecg/jeecg-boot) |
|
||||||
|
|
||||||
|
> 官方已推出 `SpringBoot3+JDK17版本` [分支源码下载](https://github.com/jeecgboot/jeecg-boot/tree/springboot3) | [升级SpringBoot3博客](https://blog.csdn.net/zhangdaiscott/article/details/134805602)
|
||||||
|
|
||||||
#### 项目说明
|
#### 项目说明
|
||||||
|
|
||||||
@ -57,7 +58,6 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||||||
| `jeecgboot-vue3` | 前端源码 (Vue3版本) |
|
| `jeecgboot-vue3` | 前端源码 (Vue3版本) |
|
||||||
| `jeecg-boot` | 后端JAVA源码(支持微服务) |
|
| `jeecg-boot` | 后端JAVA源码(支持微服务) |
|
||||||
| `jeecg-uniapp` | [APP开发框架,一份代码多终端适配,同时支持APP、小程序、H5](https://github.com/jeecgboot/jeecg-uniapp) |
|
| `jeecg-uniapp` | [APP开发框架,一份代码多终端适配,同时支持APP、小程序、H5](https://github.com/jeecgboot/jeecg-uniapp) |
|
||||||
| `SpringBoot3+JDK17 后端分支` | [分支源码](https://github.com/jeecgboot/jeecg-boot/tree/springboot3) [升级博客](https://blog.csdn.net/zhangdaiscott/article/details/134805602) |
|
|
||||||
| `更多开源项目` | [更多底层源码下载](http://jeecg.com/download) |
|
| `更多开源项目` | [更多底层源码下载](http://jeecg.com/download) |
|
||||||
|
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ Docker快速启动项目
|
|||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
- 项目官网: [http://www.jeecg.com](http://www.jeecg.com)
|
- 项目官网: [http://www.jeecg.com](http://www.jeecg.com)
|
||||||
- 开发文档: [http://help.jeecg.com](http://help.jeecg.com)
|
- 开发文档: [https://help.jeecg.com](https://help.jeecg.com)
|
||||||
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [常见问题 ](http://www.jeecg.com/doc/qa) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [1分钟低代码体验](https://my.oschina.net/jeecg/blog/3083313)
|
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [常见问题 ](http://www.jeecg.com/doc/qa) | [视频教程](https://space.bilibili.com/454617261/channel/series) | [1分钟低代码体验](https://my.oschina.net/jeecg/blog/3083313)
|
||||||
|
|
||||||
- 在线演示 : [Vue3演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex) | [敲敲云零代码](https://qiaoqiaoyun.com)
|
- 在线演示 : [Vue3演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex) | [敲敲云零代码](https://qiaoqiaoyun.com)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,5 +1,6 @@
|
|||||||
CREATE database if NOT EXISTS `nacos` default character set utf8mb4 collate utf8mb4_general_ci;
|
CREATE database if NOT EXISTS `nacos` default character set utf8mb4 collate utf8mb4_general_ci;
|
||||||
use `nacos`;
|
use `nacos`;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Navicat Premium Data Transfer
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ use `nacos`;
|
|||||||
Target Server Version : 50738
|
Target Server Version : 50738
|
||||||
File Encoding : 65001
|
File Encoding : 65001
|
||||||
|
|
||||||
Date: 01/03/2024 15:36:53
|
Date: 06/03/2024 15:44:59
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SET NAMES utf8mb4;
|
SET NAMES utf8mb4;
|
||||||
@ -24,25 +25,25 @@ SET FOREIGN_KEY_CHECKS = 0;
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `config_info`;
|
DROP TABLE IF EXISTS `config_info`;
|
||||||
CREATE TABLE `config_info` (
|
CREATE TABLE `config_info` (
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||||
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||||
`group_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`group_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||||
`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'md5',
|
`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'md5',
|
||||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||||
`src_user` text CHARACTER SET utf8 COLLATE utf8_bin NULL COMMENT 'source user',
|
`src_user` text CHARACTER SET utf8 COLLATE utf8_bin NULL COMMENT 'source user',
|
||||||
`src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip',
|
`src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip',
|
||||||
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT '租户字段',
|
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT '租户字段',
|
||||||
`c_desc` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`c_desc` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`c_use` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`c_use` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`effect` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`effect` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`type` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`type` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`c_schema` text CHARACTER SET utf8 COLLATE utf8_bin NULL,
|
`c_schema` text CHARACTER SET utf8 COLLATE utf8_bin NULL,
|
||||||
`encrypted_data_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '加密key',
|
`encrypted_data_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '加密key',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_configinfo_datagrouptenant`(`data_id`, `group_id`, `tenant_id`) USING BTREE
|
UNIQUE INDEX `uk_configinfo_datagrouptenant`(`data_id`, `group_id`, `tenant_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'config_info' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'config_info' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -72,16 +73,16 @@ INSERT INTO `config_info` VALUES (42, 'jeecg-sharding-multi.yaml', 'DEFAULT_GROU
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `config_info_aggr`;
|
DROP TABLE IF EXISTS `config_info_aggr`;
|
||||||
CREATE TABLE `config_info_aggr` (
|
CREATE TABLE `config_info_aggr` (
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||||
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||||
`group_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
`group_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
||||||
`datum_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'datum_id',
|
`datum_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'datum_id',
|
||||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '内容',
|
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '内容',
|
||||||
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
|
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
|
||||||
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT '租户字段',
|
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT '租户字段',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_configinfoaggr_datagrouptenantdatum`(`data_id`, `group_id`, `tenant_id`, `datum_id`) USING BTREE
|
UNIQUE INDEX `uk_configinfoaggr_datagrouptenantdatum`(`data_id`, `group_id`, `tenant_id`, `datum_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '增加租户字段' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '增加租户字段' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -93,21 +94,21 @@ CREATE TABLE `config_info_aggr` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `config_info_beta`;
|
DROP TABLE IF EXISTS `config_info_beta`;
|
||||||
CREATE TABLE `config_info_beta` (
|
CREATE TABLE `config_info_beta` (
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||||
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||||
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
||||||
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'app_name',
|
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'app_name',
|
||||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||||
`beta_ips` varchar(1024) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'betaIps',
|
`beta_ips` varchar(1024) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'betaIps',
|
||||||
`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'md5',
|
`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'md5',
|
||||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||||
`src_user` text CHARACTER SET utf8 COLLATE utf8_bin NULL COMMENT 'source user',
|
`src_user` text CHARACTER SET utf8 COLLATE utf8_bin NULL COMMENT 'source user',
|
||||||
`src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip',
|
`src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip',
|
||||||
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT '租户字段',
|
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT '租户字段',
|
||||||
`encrypted_data_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '加密key',
|
`encrypted_data_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '加密key',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_configinfobeta_datagrouptenant`(`data_id`, `group_id`, `tenant_id`) USING BTREE
|
UNIQUE INDEX `uk_configinfobeta_datagrouptenant`(`data_id`, `group_id`, `tenant_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'config_info_beta' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'config_info_beta' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -119,21 +120,21 @@ CREATE TABLE `config_info_beta` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `config_info_tag`;
|
DROP TABLE IF EXISTS `config_info_tag`;
|
||||||
CREATE TABLE `config_info_tag` (
|
CREATE TABLE `config_info_tag` (
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||||
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||||
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
||||||
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT 'tenant_id',
|
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT 'tenant_id',
|
||||||
`tag_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'tag_id',
|
`tag_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'tag_id',
|
||||||
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'app_name',
|
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'app_name',
|
||||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||||
`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'md5',
|
`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'md5',
|
||||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||||
`src_user` text CHARACTER SET utf8 COLLATE utf8_bin NULL COMMENT 'source user',
|
`src_user` text CHARACTER SET utf8 COLLATE utf8_bin NULL COMMENT 'source user',
|
||||||
`src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip',
|
`src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip',
|
||||||
`encrypted_data_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '加密key',
|
`encrypted_data_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '加密key',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_configinfotag_datagrouptenanttag`(`data_id`, `group_id`, `tenant_id`, `tag_id`) USING BTREE
|
UNIQUE INDEX `uk_configinfotag_datagrouptenanttag`(`data_id`, `group_id`, `tenant_id`, `tag_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'config_info_tag' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'config_info_tag' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -145,16 +146,16 @@ CREATE TABLE `config_info_tag` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `config_tags_relation`;
|
DROP TABLE IF EXISTS `config_tags_relation`;
|
||||||
CREATE TABLE `config_tags_relation` (
|
CREATE TABLE `config_tags_relation` (
|
||||||
`id` bigint(20) NOT NULL COMMENT 'id',
|
`id` bigint(20) NOT NULL COMMENT 'id',
|
||||||
`tag_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'tag_name',
|
`tag_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'tag_name',
|
||||||
`tag_type` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'tag_type',
|
`tag_type` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'tag_type',
|
||||||
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||||
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
||||||
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT 'tenant_id',
|
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT 'tenant_id',
|
||||||
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
|
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
PRIMARY KEY (`nid`) USING BTREE,
|
PRIMARY KEY (`nid`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_configtagrelation_configidtag`(`id`, `tag_name`, `tag_type`) USING BTREE,
|
UNIQUE INDEX `uk_configtagrelation_configidtag`(`id`, `tag_name`, `tag_type`) USING BTREE,
|
||||||
INDEX `idx_tenant_id`(`tenant_id`) USING BTREE
|
INDEX `idx_tenant_id`(`tenant_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'config_tag_relation' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'config_tag_relation' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -166,18 +167,18 @@ CREATE TABLE `config_tags_relation` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `group_capacity`;
|
DROP TABLE IF EXISTS `group_capacity`;
|
||||||
CREATE TABLE `group_capacity` (
|
CREATE TABLE `group_capacity` (
|
||||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
|
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
|
||||||
`quota` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '配额,0表示使用默认值',
|
`quota` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '配额,0表示使用默认值',
|
||||||
`usage` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '使用量',
|
`usage` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '使用量',
|
||||||
`max_size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
|
`max_size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
|
||||||
`max_aggr_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '聚合子配置最大个数,,0表示使用默认值',
|
`max_aggr_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '聚合子配置最大个数,,0表示使用默认值',
|
||||||
`max_aggr_size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
|
`max_aggr_size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
|
||||||
`max_history_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最大变更历史数量',
|
`max_history_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最大变更历史数量',
|
||||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_group_id`(`group_id`) USING BTREE
|
UNIQUE INDEX `uk_group_id`(`group_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '集群、各Group容量信息表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '集群、各Group容量信息表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -189,24 +190,24 @@ CREATE TABLE `group_capacity` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `his_config_info`;
|
DROP TABLE IF EXISTS `his_config_info`;
|
||||||
CREATE TABLE `his_config_info` (
|
CREATE TABLE `his_config_info` (
|
||||||
`id` bigint(20) UNSIGNED NOT NULL,
|
`id` bigint(20) UNSIGNED NOT NULL,
|
||||||
`nid` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
`nid` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
`data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
||||||
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
`group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
||||||
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'app_name',
|
`app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'app_name',
|
||||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
||||||
`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
|
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
|
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
|
||||||
`src_user` text CHARACTER SET utf8 COLLATE utf8_bin NULL,
|
`src_user` text CHARACTER SET utf8 COLLATE utf8_bin NULL,
|
||||||
`src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`op_type` char(10) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
`op_type` char(10) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
|
||||||
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT '租户字段',
|
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT '租户字段',
|
||||||
`encrypted_data_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '加密key',
|
`encrypted_data_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '加密key',
|
||||||
PRIMARY KEY (`nid`) USING BTREE,
|
PRIMARY KEY (`nid`) USING BTREE,
|
||||||
INDEX `idx_gmt_create`(`gmt_create`) USING BTREE,
|
INDEX `idx_gmt_create`(`gmt_create`) USING BTREE,
|
||||||
INDEX `idx_gmt_modified`(`gmt_modified`) USING BTREE,
|
INDEX `idx_gmt_modified`(`gmt_modified`) USING BTREE,
|
||||||
INDEX `idx_did`(`data_id`) USING BTREE
|
INDEX `idx_did`(`data_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 50 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '多租户改造' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 50 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '多租户改造' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -234,10 +235,10 @@ INSERT INTO `his_config_info` VALUES (41, 49, 'jeecg-gateway-dev.yaml', 'DEFAULT
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `permissions`;
|
DROP TABLE IF EXISTS `permissions`;
|
||||||
CREATE TABLE `permissions` (
|
CREATE TABLE `permissions` (
|
||||||
`role` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
`role` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
`resource` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
`resource` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
`action` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
`action` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
UNIQUE INDEX `uk_role_permission`(`role`, `resource`, `action`) USING BTREE
|
UNIQUE INDEX `uk_role_permission`(`role`, `resource`, `action`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -249,9 +250,9 @@ CREATE TABLE `permissions` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `roles`;
|
DROP TABLE IF EXISTS `roles`;
|
||||||
CREATE TABLE `roles` (
|
CREATE TABLE `roles` (
|
||||||
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
`role` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
`role` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
UNIQUE INDEX `uk_username_role`(`username`, `role`) USING BTREE
|
UNIQUE INDEX `uk_username_role`(`username`, `role`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -264,18 +265,18 @@ INSERT INTO `roles` VALUES ('nacos', 'ROLE_ADMIN');
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `tenant_capacity`;
|
DROP TABLE IF EXISTS `tenant_capacity`;
|
||||||
CREATE TABLE `tenant_capacity` (
|
CREATE TABLE `tenant_capacity` (
|
||||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID',
|
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID',
|
||||||
`quota` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '配额,0表示使用默认值',
|
`quota` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '配额,0表示使用默认值',
|
||||||
`usage` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '使用量',
|
`usage` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '使用量',
|
||||||
`max_size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
|
`max_size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
|
||||||
`max_aggr_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '聚合子配置最大个数',
|
`max_aggr_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '聚合子配置最大个数',
|
||||||
`max_aggr_size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
|
`max_aggr_size` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
|
||||||
`max_history_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最大变更历史数量',
|
`max_history_count` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最大变更历史数量',
|
||||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_tenant_id`(`tenant_id`) USING BTREE
|
UNIQUE INDEX `uk_tenant_id`(`tenant_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '租户容量信息表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '租户容量信息表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -287,17 +288,17 @@ CREATE TABLE `tenant_capacity` (
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `tenant_info`;
|
DROP TABLE IF EXISTS `tenant_info`;
|
||||||
CREATE TABLE `tenant_info` (
|
CREATE TABLE `tenant_info` (
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||||
`kp` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'kp',
|
`kp` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'kp',
|
||||||
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT 'tenant_id',
|
`tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT 'tenant_id',
|
||||||
`tenant_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT 'tenant_name',
|
`tenant_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT '' COMMENT 'tenant_name',
|
||||||
`tenant_desc` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'tenant_desc',
|
`tenant_desc` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'tenant_desc',
|
||||||
`create_source` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'create_source',
|
`create_source` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'create_source',
|
||||||
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
|
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
|
||||||
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
|
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_tenant_info_kptenantid`(`kp`, `tenant_id`) USING BTREE,
|
UNIQUE INDEX `uk_tenant_info_kptenantid`(`kp`, `tenant_id`) USING BTREE,
|
||||||
INDEX `idx_tenant_id`(`tenant_id`) USING BTREE
|
INDEX `idx_tenant_id`(`tenant_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'tenant_info' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = 'tenant_info' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@ -311,10 +312,10 @@ INSERT INTO `tenant_info` VALUES (2, '1', 'efc4e412-b1a1-498f-ba01-b31807649a9a'
|
|||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
DROP TABLE IF EXISTS `users`;
|
DROP TABLE IF EXISTS `users`;
|
||||||
CREATE TABLE `users` (
|
CREATE TABLE `users` (
|
||||||
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
`password` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
`password` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
`enabled` tinyint(1) NOT NULL,
|
`enabled` tinyint(1) NOT NULL,
|
||||||
PRIMARY KEY (`username`) USING BTREE
|
PRIMARY KEY (`username`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,11 +0,0 @@
|
|||||||
-- 新增风格一对多内嵌和Tab风格
|
|
||||||
INSERT INTO sys_permission (id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
|
|
||||||
VALUES ('1691031996d5931315212', '1455100420297859074', 'AUTO在线一对多内嵌', '/online/cgformInnerTableList/:id', 'super/online/cgform/auto/innerTable/OnlCgformInnerTableList', 1, '', NULL, 1, NULL, '0', 1.00, 0, NULL, 1, 0, 1, 0, NULL, 'admin', '2023-08-14 18:20:20', 'admin', '2023-08-14 18:46:18', 0, 0, NULL, 0);
|
|
||||||
INSERT INTO sys_permission (id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
|
|
||||||
VALUES ('1691031996d5931315213', '1455100420297859074', 'AUTO在线Tab风格', '/online/cgformTabList/:id', 'super/online/cgform/auto/tab/OnlCgformTabList', 1, '', NULL, 1, NULL, '0', 1.00, 0, NULL, 1, 0, 1, 0, NULL, 'admin', '2023-08-14 18:20:20', 'admin', '2023-08-14 18:46:18', 0, 0, NULL, 0);
|
|
||||||
|
|
||||||
-- 【安全】online敏感接口,加权限注解(sql解析接口、同步数据库接口、导入表接口)
|
|
||||||
INSERT INTO sys_permission (id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1699374704168534017', '1460888189937176577', 'SQL解析', NULL, NULL, 0, NULL, NULL, 2, 'online:report:parseSql', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-09-06 18:51:17', NULL, NULL, 0, 0, '1', 0);
|
|
||||||
INSERT INTO sys_permission (id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1699374509749960705', '1455101470794850305', '查询数据库表名', NULL, NULL, 0, NULL, NULL, 2, 'online:form:queryTables', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-09-06 18:50:31', NULL, NULL, 0, 0, '1', 0);
|
|
||||||
INSERT INTO sys_permission (id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) VALUES ('1699374269152100354', '1455101470794850305', '同步数据库', NULL, NULL, 0, NULL, NULL, 2, 'online:form:syncDb', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2023-09-06 18:49:33', NULL, NULL, 0, 0, '1', 0);
|
|
||||||
update sys_permission set is_leaf=0 where id in ('1460888189937176577','1455101470794850305');
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
CREATE TABLE `oauth2_registered_client` (
|
|
||||||
`id` varchar(100) NOT NULL,
|
|
||||||
`client_id` varchar(100) NOT NULL,
|
|
||||||
`client_id_issued_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`client_secret` varchar(200) DEFAULT NULL,
|
|
||||||
`client_secret_expires_at` timestamp NULL DEFAULT NULL,
|
|
||||||
`client_name` varchar(200) NOT NULL,
|
|
||||||
`client_authentication_methods` varchar(1000) NOT NULL,
|
|
||||||
`authorization_grant_types` varchar(1000) NOT NULL,
|
|
||||||
`redirect_uris` varchar(1000) DEFAULT NULL,
|
|
||||||
`post_logout_redirect_uris` varchar(1000) DEFAULT NULL,
|
|
||||||
`scopes` varchar(1000) NOT NULL,
|
|
||||||
`client_settings` varchar(2000) NOT NULL,
|
|
||||||
`token_settings` varchar(2000) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
INSERT INTO `oauth2_registered_client`
|
|
||||||
(`id`,
|
|
||||||
`client_id`,
|
|
||||||
`client_id_issued_at`,
|
|
||||||
`client_secret`,
|
|
||||||
`client_secret_expires_at`,
|
|
||||||
`client_name`,
|
|
||||||
`client_authentication_methods`,
|
|
||||||
`authorization_grant_types`,
|
|
||||||
`redirect_uris`,
|
|
||||||
`post_logout_redirect_uris`,
|
|
||||||
`scopes`,
|
|
||||||
`client_settings`,
|
|
||||||
`token_settings`)
|
|
||||||
VALUES
|
|
||||||
('3eacac0e-0de9-4727-9a64-6bdd4be2ee1f',
|
|
||||||
'jeecg-client',
|
|
||||||
now(),
|
|
||||||
'secret',
|
|
||||||
null,
|
|
||||||
'3eacac0e-0de9-4727-9a64-6bdd4be2ee1f',
|
|
||||||
'client_secret_basic',
|
|
||||||
'refresh_token,authorization_code,password,app,phone,social',
|
|
||||||
'http://127.0.0.1:8080/jeecg-',
|
|
||||||
'http://127.0.0.1:8080/',
|
|
||||||
'*',
|
|
||||||
'{"@class":"java.util.Collections$UnmodifiableMap","settings.client.require-proof-key":false,"settings.client.require-authorization-consent":true}',
|
|
||||||
'{"@class":"java.util.Collections$UnmodifiableMap","settings.token.reuse-refresh-tokens":true,"settings.token.id-token-signature-algorithm":["org.springframework.security.oauth2.jose.jws.SignatureAlgorithm","RS256"],"settings.token.access-token-time-to-live":["java.time.Duration",300000.000000000],"settings.token.access-token-format":{"@class":"org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat","value":"self-contained"},"settings.token.refresh-token-time-to-live":["java.time.Duration",3600.000000000],"settings.token.authorization-code-time-to-live":["java.time.Duration",300000.000000000],"settings.token.device-code-time-to-live":["java.time.Duration",300000.000000000]}');
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
版本升级方法?
|
|
||||||
|
|
||||||
JeecgBoot属于平台级产品,每次升级改动内容较多,目前做不到平滑升级。
|
|
||||||
|
|
||||||
升级方案建议:
|
|
||||||
1.代码升级 => 本地版本通过svn或者git做好主干,在分支上做业务开发,jeecg每次版本发布,可以手工覆盖主干的代码,对比合并代码;
|
|
||||||
2.数据库升级 => 针对数据库我们每次发布会提供增量升级SQL,可以通过执行增量SQL实现数据库的升级。
|
|
||||||
3.兼容问题 => 每次版本发布会针对不兼容地方标注说明,需要手工修改不兼容的代码。
|
|
||||||
|
|
||||||
注意: 升级sql目前只提供mysql版本,执行完脚步后,新菜单需要手工进行角色授权,刷新首页才会出现。
|
|
||||||
【20230820 放开了系统管理等模块权限注解,如果没权限请通过角色授权授权对应的按钮权限】
|
|
||||||
15
db/版本升级说明.md
Normal file
15
db/版本升级说明.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# 版本升级方法
|
||||||
|
|
||||||
|
> JeecgBoot属于平台级产品,每次升级改动较大,目前做不到平滑升级。
|
||||||
|
|
||||||
|
### 增量升级方案
|
||||||
|
#### 1.代码合并
|
||||||
|
本地通过svn或git做好主干,在分支上做业务开发,jeecg每次版本发布,可以手工覆盖主干的代码,对比合并代码;
|
||||||
|
|
||||||
|
#### 2.数据库升级
|
||||||
|
- 从3.6.2+版本增加flyway自动升级数据库机制,支持 mysql5.7、mysql8;
|
||||||
|
- 其他库请手工执行SQL, 目录: `jeecg-module-system\jeecg-system-start\src\main\resources\flyway\sql\mysql`
|
||||||
|
> 注意: 升级sql只提供mysql版本;如果有权限升级, 还需要手工角色授权,退出重新登录才好使。
|
||||||
|
|
||||||
|
#### 3.兼容问题
|
||||||
|
每次发版,会针对不兼容地方重点说明。
|
||||||
@ -4,15 +4,11 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-boot-parent</artifactId>
|
<artifactId>jeecg-boot-parent</artifactId>
|
||||||
<version>3.6.1</version>
|
<version>3.6.3</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jeecg-boot-base-core</artifactId>
|
<artifactId>jeecg-boot-base-core</artifactId>
|
||||||
|
|
||||||
<properties>
|
|
||||||
<spring-boot.version>3.1.5</spring-boot.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>aliyun</id>
|
<id>aliyun</id>
|
||||||
@ -47,22 +43,12 @@
|
|||||||
<!--jeecg-tools-->
|
<!--jeecg-tools-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-boot-common3</artifactId>
|
<artifactId>jeecg-boot-common</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--集成springmvc框架并实现自动配置 -->
|
<!--集成springmvc框架并实现自动配置 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- websocket -->
|
<!-- websocket -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -119,14 +105,14 @@
|
|||||||
<!-- druid -->
|
<!-- druid -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
<artifactId>druid-spring-boot-starter</artifactId>
|
||||||
<version>${druid.version}</version>
|
<version>${druid.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 动态数据源 -->
|
<!-- 动态数据源 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
|
||||||
<version>${dynamic-datasource-spring-boot-starter.version}</version>
|
<version>${dynamic-datasource-spring-boot-starter.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@ -173,25 +159,33 @@
|
|||||||
<version>${java-jwt.version}</version>
|
<version>${java-jwt.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--shiro-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.apache.shiro</groupId>
|
||||||
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
|
<artifactId>shiro-spring-boot-starter</artifactId>
|
||||||
|
<version>${shiro.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- shiro-redis -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.crazycake</groupId>
|
||||||
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
<artifactId>shiro-redis</artifactId>
|
||||||
</dependency>
|
<version>${shiro-redis.version}</version>
|
||||||
<!-- 添加spring security cas支持 -->
|
<exclusions>
|
||||||
<dependency>
|
<exclusion>
|
||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.apache.shiro</groupId>
|
||||||
<artifactId>spring-security-cas</artifactId>
|
<artifactId>shiro-core</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>checkstyle</artifactId>
|
||||||
|
<groupId>com.puppycrawl.tools</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- knife4j -->
|
<!-- knife4j -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||||
<version>${knife4j-spring-boot-starter.version}</version>
|
<version>${knife4j-spring-boot-starter.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@ -205,7 +199,7 @@
|
|||||||
|
|
||||||
<!-- AutoPoi Excel工具类-->
|
<!-- AutoPoi Excel工具类-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot3</groupId>
|
<groupId>org.jeecgframework</groupId>
|
||||||
<artifactId>autopoi-web</artifactId>
|
<artifactId>autopoi-web</artifactId>
|
||||||
<version>${autopoi-web.version}</version>
|
<version>${autopoi-web.version}</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
@ -248,16 +242,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.xkcoding.justauth</groupId>
|
<groupId>com.xkcoding.justauth</groupId>
|
||||||
<artifactId>justauth-spring-boot-starter</artifactId>
|
<artifactId>justauth-spring-boot-starter</artifactId>
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp3</groupId>
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
package org.apache.shiro;
|
|
||||||
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 兼容处理Online功能使用处理,请勿修改
|
|
||||||
* @author eightmonth@qq.com
|
|
||||||
* @date 2024/4/29 14:05
|
|
||||||
*/
|
|
||||||
public class SecurityUtils {
|
|
||||||
|
|
||||||
|
|
||||||
public static Subject getSubject() {
|
|
||||||
return new Subject() {
|
|
||||||
@Override
|
|
||||||
public Object getPrincipal() {
|
|
||||||
return Subject.super.getPrincipal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
package org.apache.shiro.subject;
|
|
||||||
|
|
||||||
import org.jeecg.config.security.utils.SecureUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 兼容处理Online功能使用处理,请勿修改
|
|
||||||
* @author eightmonth@qq.com
|
|
||||||
* @date 2024/4/29 14:18
|
|
||||||
*/
|
|
||||||
public interface Subject {
|
|
||||||
default Object getPrincipal() {
|
|
||||||
return SecureUtil.currentUser();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package org.jeecg.common.api;
|
package org.jeecg.common.api;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import org.jeecg.common.system.vo.*;
|
import org.jeecg.common.system.vo.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -23,10 +22,10 @@ public interface CommonAPI {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 2查询用户权限信息
|
* 2查询用户权限信息
|
||||||
* @param username
|
* @param userId
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Set<String> queryUserAuths(String username);
|
Set<String> queryUserAuths(String userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3根据 id 查询数据库中存储的 DynamicDataSourceModel
|
* 3根据 id 查询数据库中存储的 DynamicDataSourceModel
|
||||||
@ -51,13 +50,6 @@ public interface CommonAPI {
|
|||||||
*/
|
*/
|
||||||
public LoginUser getUserByName(String username);
|
public LoginUser getUserByName(String username);
|
||||||
|
|
||||||
/**
|
|
||||||
* 5根据用户手机号查询用户信息
|
|
||||||
* @param username
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public LoginUser getUserByPhone(String phone);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 6字典表的 翻译
|
* 6字典表的 翻译
|
||||||
@ -125,41 +117,17 @@ public interface CommonAPI {
|
|||||||
*/
|
*/
|
||||||
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
|
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
/**
|
/**
|
||||||
* 15 字典表的 翻译,可批量
|
* 15 字典表的 翻译,可批量
|
||||||
* @param table
|
* @param table
|
||||||
* @param text
|
* @param text
|
||||||
* @param code
|
* @param code
|
||||||
* @param keys 多个用逗号分割
|
* @param keys 多个用逗号分割
|
||||||
|
* @param dataSource 数据源
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys);
|
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys, String dataSource);
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
/**
|
|
||||||
* 登录加载系统字典
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
Map<String,List<DictModel>> queryAllDictItems();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询SysDepart集合
|
|
||||||
* @param userId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
List<SysDepartModel> queryUserDeparts(String userId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据用户名设置部门ID
|
|
||||||
* @param username
|
|
||||||
* @param orgCode
|
|
||||||
*/
|
|
||||||
void updateUserDepart(String username,String orgCode,Integer loginTenantId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置登录租户
|
|
||||||
* @param username
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
JSONObject setLoginTenant(String username);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,8 @@ public class DataLogDTO {
|
|||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
private String createName;
|
||||||
|
|
||||||
public DataLogDTO(){
|
public DataLogDTO(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package org.jeecg.common.api.dto;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -30,6 +30,13 @@ public class OnlineAuthDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String onlineFormUrl;
|
private String onlineFormUrl;
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240123 for:[QQYUN-7992]【online】工单申请下的online表单,未配置online表单开发菜单,操作报错无权限------------
|
||||||
|
/**
|
||||||
|
* online工单的地址
|
||||||
|
*/
|
||||||
|
private String onlineWorkOrderUrl;
|
||||||
|
//update-end---author:chenrui ---date:20240123 for:[QQYUN-7992]【online】工单申请下的online表单,未配置online表单开发菜单,操作报错无权限------------
|
||||||
|
|
||||||
public OnlineAuthDTO(){
|
public OnlineAuthDTO(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
package org.jeecg.common.api.vo;
|
package org.jeecg.common.api.vo;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ import java.io.Serializable;
|
|||||||
* @date 2019年1月19日
|
* @date 2019年1月19日
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Schema(description="接口返回对象")
|
@ApiModel(value="接口返回对象", description="接口返回对象")
|
||||||
public class Result<T> implements Serializable {
|
public class Result<T> implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@ -22,31 +23,31 @@ public class Result<T> implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 成功标志
|
* 成功标志
|
||||||
*/
|
*/
|
||||||
@Schema(description = "成功标志")
|
@ApiModelProperty(value = "成功标志")
|
||||||
private boolean success = true;
|
private boolean success = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回处理消息
|
* 返回处理消息
|
||||||
*/
|
*/
|
||||||
@Schema(description = "返回处理消息")
|
@ApiModelProperty(value = "返回处理消息")
|
||||||
private String message = "";
|
private String message = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回代码
|
* 返回代码
|
||||||
*/
|
*/
|
||||||
@Schema(description = "返回代码")
|
@ApiModelProperty(value = "返回代码")
|
||||||
private Integer code = 0;
|
private Integer code = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回数据对象 data
|
* 返回数据对象 data
|
||||||
*/
|
*/
|
||||||
@Schema(description = "返回数据对象")
|
@ApiModelProperty(value = "返回数据对象")
|
||||||
private T result;
|
private T result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 时间戳
|
* 时间戳
|
||||||
*/
|
*/
|
||||||
@Schema(description = "时间戳")
|
@ApiModelProperty(value = "时间戳")
|
||||||
private long timestamp = System.currentTimeMillis();
|
private long timestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
public Result() {
|
public Result() {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package org.jeecg.common.aspect;
|
package org.jeecg.common.aspect;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.alibaba.fastjson.serializer.PropertyFilter;
|
import com.alibaba.fastjson.serializer.PropertyFilter;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
@ -15,21 +15,19 @@ import org.jeecg.common.aspect.annotation.AutoLog;
|
|||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.constant.enums.ModuleType;
|
import org.jeecg.common.constant.enums.ModuleType;
|
||||||
import org.jeecg.common.constant.enums.OperateTypeEnum;
|
import org.jeecg.common.constant.enums.OperateTypeEnum;
|
||||||
import org.jeecg.config.security.utils.SecureUtil;
|
|
||||||
import org.jeecg.modules.base.service.BaseCommonService;
|
import org.jeecg.modules.base.service.BaseCommonService;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.common.util.IpUtils;
|
import org.jeecg.common.util.IpUtils;
|
||||||
import org.jeecg.common.util.SpringContextUtils;
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import jakarta.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import jakarta.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import jakarta.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@ -102,7 +100,7 @@ public class AutoLogAspect {
|
|||||||
//设置IP地址
|
//设置IP地址
|
||||||
dto.setIp(IpUtils.getIpAddr(request));
|
dto.setIp(IpUtils.getIpAddr(request));
|
||||||
//获取登录用户信息
|
//获取登录用户信息
|
||||||
LoginUser sysUser = SecureUtil.currentUser();
|
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
if(sysUser!=null){
|
if(sysUser!=null){
|
||||||
dto.setUserid(sysUser.getUsername());
|
dto.setUserid(sysUser.getUsername());
|
||||||
dto.setUsername(sysUser.getRealname());
|
dto.setUsername(sysUser.getRealname());
|
||||||
@ -160,9 +158,6 @@ public class AutoLogAspect {
|
|||||||
if(value!=null && value.toString().length()>length){
|
if(value!=null && value.toString().length()>length){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(value instanceof MultipartFile){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -140,11 +140,15 @@ public class DictAspect {
|
|||||||
String code = field.getAnnotation(Dict.class).dicCode();
|
String code = field.getAnnotation(Dict.class).dicCode();
|
||||||
String text = field.getAnnotation(Dict.class).dicText();
|
String text = field.getAnnotation(Dict.class).dicText();
|
||||||
String table = field.getAnnotation(Dict.class).dictTable();
|
String table = field.getAnnotation(Dict.class).dictTable();
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
String dataSource = field.getAnnotation(Dict.class).ds();
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
List<String> dataList;
|
List<String> dataList;
|
||||||
String dictCode = code;
|
String dictCode = code;
|
||||||
if (!StringUtils.isEmpty(table)) {
|
if (!StringUtils.isEmpty(table)) {
|
||||||
dictCode = String.format("%s,%s,%s", table, text, code);
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
}
|
}
|
||||||
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||||
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
|
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
|
||||||
@ -169,10 +173,15 @@ public class DictAspect {
|
|||||||
String code = field.getAnnotation(Dict.class).dicCode();
|
String code = field.getAnnotation(Dict.class).dicCode();
|
||||||
String text = field.getAnnotation(Dict.class).dicText();
|
String text = field.getAnnotation(Dict.class).dicText();
|
||||||
String table = field.getAnnotation(Dict.class).dictTable();
|
String table = field.getAnnotation(Dict.class).dictTable();
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
// 自定义的字典表数据源
|
||||||
|
String dataSource = field.getAnnotation(Dict.class).ds();
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
String fieldDictCode = code;
|
String fieldDictCode = code;
|
||||||
if (!StringUtils.isEmpty(table)) {
|
if (!StringUtils.isEmpty(table)) {
|
||||||
fieldDictCode = String.format("%s,%s,%s", table, text, code);
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
fieldDictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
}
|
}
|
||||||
|
|
||||||
String value = record.getString(field.getName());
|
String value = record.getString(field.getName());
|
||||||
@ -274,9 +283,25 @@ public class DictAspect {
|
|||||||
String[] arr = dictCode.split(",");
|
String[] arr = dictCode.split(",");
|
||||||
String table = arr[0], text = arr[1], code = arr[2];
|
String table = arr[0], text = arr[1], code = arr[2];
|
||||||
String values = String.join(",", needTranslDataTable);
|
String values = String.join(",", needTranslDataTable);
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
// 自定义的数据源
|
||||||
|
String dataSource = null;
|
||||||
|
if (arr.length > 3) {
|
||||||
|
dataSource = arr[3];
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
|
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
|
||||||
log.debug("translateDictFromTableByKeys.values:" + values);
|
log.debug("translateDictFromTableByKeys.values:" + values);
|
||||||
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values);
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
|
||||||
|
//update-begin---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
||||||
|
if(null == dataSource){
|
||||||
|
dataSource = "";
|
||||||
|
}
|
||||||
|
//update-end---author:wangshuai---date:2024-01-09---for:微服务下为空报错没有参数需要传递空字符串---
|
||||||
|
|
||||||
|
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource);
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
log.debug("translateDictFromTableByKeys.result:" + texts);
|
log.debug("translateDictFromTableByKeys.result:" + texts);
|
||||||
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
|
||||||
list.addAll(texts);
|
list.addAll(texts);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|||||||
@ -39,4 +39,16 @@ public @interface Dict {
|
|||||||
* @return 返回类型: String
|
* @return 返回类型: String
|
||||||
*/
|
*/
|
||||||
String dictTable() default "";
|
String dictTable() default "";
|
||||||
|
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
|
/**
|
||||||
|
* 方法描述: 数据字典表所在数据源名称
|
||||||
|
* 作 者: chenrui
|
||||||
|
* 日 期: 2023年12月20日-下午4:58
|
||||||
|
*
|
||||||
|
* @return 返回类型: String
|
||||||
|
*/
|
||||||
|
String ds() default "";
|
||||||
|
//update-end---author:chenrui ---date:20231221 for:[issues/#5643]解决分布式下表字典跨库无法查询问题------------
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,6 +69,8 @@ public interface CommonConstant {
|
|||||||
|
|
||||||
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
|
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
|
||||||
Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
|
Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
|
||||||
|
/** {@code 404 Not Found} (HTTP/1.0 - RFC 1945) */
|
||||||
|
Integer SC_INTERNAL_NOT_FOUND_404 = 404;
|
||||||
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
|
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
|
||||||
Integer SC_OK_200 = 200;
|
Integer SC_OK_200 = 200;
|
||||||
|
|
||||||
@ -78,7 +80,7 @@ public interface CommonConstant {
|
|||||||
/** 登录用户Shiro权限缓存KEY前缀 */
|
/** 登录用户Shiro权限缓存KEY前缀 */
|
||||||
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
|
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
|
||||||
/** 登录用户Token令牌缓存KEY前缀 */
|
/** 登录用户Token令牌缓存KEY前缀 */
|
||||||
String PREFIX_USER_TOKEN = "token::jeecg-client::";
|
String PREFIX_USER_TOKEN = "prefix_user_token:";
|
||||||
// /** Token缓存时间:3600秒即一小时 */
|
// /** Token缓存时间:3600秒即一小时 */
|
||||||
// int TOKEN_EXPIRE_TIME = 3600;
|
// int TOKEN_EXPIRE_TIME = 3600;
|
||||||
|
|
||||||
@ -375,6 +377,8 @@ public interface CommonConstant {
|
|||||||
/**前端vue3版本Header参数名*/
|
/**前端vue3版本Header参数名*/
|
||||||
String VERSION="X-Version";
|
String VERSION="X-Version";
|
||||||
|
|
||||||
|
String VERSION_V3 = "v3";
|
||||||
|
|
||||||
/**存储在线程变量里的动态表名*/
|
/**存储在线程变量里的动态表名*/
|
||||||
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
|
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -18,6 +18,9 @@ public interface DataBaseConstant {
|
|||||||
/**postgreSQL达梦数据库*/
|
/**postgreSQL达梦数据库*/
|
||||||
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
|
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
|
||||||
|
|
||||||
|
/**人大金仓数据库*/
|
||||||
|
public static final String DB_TYPE_KINGBASEES = "KINGBASEES";
|
||||||
|
|
||||||
/**sqlserver数据库*/
|
/**sqlserver数据库*/
|
||||||
public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
|
public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
|
||||||
|
|
||||||
|
|||||||
@ -13,12 +13,16 @@ import java.util.List;
|
|||||||
public enum RoleIndexConfigEnum {
|
public enum RoleIndexConfigEnum {
|
||||||
|
|
||||||
/**首页自定义 admin*/
|
/**首页自定义 admin*/
|
||||||
ADMIN("admin", "dashboard/Analysis"),
|
// ADMIN("admin", "dashboard/Analysis"),
|
||||||
//TEST("test", "dashboard/IndexChart"),
|
//TEST("test", "dashboard/IndexChart"),
|
||||||
/**首页自定义 hr*/
|
/**首页自定义 hr*/
|
||||||
HR("hr", "dashboard/IndexBdc");
|
// HR("hr", "dashboard/IndexBdc");
|
||||||
|
|
||||||
//DM("dm", "dashboard/IndexTask"),
|
//DM("dm", "dashboard/IndexTask"),
|
||||||
|
|
||||||
|
// 注:此值仅为防止报错,无任何实际意义
|
||||||
|
ROLE_INDEX_CONFIG_ENUM("RoleIndexConfigEnumDefault", "dashboard/Analysis");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 角色编码
|
* 角色编码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package org.jeecg.common.exception;
|
package org.jeecg.common.exception;
|
||||||
|
|
||||||
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: jeecg-boot自定义异常
|
* @Description: jeecg-boot自定义异常
|
||||||
* @author: jeecg-boot
|
* @author: jeecg-boot
|
||||||
@ -7,10 +9,24 @@ package org.jeecg.common.exception;
|
|||||||
public class JeecgBootException extends RuntimeException {
|
public class JeecgBootException extends RuntimeException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回给前端的错误code
|
||||||
|
*/
|
||||||
|
private int errCode = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
|
||||||
|
|
||||||
public JeecgBootException(String message){
|
public JeecgBootException(String message){
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JeecgBootException(String message, int errCode){
|
||||||
|
super(message);
|
||||||
|
this.errCode = errCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrCode() {
|
||||||
|
return errCode;
|
||||||
|
}
|
||||||
|
|
||||||
public JeecgBootException(Throwable cause)
|
public JeecgBootException(Throwable cause)
|
||||||
{
|
{
|
||||||
super(cause);
|
super(cause);
|
||||||
|
|||||||
@ -2,17 +2,16 @@ package org.jeecg.common.exception;
|
|||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.authz.AuthorizationException;
|
||||||
|
import org.apache.shiro.authz.UnauthorizedException;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.enums.SentinelErrorInfoEnum;
|
import org.jeecg.common.enums.SentinelErrorInfoEnum;
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.data.redis.connection.PoolException;
|
import org.springframework.data.redis.connection.PoolException;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||||
@ -28,31 +27,13 @@ import org.springframework.web.servlet.NoHandlerFoundException;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class JeecgBootExceptionHandler {
|
public class JeecgBootExceptionHandler {
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证码错误异常
|
|
||||||
*/
|
|
||||||
|
|
||||||
@ExceptionHandler(JeecgCaptchaException.class)
|
|
||||||
@ResponseStatus(HttpStatus.OK)
|
|
||||||
public Result<?> handleJeecgCaptchaException(JeecgCaptchaException e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
return Result.error(e.getCode(), e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(AuthenticationException.class)
|
|
||||||
@ResponseStatus(HttpStatus.OK)
|
|
||||||
public Result<?> handleJeecgCaptchaException(AuthenticationException e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
return Result.error(401, e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理自定义异常
|
* 处理自定义异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(JeecgBootException.class)
|
@ExceptionHandler(JeecgBootException.class)
|
||||||
public Result<?> handleJeecgBootException(JeecgBootException e){
|
public Result<?> handleJeecgBootException(JeecgBootException e){
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getErrCode(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,8 +67,9 @@ public class JeecgBootExceptionHandler {
|
|||||||
return Result.error("数据库中已存在该记录");
|
return Result.error("数据库中已存在该记录");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(AccessDeniedException.class)
|
@ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
|
||||||
public Result<?> handleAuthorizationException(AccessDeniedException e){
|
public Result<?> handleAuthorizationException(AuthorizationException e){
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
return Result.noauth("没有权限,请联系管理员授权");
|
return Result.noauth("没有权限,请联系管理员授权");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
package org.jeecg.common.exception;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author kezhijie@wuhandsj.com
|
|
||||||
* @date 2024/1/2 11:38
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class JeecgCaptchaException extends RuntimeException{
|
|
||||||
|
|
||||||
private Integer code;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -9093410345065209053L;
|
|
||||||
|
|
||||||
public JeecgCaptchaException(Integer code, String message) {
|
|
||||||
super(message);
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JeecgCaptchaException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JeecgCaptchaException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +1,17 @@
|
|||||||
package org.jeecg.common.system.base.controller;
|
package org.jeecg.common.system.base.controller;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.beanutils.PropertyUtils;
|
import org.apache.commons.beanutils.PropertyUtils;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.system.query.QueryGenerator;
|
import org.jeecg.common.system.query.QueryGenerator;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
import org.jeecg.config.security.utils.SecureUtil;
|
|
||||||
import org.jeecgframework.poi.excel.ExcelImportUtil;
|
import org.jeecgframework.poi.excel.ExcelImportUtil;
|
||||||
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
|
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
|
||||||
import org.jeecgframework.poi.excel.entity.ExportParams;
|
import org.jeecgframework.poi.excel.entity.ExportParams;
|
||||||
@ -20,14 +19,13 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
|
|||||||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -53,7 +51,7 @@ public class JeecgController<T, S extends IService<T>> {
|
|||||||
protected ModelAndView exportXls(HttpServletRequest request, T object, Class<T> clazz, String title) {
|
protected ModelAndView exportXls(HttpServletRequest request, T object, Class<T> clazz, String title) {
|
||||||
// Step.1 组装查询条件
|
// Step.1 组装查询条件
|
||||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||||
LoginUser sysUser = SecureUtil.currentUser();
|
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
|
|
||||||
// 过滤选中数据
|
// 过滤选中数据
|
||||||
String selections = request.getParameter("selections");
|
String selections = request.getParameter("selections");
|
||||||
@ -91,7 +89,7 @@ public class JeecgController<T, S extends IService<T>> {
|
|||||||
protected ModelAndView exportXlsSheet(HttpServletRequest request, T object, Class<T> clazz, String title,String exportFields,Integer pageNum) {
|
protected ModelAndView exportXlsSheet(HttpServletRequest request, T object, Class<T> clazz, String title,String exportFields,Integer pageNum) {
|
||||||
// Step.1 组装查询条件
|
// Step.1 组装查询条件
|
||||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||||
LoginUser sysUser = SecureUtil.currentUser();
|
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
// Step.2 计算分页sheet数据
|
// Step.2 计算分页sheet数据
|
||||||
double total = service.count();
|
double total = service.count();
|
||||||
int count = (int)Math.ceil(total/pageNum);
|
int count = (int)Math.ceil(total/pageNum);
|
||||||
|
|||||||
@ -9,10 +9,10 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: Entity基类
|
* @Description: Entity基类
|
||||||
@ -30,20 +30,20 @@ public class JeecgEntity implements Serializable {
|
|||||||
* ID
|
* ID
|
||||||
*/
|
*/
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
@Schema(description = "ID")
|
@ApiModelProperty(value = "ID")
|
||||||
private java.lang.String id;
|
private java.lang.String id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建人
|
* 创建人
|
||||||
*/
|
*/
|
||||||
@Schema(description = "创建人")
|
@ApiModelProperty(value = "创建人")
|
||||||
@Excel(name = "创建人", width = 15)
|
@Excel(name = "创建人", width = 15)
|
||||||
private java.lang.String createBy;
|
private java.lang.String createBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
@Schema(description = "创建时间")
|
@ApiModelProperty(value = "创建时间")
|
||||||
@Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
@Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@ -52,14 +52,14 @@ public class JeecgEntity implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 更新人
|
* 更新人
|
||||||
*/
|
*/
|
||||||
@Schema(description = "更新人")
|
@ApiModelProperty(value = "更新人")
|
||||||
@Excel(name = "更新人", width = 15)
|
@Excel(name = "更新人", width = 15)
|
||||||
private java.lang.String updateBy;
|
private java.lang.String updateBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新时间
|
* 更新时间
|
||||||
*/
|
*/
|
||||||
@Schema(description = "更新时间")
|
@ApiModelProperty(value = "更新时间")
|
||||||
@Excel(name = "更新时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
@Excel(name = "更新时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import org.jeecg.common.system.vo.SysUserCacheInfo;
|
|||||||
import org.jeecg.common.util.SpringContextUtils;
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
package org.jeecg.common.system.util;
|
package org.jeecg.common.system.util;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
import com.auth0.jwt.JWT;
|
import com.auth0.jwt.JWT;
|
||||||
import com.auth0.jwt.JWTVerifier;
|
import com.auth0.jwt.JWTVerifier;
|
||||||
import com.auth0.jwt.algorithms.Algorithm;
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
@ -12,17 +10,14 @@ import com.google.common.base.Joiner;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.*;
|
import java.util.Date;
|
||||||
import java.util.stream.Collectors;
|
import javax.servlet.ServletResponse;
|
||||||
import java.util.stream.Stream;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.ServletResponse;
|
import javax.servlet.http.HttpSession;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.common.api.CommonAPI;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.constant.DataBaseConstant;
|
import org.jeecg.common.constant.DataBaseConstant;
|
||||||
@ -34,22 +29,6 @@ import org.jeecg.common.system.vo.SysUserCacheInfo;
|
|||||||
import org.jeecg.common.util.DateUtils;
|
import org.jeecg.common.util.DateUtils;
|
||||||
import org.jeecg.common.util.SpringContextUtils;
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.jeecg.config.security.self.SelfAuthenticationProvider;
|
|
||||||
import org.jeecg.config.security.self.SelfAuthenticationToken;
|
|
||||||
import org.jeecg.config.security.utils.SecureUtil;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.oauth2.core.*;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author Scott
|
* @Author Scott
|
||||||
@ -63,8 +42,6 @@ public class JwtUtil {
|
|||||||
public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000;
|
public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000;
|
||||||
static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
|
static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
|
||||||
|
|
||||||
public static final String DEFAULT_CLIENT = "jeecg-client";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param response
|
* @param response
|
||||||
@ -100,9 +77,10 @@ public class JwtUtil {
|
|||||||
public static boolean verify(String token, String username, String secret) {
|
public static boolean verify(String token, String username, String secret) {
|
||||||
try {
|
try {
|
||||||
// 根据密码生成JWT效验器
|
// 根据密码生成JWT效验器
|
||||||
JwtDecoder jwtDecoder = SpringContextUtils.getBean(JwtDecoder.class);
|
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||||
|
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
|
||||||
// 效验TOKEN
|
// 效验TOKEN
|
||||||
jwtDecoder.decode(token);
|
DecodedJWT jwt = verifier.verify(token);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
return false;
|
return false;
|
||||||
@ -117,33 +95,24 @@ public class JwtUtil {
|
|||||||
public static String getUsername(String token) {
|
public static String getUsername(String token) {
|
||||||
try {
|
try {
|
||||||
DecodedJWT jwt = JWT.decode(token);
|
DecodedJWT jwt = JWT.decode(token);
|
||||||
LoginUser loginUser = JSONObject.parseObject(jwt.getClaim("sub").asString(), LoginUser.class);
|
return jwt.getClaim("username").asString();
|
||||||
return loginUser.getUsername();
|
|
||||||
} catch (JWTDecodeException e) {
|
} catch (JWTDecodeException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成token
|
* 生成签名,5min后过期
|
||||||
*
|
*
|
||||||
* @param username 用户名
|
* @param username 用户名
|
||||||
* @param secret 用户的密码
|
* @param secret 用户的密码
|
||||||
* @return 加密的token
|
* @return 加密的token
|
||||||
*/
|
*/
|
||||||
public static String sign(String username, String secret) {
|
public static String sign(String username, String secret) {
|
||||||
Map<String, Object> additionalParameter = new HashMap<>();
|
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
|
||||||
additionalParameter.put("username", username);
|
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||||
|
// 附带username信息
|
||||||
RegisteredClientRepository registeredClientRepository = SpringContextUtils.getBean(RegisteredClientRepository.class);
|
return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
|
||||||
SelfAuthenticationProvider selfAuthenticationProvider = SpringContextUtils.getBean(SelfAuthenticationProvider.class);
|
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken client = new OAuth2ClientAuthenticationToken(Objects.requireNonNull(registeredClientRepository.findByClientId("jeecg-client")), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null);
|
|
||||||
client.setAuthenticated(true);
|
|
||||||
SelfAuthenticationToken selfAuthenticationToken = new SelfAuthenticationToken(client, additionalParameter);
|
|
||||||
selfAuthenticationToken.setAuthenticated(true);
|
|
||||||
OAuth2AccessTokenAuthenticationToken accessToken = (OAuth2AccessTokenAuthenticationToken) selfAuthenticationProvider.authenticate(selfAuthenticationToken);
|
|
||||||
return accessToken.getAccessToken().getTokenValue();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +177,7 @@ public class JwtUtil {
|
|||||||
//2.通过shiro获取登录用户信息
|
//2.通过shiro获取登录用户信息
|
||||||
LoginUser sysUser = null;
|
LoginUser sysUser = null;
|
||||||
try {
|
try {
|
||||||
sysUser = SecureUtil.currentUser();
|
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
|
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,13 @@
|
|||||||
package org.jeecg.common.system.vo;
|
package org.jeecg.common.system.vo;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
import org.jeecg.common.desensitization.annotation.SensitiveField;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@ -25,10 +20,8 @@ import java.util.Set;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = false)
|
@EqualsAndHashCode(callSuper = false)
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class LoginUser implements Serializable {
|
public class LoginUser {
|
||||||
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -7143159031677245866L;
|
|
||||||
/**
|
/**
|
||||||
* 登录人id
|
* 登录人id
|
||||||
*/
|
*/
|
||||||
@ -134,29 +127,4 @@ public class LoginUser implements Serializable {
|
|||||||
/**设备id uniapp推送用*/
|
/**设备id uniapp推送用*/
|
||||||
private String clientId;
|
private String clientId;
|
||||||
|
|
||||||
@SensitiveField
|
|
||||||
private String salt;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
// 重新构建对象过滤一些敏感字段
|
|
||||||
LoginUser loginUser = new LoginUser();
|
|
||||||
loginUser.setId(id);
|
|
||||||
loginUser.setUsername(username);
|
|
||||||
loginUser.setRealname(realname);
|
|
||||||
loginUser.setOrgCode(orgCode);
|
|
||||||
loginUser.setSex(sex);
|
|
||||||
loginUser.setEmail(email);
|
|
||||||
loginUser.setPhone(phone);
|
|
||||||
loginUser.setDelFlag(delFlag);
|
|
||||||
loginUser.setStatus(status);
|
|
||||||
loginUser.setActivitiSync(activitiSync);
|
|
||||||
loginUser.setUserIdentity(userIdentity);
|
|
||||||
loginUser.setDepartIds(departIds);
|
|
||||||
loginUser.setPost(post);
|
|
||||||
loginUser.setTelephone(telephone);
|
|
||||||
loginUser.setRelTenantIds(relTenantIds);
|
|
||||||
loginUser.setClientId(clientId);
|
|
||||||
return JSON.toJSONString(loginUser);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,8 @@ public class SysFilesModel {
|
|||||||
private String storeType;
|
private String storeType;
|
||||||
/**文件大小(kb)*/
|
/**文件大小(kb)*/
|
||||||
private Double fileSize;
|
private Double fileSize;
|
||||||
|
/**租户id*/
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
@ -67,4 +69,12 @@ public class SysFilesModel {
|
|||||||
public void setFileSize(Double fileSize) {
|
public void setFileSize(Double fileSize) {
|
||||||
this.fileSize = fileSize;
|
this.fileSize = fileSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(String tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -5,7 +5,7 @@ import java.util.Map;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
|||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -28,7 +28,9 @@ import java.io.InputStream;
|
|||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DatabaseMetaData;
|
import java.sql.DatabaseMetaData;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -302,7 +304,7 @@ public class CommonUtils {
|
|||||||
DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE;
|
DB_TYPE = DataBaseConstant.DB_TYPE_ORACLE;
|
||||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_SQLSERVER)>=0||dbType.indexOf(sqlserver)>=0) {
|
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_SQLSERVER)>=0||dbType.indexOf(sqlserver)>=0) {
|
||||||
DB_TYPE = DataBaseConstant.DB_TYPE_SQLSERVER;
|
DB_TYPE = DataBaseConstant.DB_TYPE_SQLSERVER;
|
||||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_POSTGRESQL)>=0) {
|
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_POSTGRESQL)>=0 || dbType.indexOf(DataBaseConstant.DB_TYPE_KINGBASEES)>=0) {
|
||||||
DB_TYPE = DataBaseConstant.DB_TYPE_POSTGRESQL;
|
DB_TYPE = DataBaseConstant.DB_TYPE_POSTGRESQL;
|
||||||
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_MARIADB)>=0) {
|
}else if(dbType.indexOf(DataBaseConstant.DB_TYPE_MARIADB)>=0) {
|
||||||
DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB;
|
DB_TYPE = DataBaseConstant.DB_TYPE_MARIADB;
|
||||||
@ -467,4 +469,19 @@ public class CommonUtils {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出info日志,会捕获异常,防止因为日志问题导致程序异常
|
||||||
|
*
|
||||||
|
* @param msg
|
||||||
|
* @param objects
|
||||||
|
*/
|
||||||
|
public static void logInfo(String msg, Object... objects) {
|
||||||
|
try {
|
||||||
|
log.info(msg, objects);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("{} —— {}", msg, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -62,8 +62,8 @@ public class DySmsHelper {
|
|||||||
|
|
||||||
//update-begin-author:taoyan date:20200811 for:配置类数据获取
|
//update-begin-author:taoyan date:20200811 for:配置类数据获取
|
||||||
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
||||||
logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
|
//logger.info("阿里大鱼短信秘钥 accessKeyId:" + staticConfig.getAccessKeyId());
|
||||||
logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
|
//logger.info("阿里大鱼短信秘钥 accessKeySecret:"+ staticConfig.getAccessKeySecret());
|
||||||
setAccessKeyId(staticConfig.getAccessKeyId());
|
setAccessKeyId(staticConfig.getAccessKeyId());
|
||||||
setAccessKeySecret(staticConfig.getAccessKeySecret());
|
setAccessKeySecret(staticConfig.getAccessKeySecret());
|
||||||
//update-end-author:taoyan date:20200811 for:配置类数据获取
|
//update-end-author:taoyan date:20200811 for:配置类数据获取
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package org.jeecg.common.util;
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package org.jeecg.common.util;
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.common.constant.ServiceNameConstants;
|
import org.jeecg.common.constant.ServiceNameConstants;
|
||||||
|
|||||||
@ -29,6 +29,17 @@ public class SqlInjectionUtil {
|
|||||||
* 字典专用—sql注入关键词
|
* 字典专用—sql注入关键词
|
||||||
*/
|
*/
|
||||||
private static String specialDictSqlXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|--";
|
private static String specialDictSqlXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|--";
|
||||||
|
/**
|
||||||
|
* 完整匹配的key,不需要考虑前空格
|
||||||
|
*/
|
||||||
|
private static List<String> FULL_MATCHING_KEYWRODS = new ArrayList<>();
|
||||||
|
static {
|
||||||
|
FULL_MATCHING_KEYWRODS.add(";");
|
||||||
|
FULL_MATCHING_KEYWRODS.add("+");
|
||||||
|
FULL_MATCHING_KEYWRODS.add("--");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sql注入风险的 正则关键字
|
* sql注入风险的 正则关键字
|
||||||
*
|
*
|
||||||
@ -50,6 +61,8 @@ public class SqlInjectionUtil {
|
|||||||
* sql注释的正则
|
* sql注释的正则
|
||||||
*/
|
*/
|
||||||
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
|
private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
|
||||||
|
private final static String SQL_ANNOTATION2 = "--";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sql注入提示语
|
* sql注入提示语
|
||||||
*/
|
*/
|
||||||
@ -128,7 +141,13 @@ public class SqlInjectionUtil {
|
|||||||
if (sql.startsWith(keyword.trim())) {
|
if (sql.startsWith(keyword.trim())) {
|
||||||
return true;
|
return true;
|
||||||
} else if (sql.contains(keyword)) {
|
} else if (sql.contains(keyword)) {
|
||||||
if (sql.contains(" " + keyword)) {
|
// 需要匹配的,sql注入关键词
|
||||||
|
String matchingText = " " + keyword;
|
||||||
|
if(FULL_MATCHING_KEYWRODS.contains(keyword)){
|
||||||
|
matchingText = keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sql.contains(matchingText)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
String regularStr = "\\s+\\S+" + keyword;
|
String regularStr = "\\s+\\S+" + keyword;
|
||||||
@ -244,6 +263,13 @@ public class SqlInjectionUtil {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static void checkSqlAnnotation(String str){
|
public static void checkSqlAnnotation(String str){
|
||||||
|
if(str.contains(SQL_ANNOTATION2)){
|
||||||
|
String error = "请注意,SQL中不允许含注释,有安全风险!";
|
||||||
|
log.error(error);
|
||||||
|
throw new RuntimeException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Matcher matcher = SQL_ANNOTATION.matcher(str);
|
Matcher matcher = SQL_ANNOTATION.matcher(str);
|
||||||
if(matcher.find()){
|
if(matcher.find()){
|
||||||
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
|
||||||
@ -260,7 +286,7 @@ public class SqlInjectionUtil {
|
|||||||
*
|
*
|
||||||
* @param table
|
* @param table
|
||||||
*/
|
*/
|
||||||
private static Pattern tableNamePattern = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{0,63}$");
|
private static Pattern tableNamePattern = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_\\$]{0,63}$");
|
||||||
public static String getSqlInjectTableName(String table) {
|
public static String getSqlInjectTableName(String table) {
|
||||||
if(oConvertUtils.isEmpty(table)){
|
if(oConvertUtils.isEmpty(table)){
|
||||||
return table;
|
return table;
|
||||||
|
|||||||
@ -11,13 +11,7 @@ import org.jeecg.common.exception.JeecgBoot401Exception;
|
|||||||
import org.jeecg.common.system.util.JwtUtil;
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.jeecg.config.security.JeecgRedisOAuth2AuthorizationService;
|
|
||||||
import org.springframework.data.redis.serializer.SerializationException;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author scott
|
* @Author scott
|
||||||
@ -118,7 +112,7 @@ public class TokenUtils {
|
|||||||
throw new JeecgBoot401Exception("账号已被锁定,请联系管理员!");
|
throw new JeecgBoot401Exception("账号已被锁定,请联系管理员!");
|
||||||
}
|
}
|
||||||
// 校验token是否超时失效 & 或者账号密码是否错误
|
// 校验token是否超时失效 & 或者账号密码是否错误
|
||||||
if (!jwtTokenRefresh(token, username, user.getPassword())) {
|
if (!jwtTokenRefresh(token, username, user.getPassword(), redisUtil)) {
|
||||||
throw new JeecgBoot401Exception(CommonConstant.TOKEN_IS_INVALID_MSG);
|
throw new JeecgBoot401Exception(CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -147,15 +141,6 @@ public class TokenUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean jwtTokenRefresh(String token, String userName, String passWord) {
|
|
||||||
JeecgRedisOAuth2AuthorizationService authRedis = SpringContextUtils.getBean(JeecgRedisOAuth2AuthorizationService.class);
|
|
||||||
OAuth2Authorization authorization = authRedis.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
|
||||||
if (Objects.nonNull(authorization) && JwtUtil.verify(token, userName, passWord)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取登录用户
|
* 获取登录用户
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
package org.jeecg.common.util.encryption;
|
package org.jeecg.common.util.encryption;
|
||||||
|
|
||||||
|
import org.apache.shiro.codec.Base64;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description: AES 加密
|
* @Description: AES 加密
|
||||||
@ -48,7 +49,7 @@ public class AesEncryptUtil {
|
|||||||
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
|
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
|
||||||
byte[] encrypted = cipher.doFinal(plaintext);
|
byte[] encrypted = cipher.doFinal(plaintext);
|
||||||
|
|
||||||
return Base64.getEncoder().encodeToString(encrypted);
|
return Base64.encodeToString(encrypted);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -66,7 +67,7 @@ public class AesEncryptUtil {
|
|||||||
*/
|
*/
|
||||||
public static String desEncrypt(String data, String key, String iv) throws Exception {
|
public static String desEncrypt(String data, String key, String iv) throws Exception {
|
||||||
//update-begin-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
|
//update-begin-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
|
||||||
byte[] encrypted1 = Base64.getDecoder().decode(data);
|
byte[] encrypted1 = Base64.decode(data);
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||||
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
|
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
|
||||||
|
|||||||
@ -61,6 +61,10 @@ public class SsrfFileTypeFilter {
|
|||||||
FILE_TYPE_WHITE_LIST.add("7z");
|
FILE_TYPE_WHITE_LIST.add("7z");
|
||||||
FILE_TYPE_WHITE_LIST.add("tar");
|
FILE_TYPE_WHITE_LIST.add("tar");
|
||||||
|
|
||||||
|
//app文件后缀
|
||||||
|
FILE_TYPE_WHITE_LIST.add("apk");
|
||||||
|
FILE_TYPE_WHITE_LIST.add("wgt");
|
||||||
|
|
||||||
//设置禁止文件的头部标记
|
//设置禁止文件的头部标记
|
||||||
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");
|
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");
|
||||||
FILE_TYPE_MAP.put("3c3f7068700a0a2f2a2a0a202a205048", "php");
|
FILE_TYPE_MAP.put("3c3f7068700a0a2f2a2a0a202a205048", "php");
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import org.jeecg.common.constant.CommonConstant;
|
|||||||
import org.jeecg.common.constant.SymbolConstant;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@ -413,7 +413,7 @@ public class oConvertUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] childs = (String[]) childArray.toArray();
|
String[] childs = childArray.toArray(new String[]{});
|
||||||
for (String v : childs) {
|
for (String v : childs) {
|
||||||
if (!isIn(v, all)) {
|
if (!isIn(v, all)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package org.jeecg.config;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
import org.jeecg.common.api.CommonAPI;
|
import org.jeecg.common.api.CommonAPI;
|
||||||
import org.jeecg.common.system.vo.DictModel;
|
import org.jeecg.common.system.vo.DictModel;
|
||||||
|
|||||||
@ -2,9 +2,7 @@ package org.jeecg.config;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
|
import javax.servlet.*;
|
||||||
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
|
|
||||||
import jakarta.servlet.*;
|
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
@ -13,6 +11,8 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
|
||||||
|
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
|
||||||
import com.alibaba.druid.util.Utils;
|
import com.alibaba.druid.util.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
package org.jeecg.config;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.SpringApplicationRunListener;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
import org.springframework.core.env.ConfigurableEnvironment;
|
|
||||||
import org.springframework.core.env.MapPropertySource;
|
|
||||||
import org.springframework.core.env.MutablePropertySources;
|
|
||||||
import org.springframework.core.env.PropertySource;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author eightmonth@qq.com
|
|
||||||
* @date 2024/4/8 11:37
|
|
||||||
*/
|
|
||||||
public class DruidWallConfigRegister implements SpringApplicationRunListener {
|
|
||||||
|
|
||||||
public SpringApplication application;
|
|
||||||
|
|
||||||
private String[] args;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 必备,否则启动报错
|
|
||||||
* @param application
|
|
||||||
* @param args
|
|
||||||
*/
|
|
||||||
public DruidWallConfigRegister(SpringApplication application, String[] args) {
|
|
||||||
this.application = application;
|
|
||||||
this.args = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contextLoaded(ConfigurableApplicationContext context) {
|
|
||||||
ConfigurableEnvironment env = context.getEnvironment();
|
|
||||||
Map<String, Object> props = new HashMap<>();
|
|
||||||
props.put("spring.datasource.dynamic.druid.wall.selectWhereAlwayTrueCheck", false);
|
|
||||||
|
|
||||||
MutablePropertySources propertySources = env.getPropertySources();
|
|
||||||
|
|
||||||
PropertySource<Map<String, Object>> propertySource = new MapPropertySource("jeecg-datasource-config", props);
|
|
||||||
|
|
||||||
propertySources.addLast(propertySource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -32,6 +32,10 @@ public class JeecgBaseConfig {
|
|||||||
*/
|
*/
|
||||||
private Firewall firewall;
|
private Firewall firewall;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shiro拦截排除
|
||||||
|
*/
|
||||||
|
private Shiro shiro;
|
||||||
/**
|
/**
|
||||||
* 上传文件配置
|
* 上传文件配置
|
||||||
*/
|
*/
|
||||||
@ -84,6 +88,14 @@ public class JeecgBaseConfig {
|
|||||||
this.signatureSecret = signatureSecret;
|
this.signatureSecret = signatureSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Shiro getShiro() {
|
||||||
|
return shiro;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShiro(Shiro shiro) {
|
||||||
|
this.shiro = shiro;
|
||||||
|
}
|
||||||
|
|
||||||
public Path getPath() {
|
public Path getPath() {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,183 +1,183 @@
|
|||||||
//package org.jeecg.config;
|
package org.jeecg.config;
|
||||||
//
|
|
||||||
// 已使用swagger3config平替
|
|
||||||
//import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||||
//import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
//import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
//import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
//import org.springframework.beans.factory.config.BeanPostProcessor;
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
//import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
//import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
//import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
//import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
//import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
//import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
//import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||||
//import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
|
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
|
||||||
//import springfox.documentation.builders.ApiInfoBuilder;
|
import springfox.documentation.builders.ApiInfoBuilder;
|
||||||
//import springfox.documentation.builders.ParameterBuilder;
|
import springfox.documentation.builders.ParameterBuilder;
|
||||||
//import springfox.documentation.builders.PathSelectors;
|
import springfox.documentation.builders.PathSelectors;
|
||||||
//import springfox.documentation.builders.RequestHandlerSelectors;
|
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||||
//import springfox.documentation.oas.annotations.EnableOpenApi;
|
import springfox.documentation.oas.annotations.EnableOpenApi;
|
||||||
//import springfox.documentation.schema.ModelRef;
|
import springfox.documentation.schema.ModelRef;
|
||||||
//import springfox.documentation.service.*;
|
import springfox.documentation.service.*;
|
||||||
//import springfox.documentation.spi.DocumentationType;
|
import springfox.documentation.spi.DocumentationType;
|
||||||
//import springfox.documentation.spi.service.contexts.SecurityContext;
|
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||||
//import springfox.documentation.spring.web.plugins.Docket;
|
import springfox.documentation.spring.web.plugins.Docket;
|
||||||
//import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
|
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
|
||||||
//import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
|
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
|
||||||
//import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||||
//
|
|
||||||
//import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
//import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
//import java.util.Collections;
|
import java.util.Collections;
|
||||||
//import java.util.List;
|
import java.util.List;
|
||||||
//import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
//
|
|
||||||
///**
|
/**
|
||||||
// * @Author scott
|
* @Author scott
|
||||||
// */
|
*/
|
||||||
//@Configuration
|
@Configuration
|
||||||
//@EnableSwagger2 //开启 Swagger2
|
@EnableSwagger2 //开启 Swagger2
|
||||||
//@EnableKnife4j //开启 knife4j,可以不写
|
@EnableKnife4j //开启 knife4j,可以不写
|
||||||
//@Import(BeanValidatorPluginsConfiguration.class)
|
@Import(BeanValidatorPluginsConfiguration.class)
|
||||||
//public class Swagger2Config implements WebMvcConfigurer {
|
public class Swagger2Config implements WebMvcConfigurer {
|
||||||
//
|
|
||||||
// /**
|
/**
|
||||||
// *
|
*
|
||||||
// * 显示swagger-ui.html文档展示页,还必须注入swagger资源:
|
* 显示swagger-ui.html文档展示页,还必须注入swagger资源:
|
||||||
// *
|
*
|
||||||
// * @param registry
|
* @param registry
|
||||||
// */
|
*/
|
||||||
// @Override
|
@Override
|
||||||
// public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
// registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
|
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
|
||||||
// registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
|
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
|
||||||
// registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
|
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// /**
|
/**
|
||||||
// * swagger2的配置文件,这里可以配置swagger2的一些基本的内容,比如扫描的包等等
|
* swagger2的配置文件,这里可以配置swagger2的一些基本的内容,比如扫描的包等等
|
||||||
// *
|
*
|
||||||
// * @return Docket
|
* @return Docket
|
||||||
// */
|
*/
|
||||||
// @Bean(value = "defaultApi2")
|
@Bean(value = "defaultApi2")
|
||||||
// public Docket defaultApi2() {
|
public Docket defaultApi2() {
|
||||||
// return new Docket(DocumentationType.SWAGGER_2)
|
return new Docket(DocumentationType.SWAGGER_2)
|
||||||
// .apiInfo(apiInfo())
|
.apiInfo(apiInfo())
|
||||||
// .select()
|
.select()
|
||||||
// //此包路径下的类,才生成接口文档
|
//此包路径下的类,才生成接口文档
|
||||||
// .apis(RequestHandlerSelectors.basePackage("org.jeecg"))
|
.apis(RequestHandlerSelectors.basePackage("org.jeecg"))
|
||||||
// //加了ApiOperation注解的类,才生成接口文档
|
//加了ApiOperation注解的类,才生成接口文档
|
||||||
// .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
|
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
|
||||||
// .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||||
// .paths(PathSelectors.any())
|
.paths(PathSelectors.any())
|
||||||
// .build()
|
.build()
|
||||||
// .securitySchemes(Collections.singletonList(securityScheme()))
|
.securitySchemes(Collections.singletonList(securityScheme()))
|
||||||
// .securityContexts(securityContexts())
|
.securityContexts(securityContexts())
|
||||||
// .globalOperationParameters(setHeaderToken());
|
.globalOperationParameters(setHeaderToken());
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// /***
|
/***
|
||||||
// * oauth2配置
|
* oauth2配置
|
||||||
// * 需要增加swagger授权回调地址
|
* 需要增加swagger授权回调地址
|
||||||
// * http://localhost:8888/webjars/springfox-swagger-ui/o2c.html
|
* http://localhost:8888/webjars/springfox-swagger-ui/o2c.html
|
||||||
// * @return
|
* @return
|
||||||
// */
|
*/
|
||||||
// @Bean
|
@Bean
|
||||||
// SecurityScheme securityScheme() {
|
SecurityScheme securityScheme() {
|
||||||
// return new ApiKey(CommonConstant.X_ACCESS_TOKEN, CommonConstant.X_ACCESS_TOKEN, "header");
|
return new ApiKey(CommonConstant.X_ACCESS_TOKEN, CommonConstant.X_ACCESS_TOKEN, "header");
|
||||||
// }
|
}
|
||||||
// /**
|
/**
|
||||||
// * JWT token
|
* JWT token
|
||||||
// * @return
|
* @return
|
||||||
// */
|
*/
|
||||||
// private List<Parameter> setHeaderToken() {
|
private List<Parameter> setHeaderToken() {
|
||||||
// ParameterBuilder tokenPar = new ParameterBuilder();
|
ParameterBuilder tokenPar = new ParameterBuilder();
|
||||||
// List<Parameter> pars = new ArrayList<>();
|
List<Parameter> pars = new ArrayList<>();
|
||||||
// tokenPar.name(CommonConstant.X_ACCESS_TOKEN).description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
tokenPar.name(CommonConstant.X_ACCESS_TOKEN).description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||||
// pars.add(tokenPar.build());
|
pars.add(tokenPar.build());
|
||||||
// return pars;
|
return pars;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// /**
|
/**
|
||||||
// * api文档的详细信息函数,注意这里的注解引用的是哪个
|
* api文档的详细信息函数,注意这里的注解引用的是哪个
|
||||||
// *
|
*
|
||||||
// * @return
|
* @return
|
||||||
// */
|
*/
|
||||||
// private ApiInfo apiInfo() {
|
private ApiInfo apiInfo() {
|
||||||
// return new ApiInfoBuilder()
|
return new ApiInfoBuilder()
|
||||||
// // //大标题
|
// //大标题
|
||||||
// .title("JeecgBoot 后台服务API接口文档")
|
.title("JeecgBoot 后台服务API接口文档")
|
||||||
// // 版本号
|
// 版本号
|
||||||
// .version("1.0")
|
.version("1.0")
|
||||||
//// .termsOfServiceUrl("NO terms of service")
|
// .termsOfServiceUrl("NO terms of service")
|
||||||
// // 描述
|
// 描述
|
||||||
// .description("后台API接口")
|
.description("后台API接口")
|
||||||
// // 作者
|
// 作者
|
||||||
// .contact(new Contact("北京国炬信息技术有限公司","www.jeccg.com","jeecgos@163.com"))
|
.contact(new Contact("北京国炬信息技术有限公司","www.jeccg.com","jeecgos@163.com"))
|
||||||
// .license("The Apache License, Version 2.0")
|
.license("The Apache License, Version 2.0")
|
||||||
// .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
|
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
|
||||||
// .build();
|
.build();
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// /**
|
/**
|
||||||
// * 新增 securityContexts 保持登录状态
|
* 新增 securityContexts 保持登录状态
|
||||||
// */
|
*/
|
||||||
// private List<SecurityContext> securityContexts() {
|
private List<SecurityContext> securityContexts() {
|
||||||
// return new ArrayList(
|
return new ArrayList(
|
||||||
// Collections.singleton(SecurityContext.builder()
|
Collections.singleton(SecurityContext.builder()
|
||||||
// .securityReferences(defaultAuth())
|
.securityReferences(defaultAuth())
|
||||||
// .forPaths(PathSelectors.regex("^(?!auth).*$"))
|
.forPaths(PathSelectors.regex("^(?!auth).*$"))
|
||||||
// .build())
|
.build())
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private List<SecurityReference> defaultAuth() {
|
private List<SecurityReference> defaultAuth() {
|
||||||
// AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||||||
// AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||||
// authorizationScopes[0] = authorizationScope;
|
authorizationScopes[0] = authorizationScope;
|
||||||
// return new ArrayList(
|
return new ArrayList(
|
||||||
// Collections.singleton(new SecurityReference(CommonConstant.X_ACCESS_TOKEN, authorizationScopes)));
|
Collections.singleton(new SecurityReference(CommonConstant.X_ACCESS_TOKEN, authorizationScopes)));
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// /**
|
/**
|
||||||
// * 解决springboot2.6 和springfox不兼容问题
|
* 解决springboot2.6 和springfox不兼容问题
|
||||||
// * @return
|
* @return
|
||||||
// */
|
*/
|
||||||
// @Bean
|
@Bean
|
||||||
// public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
|
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
|
||||||
// return new BeanPostProcessor() {
|
return new BeanPostProcessor() {
|
||||||
//
|
|
||||||
// @Override
|
@Override
|
||||||
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||||
// if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
|
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
|
||||||
// customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
|
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
|
||||||
// }
|
}
|
||||||
// return bean;
|
return bean;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
|
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
|
||||||
// List<T> copy = mappings.stream()
|
List<T> copy = mappings.stream()
|
||||||
// .filter(mapping -> mapping.getPatternParser() == null)
|
.filter(mapping -> mapping.getPatternParser() == null)
|
||||||
// .collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// mappings.clear();
|
mappings.clear();
|
||||||
// mappings.addAll(copy);
|
mappings.addAll(copy);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
// private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
|
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
|
||||||
// try {
|
try {
|
||||||
// Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
|
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
|
||||||
// field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
// return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
|
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
|
||||||
// } catch (IllegalArgumentException | IllegalAccessException e) {
|
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||||
// throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
//
|
|
||||||
//}
|
}
|
||||||
|
|||||||
@ -1,59 +0,0 @@
|
|||||||
package org.jeecg.config;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.models.Components;
|
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
|
||||||
import io.swagger.v3.oas.models.Paths;
|
|
||||||
import io.swagger.v3.oas.models.info.Contact;
|
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
|
||||||
import io.swagger.v3.oas.models.info.License;
|
|
||||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
|
||||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
|
||||||
import org.springdoc.core.models.GroupedOpenApi;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class Swagger3Config implements WebMvcConfigurer {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* 显示swagger-ui.html文档展示页,还必须注入swagger资源:
|
|
||||||
*
|
|
||||||
* @param registry
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
|
||||||
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
|
|
||||||
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
|
|
||||||
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public GroupedOpenApi swaggerOpenApi() {
|
|
||||||
return GroupedOpenApi.builder()
|
|
||||||
.group("default")
|
|
||||||
.packagesToScan("org.jeecg")
|
|
||||||
// 剔除以下几个包路径的接口生成文档
|
|
||||||
.packagesToExclude("org.jeecg.modules.drag", "org.jeecg.modules.online", "org.jeecg.modules.jmreport")
|
|
||||||
// 加了Operation注解的方法,才生成接口文档
|
|
||||||
.addOpenApiMethodFilter(method -> method.isAnnotationPresent(Operation.class))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public OpenAPI customOpenAPI() {
|
|
||||||
return new OpenAPI()
|
|
||||||
.info(new Info()
|
|
||||||
.title("JeecgBoot 后台服务API接口文档")
|
|
||||||
.version("1.0")
|
|
||||||
.contact(new Contact().name("北京国炬信息技术有限公司").url("www.jeccg.com").email("jeecgos@163.com"))
|
|
||||||
.description( "后台API接口")
|
|
||||||
.termsOfService("NO terms of service")
|
|
||||||
.license(new License().name("Apache 2.0").url("http://www.apache.org/licenses/LICENSE-2.0.html"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
package org.jeecg.config;
|
|
||||||
|
|
||||||
import io.undertow.server.DefaultByteBufferPool;
|
|
||||||
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
|
||||||
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
|
||||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class UndertowCustomizer implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
|
||||||
@Override
|
|
||||||
public void customize(UndertowServletWebServerFactory factory) {
|
|
||||||
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
|
||||||
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
|
||||||
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 1024));
|
|
||||||
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -10,15 +10,12 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
|||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
|
||||||
import io.micrometer.prometheus.PrometheusMeterRegistry;
|
import io.micrometer.prometheus.PrometheusMeterRegistry;
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
|
||||||
import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@ -33,6 +30,7 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
|
|||||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@ -135,11 +133,8 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
|
|||||||
* https://blog.csdn.net/u013810234/article/details/110097201
|
* https://blog.csdn.net/u013810234/article/details/110097201
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public InMemoryHttpExchangeRepository getInMemoryHttpTrace(){
|
public InMemoryHttpTraceRepository getInMemoryHttpTrace(){
|
||||||
InMemoryHttpExchangeRepository repository = new InMemoryHttpExchangeRepository();
|
return new InMemoryHttpTraceRepository();
|
||||||
// 默认保存1000条http请求记录
|
|
||||||
repository.setCapacity(1000);
|
|
||||||
return repository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@ public class WebSocketConfig {
|
|||||||
FilterRegistrationBean bean = new FilterRegistrationBean();
|
FilterRegistrationBean bean = new FilterRegistrationBean();
|
||||||
bean.setFilter(websocketFilter());
|
bean.setFilter(websocketFilter());
|
||||||
//TODO 临时注释掉,测试下线上socket总断的问题
|
//TODO 临时注释掉,测试下线上socket总断的问题
|
||||||
bean.addUrlPatterns("/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
|
bean.addUrlPatterns("/taskCountSocket/*", "/websocket/*","/eoaSocket/*","/eoaNewChatSocket/*", "/newsWebsocket/*", "/vxeSocket/*");
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,8 @@ package org.jeecg.config.filter;
|
|||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.config.sign.util.BodyReaderHttpServletRequestWrapper;
|
import org.jeecg.config.sign.util.BodyReaderHttpServletRequestWrapper;
|
||||||
|
|
||||||
import jakarta.servlet.*;
|
import javax.servlet.*;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import org.jeecg.common.util.SpringContextUtils;
|
|||||||
import org.jeecg.common.util.TokenUtils;
|
import org.jeecg.common.util.TokenUtils;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
|
||||||
import jakarta.servlet.*;
|
import javax.servlet.*;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,21 +2,24 @@ package org.jeecg.config.firewall.interceptor;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.jeecg.common.api.CommonAPI;
|
import org.jeecg.common.api.CommonAPI;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
import org.jeecg.common.system.util.JwtUtil;
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.common.util.CommonUtils;
|
import org.jeecg.common.util.CommonUtils;
|
||||||
import org.jeecg.common.util.SpringContextUtils;
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
import org.jeecg.config.security.utils.SecureUtil;
|
import org.jeecg.config.firewall.interceptor.enums.LowCodeUrlsEnum;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -63,7 +66,7 @@ public class LowCodeModeInterceptor implements HandlerInterceptor {
|
|||||||
if (jeecgBaseConfig.getFirewall()!=null && LowCodeModeInterceptor.LOW_CODE_MODE_PROD.equals(jeecgBaseConfig.getFirewall().getLowCodeMode())) {
|
if (jeecgBaseConfig.getFirewall()!=null && LowCodeModeInterceptor.LOW_CODE_MODE_PROD.equals(jeecgBaseConfig.getFirewall().getLowCodeMode())) {
|
||||||
String requestURI = request.getRequestURI().substring(request.getContextPath().length());
|
String requestURI = request.getRequestURI().substring(request.getContextPath().length());
|
||||||
log.info("低代码模式,拦截请求路径:" + requestURI);
|
log.info("低代码模式,拦截请求路径:" + requestURI);
|
||||||
LoginUser loginUser = SecureUtil.currentUser();
|
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
Set<String> hasRoles = null;
|
Set<String> hasRoles = null;
|
||||||
if (loginUser == null) {
|
if (loginUser == null) {
|
||||||
loginUser = commonAPI.getUserByName(JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
|
loginUser = commonAPI.getUserByName(JwtUtil.getUserNameByToken(SpringContextUtils.getHttpServletRequest()));
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import org.apache.ibatis.executor.Executor;
|
|||||||
import org.apache.ibatis.mapping.MappedStatement;
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
import org.apache.ibatis.mapping.SqlCommandType;
|
import org.apache.ibatis.mapping.SqlCommandType;
|
||||||
import org.apache.ibatis.plugin.*;
|
import org.apache.ibatis.plugin.*;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.jeecg.common.config.TenantContext;
|
import org.jeecg.common.config.TenantContext;
|
||||||
import org.jeecg.common.constant.TenantConstant;
|
import org.jeecg.common.constant.TenantConstant;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.jeecg.config.security.utils.SecureUtil;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@ -173,7 +173,7 @@ public class MybatisInterceptor implements Interceptor {
|
|||||||
private LoginUser getLoginUser() {
|
private LoginUser getLoginUser() {
|
||||||
LoginUser sysUser = null;
|
LoginUser sysUser = null;
|
||||||
try {
|
try {
|
||||||
sysUser = SecureUtil.currentUser() != null ? SecureUtil.currentUser() : null;
|
sysUser = SecurityUtils.getSubject().getPrincipal() != null ? (LoginUser) SecurityUtils.getSubject().getPrincipal() : null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//e.printStackTrace();
|
//e.printStackTrace();
|
||||||
sysUser = null;
|
sysUser = null;
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import org.jeecg.common.util.SpringContextUtils;
|
|||||||
import org.jeecg.config.mybatis.ThreadLocalDataHelper;
|
import org.jeecg.config.mybatis.ThreadLocalDataHelper;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态数据源切换拦截器
|
* 动态数据源切换拦截器
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
package org.jeecg.config.security;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
||||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spring authorization server 注册客户端便捷工具类
|
|
||||||
* @author eightmonth@qq.com
|
|
||||||
* @date 2024/3/7 11:22
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class ClientService {
|
|
||||||
|
|
||||||
private RegisteredClientRepository registeredClientRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改客户端token有效期
|
|
||||||
* 认证码、设备码有效期与accessToken有效期保持一致
|
|
||||||
*/
|
|
||||||
public void updateTokenValidation(String clientId, Long accessTokenValidation, Long refreshTokenValidation){
|
|
||||||
RegisteredClient registeredClient = findByClientId(clientId);
|
|
||||||
RegisteredClient.Builder builder = RegisteredClient.from(registeredClient);
|
|
||||||
TokenSettings tokenSettings = TokenSettings.builder()
|
|
||||||
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
|
|
||||||
.accessTokenTimeToLive(Duration.ofSeconds(accessTokenValidation))
|
|
||||||
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
|
|
||||||
.reuseRefreshTokens(true)
|
|
||||||
.refreshTokenTimeToLive(Duration.ofSeconds(refreshTokenValidation))
|
|
||||||
.authorizationCodeTimeToLive(Duration.ofSeconds(accessTokenValidation))
|
|
||||||
.deviceCodeTimeToLive(Duration.ofSeconds(accessTokenValidation))
|
|
||||||
.build();
|
|
||||||
builder.tokenSettings(tokenSettings);
|
|
||||||
registeredClientRepository.save(builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改客户端授权类型
|
|
||||||
* @param clientId
|
|
||||||
* @param grantTypes
|
|
||||||
*/
|
|
||||||
public void updateGrantType(String clientId, Set<AuthorizationGrantType> grantTypes) {
|
|
||||||
RegisteredClient registeredClient = findByClientId(clientId);
|
|
||||||
RegisteredClient.Builder builder = RegisteredClient.from(registeredClient);
|
|
||||||
for (AuthorizationGrantType grantType : grantTypes) {
|
|
||||||
builder.authorizationGrantType(grantType);
|
|
||||||
}
|
|
||||||
registeredClientRepository.save(builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改客户端重定向uri
|
|
||||||
* @param clientId
|
|
||||||
* @param redirectUris
|
|
||||||
*/
|
|
||||||
public void updateRedirectUris(String clientId, String redirectUris) {
|
|
||||||
RegisteredClient registeredClient = findByClientId(clientId);
|
|
||||||
RegisteredClient.Builder builder = RegisteredClient.from(registeredClient);
|
|
||||||
builder.redirectUri(redirectUris);
|
|
||||||
registeredClientRepository.save(builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改客户端授权范围
|
|
||||||
* @param clientId
|
|
||||||
* @param scopes
|
|
||||||
*/
|
|
||||||
public void updateScopes(String clientId, Set<String> scopes) {
|
|
||||||
RegisteredClient registeredClient = findByClientId(clientId);
|
|
||||||
RegisteredClient.Builder builder = RegisteredClient.from(registeredClient);
|
|
||||||
for (String scope : scopes) {
|
|
||||||
builder.scope(scope);
|
|
||||||
}
|
|
||||||
registeredClientRepository.save(builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public RegisteredClient findByClientId(String clientId) {
|
|
||||||
return registeredClientRepository.findByClientId(clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
package org.jeecg.config.security;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jeecg.common.api.CommonAPI;
|
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
|
||||||
import org.jeecg.common.util.RedisUtil;
|
|
||||||
import org.jeecg.config.security.utils.SecureUtil;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.PatternMatchUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spring authorization server自定义权限处理,根据@PreAuthorize注解,判断当前用户是否具备权限
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/10 17:00
|
|
||||||
*/
|
|
||||||
@Service("jps")
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Slf4j
|
|
||||||
public class JeecgPermissionService {
|
|
||||||
private final String SPLIT = "::";
|
|
||||||
private final String PERM_PREFIX = "jps" + SPLIT;
|
|
||||||
|
|
||||||
private final CommonAPI commonAPI;
|
|
||||||
private final RedisUtil redisUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断接口是否有任意xxx,xxx权限
|
|
||||||
* @param permissions 权限
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
public boolean requiresPermissions(String... permissions) {
|
|
||||||
if (ArrayUtil.isEmpty(permissions)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LoginUser loginUser = SecureUtil.currentUser();
|
|
||||||
|
|
||||||
Object cache = redisUtil.get(buildKey("permission", loginUser.getUsername()));
|
|
||||||
Set<String> permissionList;
|
|
||||||
if (Objects.nonNull(cache)) {
|
|
||||||
permissionList = (Set<String>) cache;
|
|
||||||
} else {
|
|
||||||
permissionList = commonAPI.queryUserAuths(loginUser.getUsername());
|
|
||||||
redisUtil.set(buildKey("permission", loginUser.getUsername()), permissionList);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean pass = permissionList.stream().filter(StringUtils::hasText)
|
|
||||||
.anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
|
|
||||||
if (!pass) {
|
|
||||||
log.error("权限不足,缺少权限:"+ Arrays.toString(permissions));
|
|
||||||
}
|
|
||||||
return pass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断接口是否有任意xxx,xxx角色
|
|
||||||
* @param roles 角色
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
public boolean requiresRoles(String... roles) {
|
|
||||||
if (ArrayUtil.isEmpty(roles)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LoginUser loginUser = SecureUtil.currentUser();
|
|
||||||
|
|
||||||
Object cache = redisUtil.get(buildKey("role", loginUser.getUsername()));
|
|
||||||
Set<String> roleList;
|
|
||||||
if (Objects.nonNull(cache)) {
|
|
||||||
roleList = (Set<String>) cache;
|
|
||||||
} else {
|
|
||||||
roleList = commonAPI.queryUserRoles(loginUser.getUsername());
|
|
||||||
redisUtil.set(buildKey("role", loginUser.getUsername()), roleList);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean pass = roleList.stream().filter(StringUtils::hasText)
|
|
||||||
.anyMatch(x -> PatternMatchUtils.simpleMatch(roles, x));
|
|
||||||
if (!pass) {
|
|
||||||
log.error("权限不足,缺少角色:" + Arrays.toString(roles));
|
|
||||||
}
|
|
||||||
return pass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 由于缓存key是以人的维度,角色列表、权限列表在值中,jeecg是以权限列表绑定在角色上,形成的权限集合
|
|
||||||
* 权限发生变更时,需要清理全部人的权限缓存
|
|
||||||
*/
|
|
||||||
public void clearCache() {
|
|
||||||
redisUtil.removeAll(PERM_PREFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildKey(String type, String username) {
|
|
||||||
return PERM_PREFIX + type + SPLIT + username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
package org.jeecg.config.security;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spring authorization server 自定义redis保存授权范围信息
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class JeecgRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
|
|
||||||
|
|
||||||
private final RedisTemplate<String, Object> redisTemplate;
|
|
||||||
|
|
||||||
private final static Long TIMEOUT = 10L;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
|
||||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
|
||||||
|
|
||||||
redisTemplate.opsForValue().set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT,
|
|
||||||
TimeUnit.MINUTES);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
|
|
||||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
|
||||||
redisTemplate.delete(buildKey(authorizationConsent));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
|
|
||||||
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
|
|
||||||
Assert.hasText(principalName, "principalName cannot be empty");
|
|
||||||
return (OAuth2AuthorizationConsent) redisTemplate.opsForValue()
|
|
||||||
.get(buildKey(registeredClientId, principalName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String buildKey(String registeredClientId, String principalName) {
|
|
||||||
return "token:consent:" + registeredClientId + ":" + principalName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String buildKey(OAuth2AuthorizationConsent authorizationConsent) {
|
|
||||||
return buildKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,181 +0,0 @@
|
|||||||
package org.jeecg.config.security;
|
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spring authorization server自定义redis保存认证信息
|
|
||||||
* @author EightMonth
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class JeecgRedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
|
|
||||||
|
|
||||||
private final static Long TIMEOUT = 10L;
|
|
||||||
|
|
||||||
private static final String AUTHORIZATION = "token";
|
|
||||||
|
|
||||||
private final RedisTemplate<String, Object> redisTemplate;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(OAuth2Authorization authorization) {
|
|
||||||
Assert.notNull(authorization, "authorization cannot be null");
|
|
||||||
|
|
||||||
if (isState(authorization)) {
|
|
||||||
String token = authorization.getAttribute("state");
|
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
|
||||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT,
|
|
||||||
TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCode(authorization)) {
|
|
||||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
|
||||||
.getToken(OAuth2AuthorizationCode.class);
|
|
||||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
|
||||||
long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
|
|
||||||
authorizationCodeToken.getExpiresAt());
|
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
|
||||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()),
|
|
||||||
authorization, between, TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRefreshToken(authorization)) {
|
|
||||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
|
||||||
long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
|
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
|
||||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()),
|
|
||||||
authorization, between, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAccessToken(authorization)) {
|
|
||||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
|
||||||
long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
|
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
|
||||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()),
|
|
||||||
authorization, between, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
|
||||||
String tokenUsername = String.format("%s::%s::%s", AUTHORIZATION, authorization.getPrincipalName(), accessToken.getTokenValue());
|
|
||||||
redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove(OAuth2Authorization authorization) {
|
|
||||||
Assert.notNull(authorization, "authorization cannot be null");
|
|
||||||
|
|
||||||
List<String> keys = new ArrayList<>();
|
|
||||||
if (isState(authorization)) {
|
|
||||||
String token = authorization.getAttribute("state");
|
|
||||||
keys.add(buildKey(OAuth2ParameterNames.STATE, token));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCode(authorization)) {
|
|
||||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
|
||||||
.getToken(OAuth2AuthorizationCode.class);
|
|
||||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
|
||||||
keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRefreshToken(authorization)) {
|
|
||||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
|
||||||
keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAccessToken(authorization)) {
|
|
||||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
|
||||||
keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()));
|
|
||||||
|
|
||||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
|
||||||
String key = String.format("%s::%s::%s", AUTHORIZATION, authorization.getPrincipalName(), accessToken.getTokenValue());
|
|
||||||
keys.add(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
redisTemplate.delete(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public OAuth2Authorization findById(String id) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
|
|
||||||
Assert.hasText(token, "token cannot be empty");
|
|
||||||
Assert.notNull(tokenType, "tokenType cannot be empty");
|
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
|
||||||
return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildKey(String type, String id) {
|
|
||||||
return String.format("%s::%s::%s", AUTHORIZATION, type, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isState(OAuth2Authorization authorization) {
|
|
||||||
return Objects.nonNull(authorization.getAttribute("state"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isCode(OAuth2Authorization authorization) {
|
|
||||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
|
||||||
.getToken(OAuth2AuthorizationCode.class);
|
|
||||||
return Objects.nonNull(authorizationCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isRefreshToken(OAuth2Authorization authorization) {
|
|
||||||
return Objects.nonNull(authorization.getRefreshToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAccessToken(OAuth2Authorization authorization) {
|
|
||||||
return Objects.nonNull(authorization.getAccessToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 扩展方法根据 username 查询是否存在存储的
|
|
||||||
* @param authentication
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public void removeByUsername(Authentication authentication) {
|
|
||||||
// 根据 username查询对应access-token
|
|
||||||
String authenticationName = authentication.getName();
|
|
||||||
|
|
||||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
|
||||||
String tokenUsernameKey = String.format("%s::%s::*", AUTHORIZATION, authenticationName);
|
|
||||||
Set<String> keys = redisTemplate.keys(tokenUsernameKey);
|
|
||||||
if (CollUtil.isEmpty(keys)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Object> tokenList = redisTemplate.opsForValue().multiGet(keys);
|
|
||||||
|
|
||||||
for (Object token : tokenList) {
|
|
||||||
// 根据token 查询存储的 OAuth2Authorization
|
|
||||||
OAuth2Authorization authorization = this.findByToken((String) token, OAuth2TokenType.ACCESS_TOKEN);
|
|
||||||
// 根据 OAuth2Authorization 删除相关令牌
|
|
||||||
this.remove(authorization);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
package org.jeecg.config.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录模式
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/10 17:43
|
|
||||||
*/
|
|
||||||
public class LoginType {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密码模式
|
|
||||||
*/
|
|
||||||
public static final String PASSWORD = "password";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手机号+验证码模式
|
|
||||||
*/
|
|
||||||
public static final String PHONE = "phone";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* app登录
|
|
||||||
*/
|
|
||||||
public static final String APP = "app";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 扫码登录
|
|
||||||
*/
|
|
||||||
public static final String SCAN = "scan";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所有联合登录,比如github\钉钉\企业微信\微信
|
|
||||||
*/
|
|
||||||
public static final String SOCIAL = "social";
|
|
||||||
|
|
||||||
public static final String SELF = "self";
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
package org.jeecg.config.security;
|
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.jeecg.common.system.util.JwtUtil;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
|
|
||||||
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当用户被强退时,使客户端token失效
|
|
||||||
* @author eightmonth@qq.com
|
|
||||||
* @date 2024/3/7 17:30
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class RedisTokenValidationFilter extends OncePerRequestFilter {
|
|
||||||
private OAuth2AuthorizationService authorizationService;
|
|
||||||
private JwtDecoder jwtDecoder;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
|
||||||
// 从请求中获取token
|
|
||||||
DefaultBearerTokenResolver defaultBearerTokenResolver = new DefaultBearerTokenResolver();
|
|
||||||
String token = defaultBearerTokenResolver.resolve(request);
|
|
||||||
|
|
||||||
|
|
||||||
if (Objects.nonNull(token)) {
|
|
||||||
// 检查认证信息是否已被清除,如果已被清除,则令该token失效
|
|
||||||
OAuth2Authorization oAuth2Authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
|
||||||
if (Objects.isNull(oAuth2Authorization)) {
|
|
||||||
throw new OAuth2AuthenticationException(BearerTokenErrors.invalidToken("认证信息已失效,请重新登录"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,262 +0,0 @@
|
|||||||
package org.jeecg.config.security;
|
|
||||||
|
|
||||||
import com.nimbusds.jose.jwk.JWKSet;
|
|
||||||
import com.nimbusds.jose.jwk.RSAKey;
|
|
||||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
|
||||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
|
||||||
import com.nimbusds.jose.proc.SecurityContext;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.jeecg.config.security.app.AppGrantAuthenticationConvert;
|
|
||||||
import org.jeecg.config.security.app.AppGrantAuthenticationProvider;
|
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationConvert;
|
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationProvider;
|
|
||||||
import org.jeecg.config.security.phone.PhoneGrantAuthenticationConvert;
|
|
||||||
import org.jeecg.config.security.phone.PhoneGrantAuthenticationProvider;
|
|
||||||
import org.jeecg.config.security.social.SocialGrantAuthenticationConvert;
|
|
||||||
import org.jeecg.config.security.social.SocialGrantAuthenticationProvider;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.security.config.Customizer;
|
|
||||||
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.annotation.web.configurers.HeadersConfigurer;
|
|
||||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.*;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
|
||||||
import org.springframework.security.web.header.writers.frameoptions.RegExpAllowFromStrategy;
|
|
||||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
|
||||||
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spring authorization server核心配置
|
|
||||||
* @author eightmonth@qq.com
|
|
||||||
* @date 2024/1/2 9:29
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableMethodSecurity
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class SecurityConfig {
|
|
||||||
|
|
||||||
private JdbcTemplate jdbcTemplate;
|
|
||||||
private OAuth2AuthorizationService authorizationService;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Order(1)
|
|
||||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
|
|
||||||
throws Exception {
|
|
||||||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
|
||||||
// 注册自定义登录类型
|
|
||||||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
|
||||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new PasswordGrantAuthenticationConvert())
|
|
||||||
.authenticationProvider(new PasswordGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
|
||||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new PhoneGrantAuthenticationConvert())
|
|
||||||
.authenticationProvider(new PhoneGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
|
||||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new AppGrantAuthenticationConvert())
|
|
||||||
.authenticationProvider(new AppGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
|
||||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new SocialGrantAuthenticationConvert())
|
|
||||||
.authenticationProvider(new SocialGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
|
||||||
//开启OpenID Connect 1.0(其中oidc为OpenID Connect的缩写)。 访问 /.well-known/openid-configuration即可获取认证信息
|
|
||||||
.oidc(Customizer.withDefaults());
|
|
||||||
http
|
|
||||||
//将需要认证的请求,重定向到login页面行登录认证。
|
|
||||||
.exceptionHandling((exceptions) -> exceptions
|
|
||||||
.defaultAuthenticationEntryPointFor(
|
|
||||||
new LoginUrlAuthenticationEntryPoint("/sys/login"),
|
|
||||||
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// 使用jwt处理接收到的access token
|
|
||||||
.oauth2ResourceServer(oauth2ResourceServer ->
|
|
||||||
oauth2ResourceServer.jwt(Customizer.withDefaults()));
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Order(2)
|
|
||||||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
|
|
||||||
throws Exception {
|
|
||||||
http
|
|
||||||
//设置所有请求都需要认证,未认证的请求都被重定向到login页面进行登录
|
|
||||||
.authorizeHttpRequests((authorize) -> authorize
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/cas/client/validateLogin")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/randomImage/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/checkCaptcha")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/login")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/mLogin")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/logout")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/thirdLogin/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/getEncryptedString")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/sms")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/phoneLogin")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/user/checkOnlyUser")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/user/register")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/user/phoneVerification")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/user/passwordChange")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/auth/2step-code")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/common/static/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/common/pdf/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/generic/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/getLoginQrcode/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/getQrcodeToken/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/checkAuth")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/doc.html")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.js")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.css")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.html")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.svg")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.pdf")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.jpg")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.png")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.gif")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.ico")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.ttf")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.woff")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.woff2")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/druid/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/swagger-ui.html")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/swagger**/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/webjars/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/v3/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/WW_verify*")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/annountCement/show/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/jmreport/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.js.map")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.css.map")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/view")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/page/queryById")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getAllChartData")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getTotalData")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/mock/json/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/test/bigScreen/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/bigscreen/template1/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/bigscreen/template1/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/websocket/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/newsWebsocket/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/vxeSocket/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/test/seata/**")).permitAll()
|
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/error")).permitAll()
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
|
||||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
|
||||||
.cors(cors -> cors
|
|
||||||
.configurationSource(req -> {
|
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
|
||||||
config.applyPermitDefaultValues();
|
|
||||||
config.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
|
||||||
return config;
|
|
||||||
}))
|
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据库保存注册客户端信息
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public RegisteredClientRepository registeredClientRepository() {
|
|
||||||
return new JdbcRegisteredClientRepository(jdbcTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*配置 JWK,为JWT(id_token)提供加密密钥,用于加密/解密或签名/验签
|
|
||||||
* JWK详细见:https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key-41
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public JWKSource<SecurityContext> jwkSource() {
|
|
||||||
KeyPair keyPair = generateRsaKey();
|
|
||||||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
|
||||||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
|
||||||
RSAKey rsaKey = new RSAKey.Builder(publicKey)
|
|
||||||
.privateKey(privateKey)
|
|
||||||
// 重要!生产环境需要修改!
|
|
||||||
.keyID("jeecg")
|
|
||||||
.build();
|
|
||||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
|
||||||
return new ImmutableJWKSet<>(jwkSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public PasswordEncoder passwordEncoder() {
|
|
||||||
return NoOpPasswordEncoder.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*生成RSA密钥对,给上面jwkSource() 方法的提供密钥对
|
|
||||||
*/
|
|
||||||
private static KeyPair generateRsaKey() {
|
|
||||||
KeyPair keyPair;
|
|
||||||
try {
|
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
|
||||||
|
|
||||||
// 生产环境不应该设置secureRandom,seed如果被泄露,jwt容易被伪造
|
|
||||||
// 如果不设置secureRandom,会存在一个问题,当应用重启后,原有的token将会全部失效,因为重启的keyPair与之前已经不同
|
|
||||||
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
|
||||||
// 重要!生产环境需要修改!
|
|
||||||
secureRandom.setSeed("jeecg".getBytes());
|
|
||||||
keyPairGenerator.initialize(2048, secureRandom);
|
|
||||||
keyPair = keyPairGenerator.generateKeyPair();
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
return keyPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置jwt解析器
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
|
||||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*配置认证服务器请求地址
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public AuthorizationServerSettings authorizationServerSettings() {
|
|
||||||
return AuthorizationServerSettings.builder().tokenEndpoint("/sys/login").build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*配置token生成器
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
OAuth2TokenGenerator<?> tokenGenerator() {
|
|
||||||
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource()));
|
|
||||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
|
||||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
|
||||||
return new DelegatingOAuth2TokenGenerator(
|
|
||||||
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
package org.jeecg.config.security.app;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* APP模式认证转换器
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
public class AppGrantAuthenticationConvert implements AuthenticationConverter {
|
|
||||||
@Override
|
|
||||||
public Authentication convert(HttpServletRequest request) {
|
|
||||||
|
|
||||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
|
||||||
if (!LoginType.APP.equals(grantType)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
|
|
||||||
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
|
||||||
MultiValueMap<String, String> parameters = getParameters(request);
|
|
||||||
|
|
||||||
// username (REQUIRED)
|
|
||||||
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
|
|
||||||
if (!StringUtils.hasText(username) ||
|
|
||||||
parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
|
|
||||||
throw new OAuth2AuthenticationException("无效请求,用户名不能为空!");
|
|
||||||
}
|
|
||||||
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
|
|
||||||
if (!StringUtils.hasText(password) ||
|
|
||||||
parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
|
|
||||||
throw new OAuth2AuthenticationException("无效请求,密码不能为空!");
|
|
||||||
}
|
|
||||||
|
|
||||||
//收集要传入PasswordGrantAuthenticationToken构造方法的参数,
|
|
||||||
//该参数接下来在PasswordGrantAuthenticationProvider中使用
|
|
||||||
Map<String, Object> additionalParameters = new HashMap<>();
|
|
||||||
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
|
||||||
parameters.forEach((key, value) -> {
|
|
||||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
|
||||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
|
||||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
|
||||||
additionalParameters.put(key, value.get(0));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//返回自定义的PasswordGrantAuthenticationToken对象
|
|
||||||
return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
|
||||||
*/
|
|
||||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
|
||||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
|
||||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
|
||||||
parameterMap.forEach((key, values) -> {
|
|
||||||
if (values.length > 0) {
|
|
||||||
for (String value : values) {
|
|
||||||
parameters.add(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return parameters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,318 +0,0 @@
|
|||||||
package org.jeecg.config.security.app;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jeecg.common.api.CommonAPI;
|
|
||||||
import org.jeecg.common.constant.CacheConstant;
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
|
||||||
import org.jeecg.common.exception.JeecgCaptchaException;
|
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
|
||||||
import org.jeecg.common.system.vo.SysDepartModel;
|
|
||||||
import org.jeecg.common.util.Md5Util;
|
|
||||||
import org.jeecg.common.util.PasswordUtil;
|
|
||||||
import org.jeecg.common.util.RedisUtil;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
|
||||||
import org.jeecg.modules.base.service.BaseCommonService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.*;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* APP模式认证处理器,负责处理该认证模式下的核心逻辑
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class AppGrantAuthenticationProvider implements AuthenticationProvider {
|
|
||||||
|
|
||||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
|
||||||
|
|
||||||
private final OAuth2AuthorizationService authorizationService;
|
|
||||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
|
||||||
@Autowired
|
|
||||||
private CommonAPI commonAPI;
|
|
||||||
@Autowired
|
|
||||||
private RedisUtil redisUtil;
|
|
||||||
@Autowired
|
|
||||||
private JeecgBaseConfig jeecgBaseConfig;
|
|
||||||
@Autowired
|
|
||||||
private BaseCommonService baseCommonService;
|
|
||||||
|
|
||||||
public AppGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
|
||||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
|
||||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
|
||||||
this.authorizationService = authorizationService;
|
|
||||||
this.tokenGenerator = tokenGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
|
||||||
AppGrantAuthenticationToken appGrantAuthenticationToken = (AppGrantAuthenticationToken) authentication;
|
|
||||||
Map<String, Object> additionalParameter = appGrantAuthenticationToken.getAdditionalParameters();
|
|
||||||
|
|
||||||
// 授权类型
|
|
||||||
AuthorizationGrantType authorizationGrantType = appGrantAuthenticationToken.getGrantType();
|
|
||||||
// 用户名
|
|
||||||
String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
|
|
||||||
// 密码
|
|
||||||
String password = (String) additionalParameter.get(OAuth2ParameterNames.PASSWORD);
|
|
||||||
//请求参数权限范围
|
|
||||||
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
|
||||||
//请求参数权限范围专场集合
|
|
||||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
|
||||||
// 验证码
|
|
||||||
String captcha = (String) additionalParameter.get("captcha");
|
|
||||||
String checkKey = (String) additionalParameter.get("checkKey");
|
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(appGrantAuthenticationToken);
|
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
|
||||||
|
|
||||||
// 检查登录失败次数
|
|
||||||
if(isLoginFailOvertimes(username)){
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "该用户登录失败次数过多,请于10分钟后再次登录!");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(captcha==null){
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "验证码无效");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
String lowerCaseCaptcha = captcha.toLowerCase();
|
|
||||||
// 加入密钥作为混淆,避免简单的拼接,被外部利用,用户自定义该密钥即可
|
|
||||||
String origin = lowerCaseCaptcha+checkKey+jeecgBaseConfig.getSignatureSecret();
|
|
||||||
String realKey = Md5Util.md5Encode(origin, "utf-8");
|
|
||||||
Object checkCode = redisUtil.get(realKey);
|
|
||||||
//当进入登录页时,有一定几率出现验证码错误 #1714
|
|
||||||
if(checkCode==null || !checkCode.toString().equals(lowerCaseCaptcha)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "验证码错误");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "非法登录");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过用户名获取用户信息
|
|
||||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
|
||||||
//update-begin---author:eightmonth ---date:2024-04-30 for:【6168】master分支切sas分支登录发生错误-----------
|
|
||||||
if (Objects.isNull(loginUser) || !StringUtils.hasText(loginUser.getSalt())) {
|
|
||||||
redisUtil.del(CacheConstant.SYS_USERS_CACHE+"::"+username);
|
|
||||||
loginUser = commonAPI.getUserByName(username);
|
|
||||||
}
|
|
||||||
//update-end---author:eightmonth ---date::2024-04-30 for:【6168】master分支切sas分支登录发生错误--------------
|
|
||||||
// 检查用户可行性
|
|
||||||
checkUserIsEffective(loginUser);
|
|
||||||
|
|
||||||
// 不使用spring security passwordEncoder针对密码进行匹配,使用自有加密匹配,针对 spring security使用noop传输
|
|
||||||
password = PasswordUtil.encrypt(username, password, loginUser.getSalt());
|
|
||||||
if (!password.equals(loginUser.getPassword())) {
|
|
||||||
addLoginFailOvertimes(username);
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "用户名或密码不正确");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
|
||||||
|
|
||||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
|
||||||
.registeredClient(registeredClient)
|
|
||||||
.principal(usernamePasswordAuthenticationToken)
|
|
||||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
|
||||||
.authorizationGrantType(authorizationGrantType)
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.authorizationGrant(appGrantAuthenticationToken);
|
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
|
||||||
.principalName(clientPrincipal.getName())
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.attribute(Principal.class.getName(), username)
|
|
||||||
.authorizationGrantType(authorizationGrantType);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Access token -----
|
|
||||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
|
||||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (generatedAccessToken == null) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成访问token,请联系管理系。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
|
||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
|
||||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
|
||||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
|
||||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
|
||||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
authorizationBuilder.accessToken(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Refresh token -----
|
|
||||||
OAuth2RefreshToken refreshToken = null;
|
|
||||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
|
||||||
// 不向公共客户端颁发刷新令牌
|
|
||||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
|
||||||
|
|
||||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
|
||||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
|
||||||
authorizationBuilder.refreshToken(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
|
||||||
|
|
||||||
// 保存认证信息至redis
|
|
||||||
authorizationService.save(authorization);
|
|
||||||
|
|
||||||
// 登录成功,删除redis中的验证码
|
|
||||||
redisUtil.del(realKey);
|
|
||||||
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
|
|
||||||
baseCommonService.addLog("用户名: " + username + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
|
||||||
|
|
||||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
|
||||||
addition.put("token", accessToken.getTokenValue());
|
|
||||||
// 设置租户
|
|
||||||
JSONObject jsonObject = commonAPI.setLoginTenant(username);
|
|
||||||
addition.putAll(jsonObject.getInnerMap());
|
|
||||||
|
|
||||||
// 设置登录用户信息
|
|
||||||
addition.put("userInfo", loginUser);
|
|
||||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
|
||||||
|
|
||||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
|
||||||
addition.put("departs", departs);
|
|
||||||
if (departs == null || departs.size() == 0) {
|
|
||||||
addition.put("multi_depart", 0);
|
|
||||||
} else if (departs.size() == 1) {
|
|
||||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
|
||||||
addition.put("multi_depart", 1);
|
|
||||||
} else {
|
|
||||||
//查询当前是否有登录部门
|
|
||||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
|
||||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
|
||||||
}
|
|
||||||
addition.put("multi_depart", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兼容原有shiro登录结果处理
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("result", addition);
|
|
||||||
map.put("code", 200);
|
|
||||||
map.put("success", true);
|
|
||||||
map.put("timestamp", System.currentTimeMillis());
|
|
||||||
|
|
||||||
// 返回access_token、refresh_token以及其它信息给到前端
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> authentication) {
|
|
||||||
return AppGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
|
||||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
|
||||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
|
||||||
}
|
|
||||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
|
||||||
return clientPrincipal;
|
|
||||||
}
|
|
||||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录失败超出次数5 返回true
|
|
||||||
* @param username
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isLoginFailOvertimes(String username){
|
|
||||||
String key = CommonConstant.LOGIN_FAIL + username;
|
|
||||||
Object failTime = redisUtil.get(key);
|
|
||||||
if(failTime!=null){
|
|
||||||
Integer val = Integer.parseInt(failTime.toString());
|
|
||||||
if(val>5){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录登录失败次数
|
|
||||||
* @param username
|
|
||||||
*/
|
|
||||||
private void addLoginFailOvertimes(String username){
|
|
||||||
String key = CommonConstant.LOGIN_FAIL + username;
|
|
||||||
Object failTime = redisUtil.get(key);
|
|
||||||
Integer val = 0;
|
|
||||||
if(failTime!=null){
|
|
||||||
val = Integer.parseInt(failTime.toString());
|
|
||||||
}
|
|
||||||
// 10分钟
|
|
||||||
redisUtil.set(key, ++val, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验用户是否有效
|
|
||||||
*/
|
|
||||||
private void checkUserIsEffective(LoginUser loginUser) {
|
|
||||||
//情况1:根据用户信息查询,该用户不存在
|
|
||||||
if (Objects.isNull(loginUser)) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户不存在,请注册");
|
|
||||||
}
|
|
||||||
//情况2:根据用户信息查询,该用户已注销
|
|
||||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
|
||||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已注销");
|
|
||||||
}
|
|
||||||
//情况3:根据用户信息查询,该用户已冻结
|
|
||||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已冻结");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package org.jeecg.config.security.app;
|
|
||||||
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* APP模式认证专用token类型,方法spring authorization server进行认证流转,配合convert使用
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
public class AppGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
|
||||||
|
|
||||||
public AppGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
|
||||||
super(new AuthorizationGrantType(LoginType.APP), clientPrincipal, additionalParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
package org.jeecg.config.security.password;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密码模式认证转换器
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
public class PasswordGrantAuthenticationConvert implements AuthenticationConverter {
|
|
||||||
@Override
|
|
||||||
public Authentication convert(HttpServletRequest request) {
|
|
||||||
|
|
||||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
|
||||||
if (!LoginType.PASSWORD.equals(grantType)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
|
|
||||||
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
|
||||||
MultiValueMap<String, String> parameters = getParameters(request);
|
|
||||||
|
|
||||||
// username (REQUIRED)
|
|
||||||
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
|
|
||||||
if (!StringUtils.hasText(username) ||
|
|
||||||
parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
|
|
||||||
throw new OAuth2AuthenticationException("无效请求,用户名不能为空!");
|
|
||||||
}
|
|
||||||
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
|
|
||||||
if (!StringUtils.hasText(password) ||
|
|
||||||
parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
|
|
||||||
throw new OAuth2AuthenticationException("无效请求,密码不能为空!");
|
|
||||||
}
|
|
||||||
|
|
||||||
//收集要传入PasswordGrantAuthenticationToken构造方法的参数,
|
|
||||||
//该参数接下来在PasswordGrantAuthenticationProvider中使用
|
|
||||||
Map<String, Object> additionalParameters = new HashMap<>();
|
|
||||||
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
|
||||||
parameters.forEach((key, value) -> {
|
|
||||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
|
||||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
|
||||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
|
||||||
additionalParameters.put(key, value.get(0));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//返回自定义的PasswordGrantAuthenticationToken对象
|
|
||||||
return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
|
||||||
*/
|
|
||||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
|
||||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
|
||||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
|
||||||
parameterMap.forEach((key, values) -> {
|
|
||||||
if (values.length > 0) {
|
|
||||||
for (String value : values) {
|
|
||||||
parameters.add(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return parameters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,317 +0,0 @@
|
|||||||
package org.jeecg.config.security.password;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jeecg.common.api.CommonAPI;
|
|
||||||
import org.jeecg.common.constant.CacheConstant;
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
|
||||||
import org.jeecg.common.exception.JeecgCaptchaException;
|
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
|
||||||
import org.jeecg.common.system.vo.SysDepartModel;
|
|
||||||
import org.jeecg.common.util.Md5Util;
|
|
||||||
import org.jeecg.common.util.PasswordUtil;
|
|
||||||
import org.jeecg.common.util.RedisUtil;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
|
||||||
import org.jeecg.modules.base.service.BaseCommonService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.security.oauth2.core.*;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密码模式认证处理器,负责处理该认证模式下的核心逻辑
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class PasswordGrantAuthenticationProvider implements AuthenticationProvider {
|
|
||||||
|
|
||||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
|
||||||
|
|
||||||
private final OAuth2AuthorizationService authorizationService;
|
|
||||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
|
||||||
@Autowired
|
|
||||||
private CommonAPI commonAPI;
|
|
||||||
@Autowired
|
|
||||||
private RedisUtil redisUtil;
|
|
||||||
@Autowired
|
|
||||||
private JeecgBaseConfig jeecgBaseConfig;
|
|
||||||
@Autowired
|
|
||||||
private BaseCommonService baseCommonService;
|
|
||||||
|
|
||||||
public PasswordGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
|
||||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
|
||||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
|
||||||
this.authorizationService = authorizationService;
|
|
||||||
this.tokenGenerator = tokenGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
|
||||||
PasswordGrantAuthenticationToken passwordGrantAuthenticationToken = (PasswordGrantAuthenticationToken) authentication;
|
|
||||||
Map<String, Object> additionalParameter = passwordGrantAuthenticationToken.getAdditionalParameters();
|
|
||||||
|
|
||||||
// 授权类型
|
|
||||||
AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();
|
|
||||||
// 用户名
|
|
||||||
String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
|
|
||||||
// 密码
|
|
||||||
String password = (String) additionalParameter.get(OAuth2ParameterNames.PASSWORD);
|
|
||||||
//请求参数权限范围
|
|
||||||
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
|
||||||
//请求参数权限范围专场集合
|
|
||||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
|
||||||
// 验证码
|
|
||||||
String captcha = (String) additionalParameter.get("captcha");
|
|
||||||
String checkKey = (String) additionalParameter.get("checkKey");
|
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
|
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
|
||||||
|
|
||||||
// 检查登录失败次数
|
|
||||||
if(isLoginFailOvertimes(username)){
|
|
||||||
throw new JeecgBootException("该用户登录失败次数过多,请于10分钟后再次登录!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(captcha==null){
|
|
||||||
throw new JeecgBootException("验证码无效");
|
|
||||||
}
|
|
||||||
String lowerCaseCaptcha = captcha.toLowerCase();
|
|
||||||
// 加入密钥作为混淆,避免简单的拼接,被外部利用,用户自定义该密钥即可
|
|
||||||
String origin = lowerCaseCaptcha+checkKey+jeecgBaseConfig.getSignatureSecret();
|
|
||||||
String realKey = Md5Util.md5Encode(origin, "utf-8");
|
|
||||||
Object checkCode = redisUtil.get(realKey);
|
|
||||||
//当进入登录页时,有一定几率出现验证码错误 #1714
|
|
||||||
if(checkCode==null || !checkCode.toString().equals(lowerCaseCaptcha)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "验证码错误");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "非法登录");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过用户名获取用户信息
|
|
||||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
|
||||||
//update-begin---author:eightmonth ---date:2024-04-30 for:【6168】master分支切sas分支登录发生错误-----------
|
|
||||||
if (Objects.isNull(loginUser) || !StringUtils.hasText(loginUser.getSalt())) {
|
|
||||||
redisUtil.del(CacheConstant.SYS_USERS_CACHE+"::"+username);
|
|
||||||
loginUser = commonAPI.getUserByName(username);
|
|
||||||
}
|
|
||||||
//update-end---author:eightmonth ---date::2024-04-30 for:【6168】master分支切sas分支登录发生错误--------------
|
|
||||||
// 检查用户可行性
|
|
||||||
checkUserIsEffective(loginUser);
|
|
||||||
|
|
||||||
// 不使用spring security passwordEncoder针对密码进行匹配,使用自有加密匹配,针对 spring security使用noop传输
|
|
||||||
password = PasswordUtil.encrypt(username, password, loginUser.getSalt());
|
|
||||||
if (!password.equals(loginUser.getPassword())) {
|
|
||||||
addLoginFailOvertimes(username);
|
|
||||||
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "用户名或密码不正确");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
|
||||||
|
|
||||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
|
||||||
.registeredClient(registeredClient)
|
|
||||||
.principal(usernamePasswordAuthenticationToken)
|
|
||||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
|
||||||
.authorizationGrantType(authorizationGrantType)
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.authorizationGrant(passwordGrantAuthenticationToken);
|
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
|
||||||
.principalName(clientPrincipal.getName())
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.attribute(Principal.class.getName(), username)
|
|
||||||
.authorizationGrantType(authorizationGrantType);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Access token -----
|
|
||||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
|
||||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (generatedAccessToken == null) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成访问token,请联系管理系。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
|
||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
|
||||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
|
||||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
|
||||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
|
||||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
authorizationBuilder.accessToken(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Refresh token -----
|
|
||||||
OAuth2RefreshToken refreshToken = null;
|
|
||||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
|
||||||
// 不向公共客户端颁发刷新令牌
|
|
||||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
|
||||||
|
|
||||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
|
||||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成访问token,请联系管理系。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
|
||||||
authorizationBuilder.refreshToken(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
|
||||||
|
|
||||||
// 保存认证信息至redis
|
|
||||||
authorizationService.save(authorization);
|
|
||||||
|
|
||||||
// 登录成功,删除redis中的验证码
|
|
||||||
redisUtil.del(realKey);
|
|
||||||
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
|
|
||||||
baseCommonService.addLog("用户名: " + username + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
|
||||||
|
|
||||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
|
||||||
addition.put("token", accessToken.getTokenValue());
|
|
||||||
|
|
||||||
// 设置租户
|
|
||||||
JSONObject jsonObject = commonAPI.setLoginTenant(username);
|
|
||||||
addition.putAll(jsonObject.getInnerMap());
|
|
||||||
|
|
||||||
// 设置登录用户信息
|
|
||||||
addition.put("userInfo", loginUser);
|
|
||||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
|
||||||
|
|
||||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
|
||||||
addition.put("departs", departs);
|
|
||||||
if (departs == null || departs.size() == 0) {
|
|
||||||
addition.put("multi_depart", 0);
|
|
||||||
} else if (departs.size() == 1) {
|
|
||||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
|
||||||
addition.put("multi_depart", 1);
|
|
||||||
} else {
|
|
||||||
//查询当前是否有登录部门
|
|
||||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
|
||||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
|
||||||
}
|
|
||||||
addition.put("multi_depart", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兼容原有shiro登录结果处理
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("result", addition);
|
|
||||||
map.put("code", 200);
|
|
||||||
map.put("success", true);
|
|
||||||
map.put("timestamp", System.currentTimeMillis());
|
|
||||||
|
|
||||||
// 返回access_token、refresh_token以及其它信息给到前端
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> authentication) {
|
|
||||||
return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
|
||||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
|
||||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
|
||||||
}
|
|
||||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
|
||||||
return clientPrincipal;
|
|
||||||
}
|
|
||||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录失败超出次数5 返回true
|
|
||||||
* @param username
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isLoginFailOvertimes(String username){
|
|
||||||
String key = CommonConstant.LOGIN_FAIL + username;
|
|
||||||
Object failTime = redisUtil.get(key);
|
|
||||||
if(failTime!=null){
|
|
||||||
Integer val = Integer.parseInt(failTime.toString());
|
|
||||||
if(val>5){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录登录失败次数
|
|
||||||
* @param username
|
|
||||||
*/
|
|
||||||
private void addLoginFailOvertimes(String username){
|
|
||||||
String key = CommonConstant.LOGIN_FAIL + username;
|
|
||||||
Object failTime = redisUtil.get(key);
|
|
||||||
Integer val = 0;
|
|
||||||
if(failTime!=null){
|
|
||||||
val = Integer.parseInt(failTime.toString());
|
|
||||||
}
|
|
||||||
// 10分钟
|
|
||||||
redisUtil.set(key, ++val, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验用户是否有效
|
|
||||||
*/
|
|
||||||
private void checkUserIsEffective(LoginUser loginUser) {
|
|
||||||
//情况1:根据用户信息查询,该用户不存在
|
|
||||||
if (Objects.isNull(loginUser)) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户不存在,请注册");
|
|
||||||
}
|
|
||||||
//情况2:根据用户信息查询,该用户已注销
|
|
||||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
|
||||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已注销");
|
|
||||||
}
|
|
||||||
//情况3:根据用户信息查询,该用户已冻结
|
|
||||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已冻结");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package org.jeecg.config.security.password;
|
|
||||||
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密码模式认证专用token类型,方法spring authorization server进行认证流转,配合convert使用
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
public class PasswordGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
|
||||||
|
|
||||||
public PasswordGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
|
||||||
super(new AuthorizationGrantType(LoginType.PASSWORD), clientPrincipal, additionalParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
package org.jeecg.config.security.phone;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手机号模式认证转换器
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class PhoneGrantAuthenticationConvert implements AuthenticationConverter {
|
|
||||||
@Override
|
|
||||||
public Authentication convert(HttpServletRequest request) {
|
|
||||||
|
|
||||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
|
||||||
if (!LoginType.PHONE.equals(grantType)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
|
|
||||||
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
|
||||||
MultiValueMap<String, String> parameters = getParameters(request);
|
|
||||||
|
|
||||||
// 验证码
|
|
||||||
String captcha = parameters.getFirst("captcha");
|
|
||||||
if (!StringUtils.hasText(captcha)) {
|
|
||||||
throw new OAuth2AuthenticationException("无效请求,验证码不能为空!");
|
|
||||||
}
|
|
||||||
|
|
||||||
//收集要传入PhoneGrantAuthenticationToken构造方法的参数,
|
|
||||||
//该参数接下来在PhoneGrantAuthenticationProvider中使用
|
|
||||||
Map<String, Object> additionalParameters = new HashMap<>();
|
|
||||||
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
|
||||||
parameters.forEach((key, value) -> {
|
|
||||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
|
||||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
|
||||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
|
||||||
additionalParameters.put(key, value.get(0));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//返回自定义的PhoneGrantAuthenticationToken对象
|
|
||||||
return new PhoneGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
|
||||||
*/
|
|
||||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
|
||||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
|
||||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
|
||||||
parameterMap.forEach((key, values) -> {
|
|
||||||
if (values.length > 0) {
|
|
||||||
for (String value : values) {
|
|
||||||
parameters.add(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,290 +0,0 @@
|
|||||||
package org.jeecg.config.security.phone;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jeecg.common.api.CommonAPI;
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
|
||||||
import org.jeecg.common.exception.JeecgCaptchaException;
|
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
|
||||||
import org.jeecg.common.system.vo.SysDepartModel;
|
|
||||||
import org.jeecg.common.util.Md5Util;
|
|
||||||
import org.jeecg.common.util.PasswordUtil;
|
|
||||||
import org.jeecg.common.util.RedisUtil;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
|
||||||
import org.jeecg.modules.base.service.BaseCommonService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.*;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手机号模式认证处理器,负责处理该认证模式下的核心逻辑
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class PhoneGrantAuthenticationProvider implements AuthenticationProvider {
|
|
||||||
|
|
||||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
|
||||||
|
|
||||||
private final OAuth2AuthorizationService authorizationService;
|
|
||||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
|
||||||
@Autowired
|
|
||||||
private CommonAPI commonAPI;
|
|
||||||
@Autowired
|
|
||||||
private RedisUtil redisUtil;
|
|
||||||
@Autowired
|
|
||||||
private JeecgBaseConfig jeecgBaseConfig;
|
|
||||||
@Autowired
|
|
||||||
private BaseCommonService baseCommonService;
|
|
||||||
|
|
||||||
public PhoneGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
|
||||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
|
||||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
|
||||||
this.authorizationService = authorizationService;
|
|
||||||
this.tokenGenerator = tokenGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
|
||||||
PhoneGrantAuthenticationToken phoneGrantAuthenticationToken = (PhoneGrantAuthenticationToken) authentication;
|
|
||||||
Map<String, Object> additionalParameter = phoneGrantAuthenticationToken.getAdditionalParameters();
|
|
||||||
|
|
||||||
// 授权类型
|
|
||||||
AuthorizationGrantType authorizationGrantType = phoneGrantAuthenticationToken.getGrantType();
|
|
||||||
// 手机号
|
|
||||||
String phone = (String) additionalParameter.get("mobile");
|
|
||||||
|
|
||||||
if(isLoginFailOvertimes(phone)){
|
|
||||||
throw new JeecgBootException("该用户登录失败次数过多,请于10分钟后再次登录!");
|
|
||||||
}
|
|
||||||
|
|
||||||
//请求参数权限范围
|
|
||||||
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
|
||||||
//请求参数权限范围专场集合
|
|
||||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
|
||||||
// 验证码
|
|
||||||
String captcha = (String) additionalParameter.get("captcha");
|
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(phoneGrantAuthenticationToken);
|
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
|
||||||
|
|
||||||
// 通过手机号获取用户信息
|
|
||||||
LoginUser loginUser = commonAPI.getUserByPhone(phone);
|
|
||||||
// 检查用户可行性
|
|
||||||
checkUserIsEffective(loginUser);
|
|
||||||
|
|
||||||
|
|
||||||
String redisKey = CommonConstant.PHONE_REDIS_KEY_PRE+phone;
|
|
||||||
Object code = redisUtil.get(redisKey);
|
|
||||||
|
|
||||||
if (!captcha.equals(code)) {
|
|
||||||
//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
|
|
||||||
addLoginFailOvertimes(phone);
|
|
||||||
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "手机验证码错误");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "非法登录");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
|
||||||
|
|
||||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
|
||||||
.registeredClient(registeredClient)
|
|
||||||
.principal(usernamePasswordAuthenticationToken)
|
|
||||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
|
||||||
.authorizationGrantType(authorizationGrantType)
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.authorizationGrant(phoneGrantAuthenticationToken);
|
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
|
||||||
.principalName(clientPrincipal.getName())
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.attribute(Principal.class.getName(), loginUser.getUsername())
|
|
||||||
.authorizationGrantType(authorizationGrantType);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Access token -----
|
|
||||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
|
||||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (generatedAccessToken == null) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
|
||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
|
||||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
|
||||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
|
||||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
|
||||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
authorizationBuilder.accessToken(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Refresh token -----
|
|
||||||
OAuth2RefreshToken refreshToken = null;
|
|
||||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
|
||||||
// 不向公共客户端颁发刷新令牌
|
|
||||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
|
||||||
|
|
||||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
|
||||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
|
||||||
authorizationBuilder.refreshToken(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
|
||||||
|
|
||||||
// 保存认证信息至redis
|
|
||||||
authorizationService.save(authorization);
|
|
||||||
|
|
||||||
baseCommonService.addLog("用户名: " + loginUser.getUsername() + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
|
||||||
|
|
||||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
|
||||||
addition.put("token", accessToken.getTokenValue());
|
|
||||||
// 设置租户
|
|
||||||
JSONObject jsonObject = commonAPI.setLoginTenant(loginUser.getUsername());
|
|
||||||
addition.putAll(jsonObject.getInnerMap());
|
|
||||||
|
|
||||||
// 设置登录用户信息
|
|
||||||
addition.put("userInfo", loginUser);
|
|
||||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
|
||||||
|
|
||||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
|
||||||
addition.put("departs", departs);
|
|
||||||
if (departs == null || departs.size() == 0) {
|
|
||||||
addition.put("multi_depart", 0);
|
|
||||||
} else if (departs.size() == 1) {
|
|
||||||
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
|
||||||
addition.put("multi_depart", 1);
|
|
||||||
} else {
|
|
||||||
//查询当前是否有登录部门
|
|
||||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
|
||||||
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
|
||||||
}
|
|
||||||
addition.put("multi_depart", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兼容原有shiro登录结果处理
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("result", addition);
|
|
||||||
map.put("code", 200);
|
|
||||||
map.put("success", true);
|
|
||||||
map.put("timestamp", System.currentTimeMillis());
|
|
||||||
|
|
||||||
// 返回access_token、refresh_token以及其它信息给到前端
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> authentication) {
|
|
||||||
return PhoneGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
|
||||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
|
||||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
|
||||||
}
|
|
||||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
|
||||||
return clientPrincipal;
|
|
||||||
}
|
|
||||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录失败超出次数5 返回true
|
|
||||||
* @param username
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isLoginFailOvertimes(String username){
|
|
||||||
String key = CommonConstant.LOGIN_FAIL + username;
|
|
||||||
Object failTime = redisUtil.get(key);
|
|
||||||
if(failTime!=null){
|
|
||||||
Integer val = Integer.parseInt(failTime.toString());
|
|
||||||
if(val>5){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录登录失败次数
|
|
||||||
* @param username
|
|
||||||
*/
|
|
||||||
private void addLoginFailOvertimes(String username){
|
|
||||||
String key = CommonConstant.LOGIN_FAIL + username;
|
|
||||||
Object failTime = redisUtil.get(key);
|
|
||||||
Integer val = 0;
|
|
||||||
if(failTime!=null){
|
|
||||||
val = Integer.parseInt(failTime.toString());
|
|
||||||
}
|
|
||||||
// 10分钟
|
|
||||||
redisUtil.set(key, ++val, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验用户是否有效
|
|
||||||
*/
|
|
||||||
private void checkUserIsEffective(LoginUser loginUser) {
|
|
||||||
//情况1:根据用户信息查询,该用户不存在
|
|
||||||
if (Objects.isNull(loginUser)) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户不存在,请注册");
|
|
||||||
}
|
|
||||||
//情况2:根据用户信息查询,该用户已注销
|
|
||||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
|
||||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已注销");
|
|
||||||
}
|
|
||||||
//情况3:根据用户信息查询,该用户已冻结
|
|
||||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已冻结");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package org.jeecg.config.security.phone;
|
|
||||||
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手机号模式认证专用token类型,方法spring authorization server进行认证流转,配合convert使用
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
public class PhoneGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
|
||||||
|
|
||||||
public PhoneGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
|
||||||
super(new AuthorizationGrantType(LoginType.PHONE), clientPrincipal, additionalParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,228 +0,0 @@
|
|||||||
package org.jeecg.config.security.self;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import org.jeecg.common.api.CommonAPI;
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
|
||||||
import org.jeecg.common.system.vo.SysDepartModel;
|
|
||||||
import org.jeecg.common.util.RedisUtil;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
|
||||||
import org.jeecg.modules.base.service.BaseCommonService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.*;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自用生成token处理器,不对外开放,外部请求无法通过该方式生成token
|
|
||||||
* @author eightmonth@qq.com
|
|
||||||
* @date 2024/3/19 11:40
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class SelfAuthenticationProvider implements AuthenticationProvider {
|
|
||||||
|
|
||||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
|
||||||
|
|
||||||
private final OAuth2AuthorizationService authorizationService;
|
|
||||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
|
||||||
@Autowired
|
|
||||||
private CommonAPI commonAPI;
|
|
||||||
@Autowired
|
|
||||||
private RedisUtil redisUtil;
|
|
||||||
@Autowired
|
|
||||||
private JeecgBaseConfig jeecgBaseConfig;
|
|
||||||
@Autowired
|
|
||||||
private BaseCommonService baseCommonService;
|
|
||||||
|
|
||||||
public SelfAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
|
||||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
|
||||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
|
||||||
this.authorizationService = authorizationService;
|
|
||||||
this.tokenGenerator = tokenGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
|
||||||
SelfAuthenticationToken passwordGrantAuthenticationToken = (SelfAuthenticationToken) authentication;
|
|
||||||
Map<String, Object> additionalParameter = passwordGrantAuthenticationToken.getAdditionalParameters();
|
|
||||||
|
|
||||||
// 授权类型
|
|
||||||
AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();
|
|
||||||
// 用户名
|
|
||||||
String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
|
|
||||||
//请求参数权限范围
|
|
||||||
String requestScopesStr = "*";
|
|
||||||
//请求参数权限范围专场集合
|
|
||||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
|
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
|
||||||
|
|
||||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "非法登录");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过用户名获取用户信息
|
|
||||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
|
||||||
// 检查用户可行性
|
|
||||||
checkUserIsEffective(loginUser);
|
|
||||||
|
|
||||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
|
||||||
|
|
||||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
|
||||||
.registeredClient(registeredClient)
|
|
||||||
.principal(usernamePasswordAuthenticationToken)
|
|
||||||
.authorizationGrantType(authorizationGrantType)
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.authorizationGrant(passwordGrantAuthenticationToken);
|
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
|
||||||
.principalName(clientPrincipal.getName())
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.attribute(Principal.class.getName(), username)
|
|
||||||
.authorizationGrantType(authorizationGrantType);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Access token -----
|
|
||||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
|
||||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (generatedAccessToken == null) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
|
|
||||||
}
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
|
||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
|
||||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
|
||||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
|
||||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
|
||||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
authorizationBuilder.accessToken(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Refresh token -----
|
|
||||||
OAuth2RefreshToken refreshToken = null;
|
|
||||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
|
||||||
// 不向公共客户端颁发刷新令牌
|
|
||||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
|
||||||
|
|
||||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
|
||||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
|
||||||
authorizationBuilder.refreshToken(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
|
||||||
|
|
||||||
// 保存认证信息至redis
|
|
||||||
authorizationService.save(authorization);
|
|
||||||
|
|
||||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
|
||||||
addition.put("token", accessToken.getTokenValue());
|
|
||||||
// 设置租户
|
|
||||||
JSONObject jsonObject = commonAPI.setLoginTenant(username);
|
|
||||||
addition.putAll(jsonObject.getInnerMap());
|
|
||||||
|
|
||||||
// 设置登录用户信息
|
|
||||||
addition.put("userInfo", loginUser);
|
|
||||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
|
||||||
|
|
||||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
|
||||||
addition.put("departs", departs);
|
|
||||||
if (departs == null || departs.size() == 0) {
|
|
||||||
addition.put("multi_depart", 0);
|
|
||||||
} else if (departs.size() == 1) {
|
|
||||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
|
||||||
addition.put("multi_depart", 1);
|
|
||||||
} else {
|
|
||||||
//查询当前是否有登录部门
|
|
||||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
|
||||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
|
||||||
}
|
|
||||||
addition.put("multi_depart", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兼容原有shiro登录结果处理
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("result", addition);
|
|
||||||
map.put("code", 200);
|
|
||||||
map.put("success", true);
|
|
||||||
map.put("timestamp", System.currentTimeMillis());
|
|
||||||
|
|
||||||
// 返回access_token、refresh_token以及其它信息给到前端
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> authentication) {
|
|
||||||
return SelfAuthenticationToken.class.isAssignableFrom(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
|
||||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
|
||||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
|
||||||
}
|
|
||||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
|
||||||
return clientPrincipal;
|
|
||||||
}
|
|
||||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验用户是否有效
|
|
||||||
*/
|
|
||||||
private void checkUserIsEffective(LoginUser loginUser) {
|
|
||||||
//情况1:根据用户信息查询,该用户不存在
|
|
||||||
if (Objects.isNull(loginUser)) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户不存在,请注册");
|
|
||||||
}
|
|
||||||
//情况2:根据用户信息查询,该用户已注销
|
|
||||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
|
||||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已注销");
|
|
||||||
}
|
|
||||||
//情况3:根据用户信息查询,该用户已冻结
|
|
||||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已冻结");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
package org.jeecg.config.security.self;
|
|
||||||
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自用生成token,不支持对外请求,仅为程序内部生成token
|
|
||||||
* @author eightmonth
|
|
||||||
* @date 2024/3/19 11:37
|
|
||||||
*/
|
|
||||||
public class SelfAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
|
||||||
public SelfAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
|
||||||
super(new AuthorizationGrantType(LoginType.SELF), clientPrincipal, additionalParameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
package org.jeecg.config.security.social;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 社交模式认证转换器,配合github、企业微信、钉钉、微信登录使用
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class SocialGrantAuthenticationConvert implements AuthenticationConverter {
|
|
||||||
@Override
|
|
||||||
public Authentication convert(HttpServletRequest request) {
|
|
||||||
|
|
||||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
|
||||||
if (!LoginType.SOCIAL.equals(grantType)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
|
|
||||||
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
|
||||||
MultiValueMap<String, String> parameters = getParameters(request);
|
|
||||||
|
|
||||||
String token = parameters.getFirst("token");
|
|
||||||
if (!StringUtils.hasText(token)) {
|
|
||||||
throw new OAuth2AuthenticationException("无效请求,三方token不能为空!");
|
|
||||||
}
|
|
||||||
|
|
||||||
String source = parameters.getFirst("thirdType");
|
|
||||||
if (!StringUtils.hasText(source)) {
|
|
||||||
throw new OAuth2AuthenticationException("无效请求,三方来源不能为空!");
|
|
||||||
}
|
|
||||||
|
|
||||||
//收集要传入PhoneGrantAuthenticationToken构造方法的参数,
|
|
||||||
//该参数接下来在PhoneGrantAuthenticationProvider中使用
|
|
||||||
Map<String, Object> additionalParameters = new HashMap<>();
|
|
||||||
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
|
||||||
parameters.forEach((key, value) -> {
|
|
||||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
|
||||||
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
|
||||||
!key.equals(OAuth2ParameterNames.CODE)) {
|
|
||||||
additionalParameters.put(key, value.get(0));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//返回自定义的PhoneGrantAuthenticationToken对象
|
|
||||||
return new SocialGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
|
||||||
*/
|
|
||||||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
|
||||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
|
||||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
|
||||||
parameterMap.forEach((key, values) -> {
|
|
||||||
if (values.length > 0) {
|
|
||||||
for (String value : values) {
|
|
||||||
parameters.add(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,276 +0,0 @@
|
|||||||
package org.jeecg.config.security.social;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.auth0.jwt.JWT;
|
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jeecg.common.api.CommonAPI;
|
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
|
||||||
import org.jeecg.common.system.vo.SysDepartModel;
|
|
||||||
import org.jeecg.common.util.RedisUtil;
|
|
||||||
import org.jeecg.common.util.oConvertUtils;
|
|
||||||
import org.jeecg.config.JeecgBaseConfig;
|
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
|
||||||
import org.jeecg.modules.base.service.BaseCommonService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.*;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 社交模式认证处理器,负责处理该认证模式下的核心逻辑,配合github、企业微信、钉钉、微信登录使用
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class SocialGrantAuthenticationProvider implements AuthenticationProvider {
|
|
||||||
|
|
||||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
|
||||||
|
|
||||||
private final OAuth2AuthorizationService authorizationService;
|
|
||||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
|
||||||
@Autowired
|
|
||||||
private CommonAPI commonAPI;
|
|
||||||
@Autowired
|
|
||||||
private RedisUtil redisUtil;
|
|
||||||
@Autowired
|
|
||||||
private JeecgBaseConfig jeecgBaseConfig;
|
|
||||||
@Autowired
|
|
||||||
private BaseCommonService baseCommonService;
|
|
||||||
|
|
||||||
public SocialGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
|
||||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
|
||||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
|
||||||
this.authorizationService = authorizationService;
|
|
||||||
this.tokenGenerator = tokenGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
|
||||||
SocialGrantAuthenticationToken socialGrantAuthenticationToken = (SocialGrantAuthenticationToken) authentication;
|
|
||||||
Map<String, Object> additionalParameter = socialGrantAuthenticationToken.getAdditionalParameters();
|
|
||||||
|
|
||||||
// 授权类型
|
|
||||||
AuthorizationGrantType authorizationGrantType = socialGrantAuthenticationToken.getGrantType();
|
|
||||||
// 三方token
|
|
||||||
String token = (String) additionalParameter.get("token");
|
|
||||||
// 三方来源
|
|
||||||
String source = (String) additionalParameter.get("thirdType");
|
|
||||||
|
|
||||||
//请求参数权限范围
|
|
||||||
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
|
||||||
//请求参数权限范围专场集合
|
|
||||||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
DecodedJWT jwt = JWT.decode(token);
|
|
||||||
String username = jwt.getClaim("username").asString();
|
|
||||||
|
|
||||||
// 通过手机号获取用户信息
|
|
||||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
|
||||||
// 检查用户可行性
|
|
||||||
checkUserIsEffective(loginUser);
|
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(socialGrantAuthenticationToken);
|
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
|
||||||
|
|
||||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "非法登录");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
|
||||||
|
|
||||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
|
||||||
.registeredClient(registeredClient)
|
|
||||||
.principal(usernamePasswordAuthenticationToken)
|
|
||||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
|
||||||
.authorizationGrantType(authorizationGrantType)
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.authorizationGrant(socialGrantAuthenticationToken);
|
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
|
||||||
.principalName(clientPrincipal.getName())
|
|
||||||
.authorizedScopes(requestScopeSet)
|
|
||||||
.attribute(Principal.class.getName(), loginUser.getUsername())
|
|
||||||
.authorizationGrantType(authorizationGrantType);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- Access token -----
|
|
||||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
|
||||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (generatedAccessToken == null) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成访问token,请联系管理系。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
|
|
||||||
}
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
|
||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
|
||||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
|
||||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
|
||||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
|
||||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
authorizationBuilder.accessToken(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- Refresh token -----
|
|
||||||
OAuth2RefreshToken refreshToken = null;
|
|
||||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
|
||||||
// 不向公共客户端颁发刷新令牌
|
|
||||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
|
||||||
|
|
||||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
|
||||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
|
||||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
|
||||||
authorizationBuilder.refreshToken(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
|
||||||
|
|
||||||
// 保存认证信息至redis
|
|
||||||
authorizationService.save(authorization);
|
|
||||||
|
|
||||||
baseCommonService.addLog("用户名: " + loginUser.getUsername() + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
|
||||||
|
|
||||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
|
||||||
addition.put("token", accessToken.getTokenValue());
|
|
||||||
// 设置租户
|
|
||||||
JSONObject jsonObject = commonAPI.setLoginTenant(loginUser.getUsername());
|
|
||||||
addition.putAll(jsonObject.getInnerMap());
|
|
||||||
|
|
||||||
// 设置登录用户信息
|
|
||||||
addition.put("userInfo", loginUser);
|
|
||||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
|
||||||
|
|
||||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
|
||||||
addition.put("departs", departs);
|
|
||||||
if (departs == null || departs.size() == 0) {
|
|
||||||
addition.put("multi_depart", 0);
|
|
||||||
} else if (departs.size() == 1) {
|
|
||||||
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
|
||||||
addition.put("multi_depart", 1);
|
|
||||||
} else {
|
|
||||||
//查询当前是否有登录部门
|
|
||||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
|
||||||
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
|
||||||
}
|
|
||||||
addition.put("multi_depart", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兼容原有shiro登录结果处理
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("result", addition);
|
|
||||||
map.put("code", 200);
|
|
||||||
map.put("success", true);
|
|
||||||
map.put("timestamp", System.currentTimeMillis());
|
|
||||||
|
|
||||||
|
|
||||||
// 返回access_token、refresh_token以及其它信息给到前端
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> authentication) {
|
|
||||||
return SocialGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
|
||||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
|
||||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
|
||||||
}
|
|
||||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
|
||||||
return clientPrincipal;
|
|
||||||
}
|
|
||||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录失败超出次数5 返回true
|
|
||||||
* @param username
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isLoginFailOvertimes(String username){
|
|
||||||
String key = CommonConstant.LOGIN_FAIL + username;
|
|
||||||
Object failTime = redisUtil.get(key);
|
|
||||||
if(failTime!=null){
|
|
||||||
Integer val = Integer.parseInt(failTime.toString());
|
|
||||||
if(val>5){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录登录失败次数
|
|
||||||
* @param username
|
|
||||||
*/
|
|
||||||
private void addLoginFailOvertimes(String username){
|
|
||||||
String key = CommonConstant.LOGIN_FAIL + username;
|
|
||||||
Object failTime = redisUtil.get(key);
|
|
||||||
Integer val = 0;
|
|
||||||
if(failTime!=null){
|
|
||||||
val = Integer.parseInt(failTime.toString());
|
|
||||||
}
|
|
||||||
// 10分钟
|
|
||||||
redisUtil.set(key, ++val, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验用户是否有效
|
|
||||||
*/
|
|
||||||
private void checkUserIsEffective(LoginUser loginUser) {
|
|
||||||
//情况1:根据用户信息查询,该用户不存在
|
|
||||||
if (Objects.isNull(loginUser)) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户不存在,请注册");
|
|
||||||
}
|
|
||||||
//情况2:根据用户信息查询,该用户已注销
|
|
||||||
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
|
||||||
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已注销");
|
|
||||||
}
|
|
||||||
//情况3:根据用户信息查询,该用户已冻结
|
|
||||||
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
|
||||||
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
|
||||||
throw new JeecgBootException("该用户已冻结");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package org.jeecg.config.security.social;
|
|
||||||
|
|
||||||
import org.jeecg.config.security.LoginType;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 社交模式认证专用token类型,方法spring authorization server进行认证流转,配合convert使用,配合github、企业微信、钉钉、微信登录使用
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/1
|
|
||||||
*/
|
|
||||||
public class SocialGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
|
||||||
|
|
||||||
public SocialGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
|
||||||
super(new AuthorizationGrantType(LoginType.SOCIAL), clientPrincipal, additionalParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
package org.jeecg.config.security.utils;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
|
||||||
import org.jeecg.common.util.SpringContextUtils;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 认证信息工具类
|
|
||||||
* @author EightMonth
|
|
||||||
* @date 2024/1/10 17:03
|
|
||||||
*/
|
|
||||||
public class SecureUtil {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过当前认证信息获取用户信息
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static LoginUser currentUser() {
|
|
||||||
String name = SecurityContextHolder.getContext().getAuthentication().getName();
|
|
||||||
return JSONObject.parseObject(name, LoginUser.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package org.jeecg.config.shiro;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author Scott
|
||||||
|
* @create 2018-07-12 15:19
|
||||||
|
* @desc
|
||||||
|
**/
|
||||||
|
public class JwtToken implements AuthenticationToken {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
public JwtToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,323 @@
|
|||||||
|
package org.jeecg.config.shiro;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||||
|
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
|
||||||
|
import org.apache.shiro.mgt.DefaultSubjectDAO;
|
||||||
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
|
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||||
|
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||||
|
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||||
|
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||||
|
import org.crazycake.shiro.*;
|
||||||
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
|
import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
|
||||||
|
import org.jeecg.config.shiro.filters.JwtFilter;
|
||||||
|
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||||
|
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||||
|
import redis.clients.jedis.HostAndPort;
|
||||||
|
import redis.clients.jedis.JedisCluster;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: Scott
|
||||||
|
* @date: 2018/2/7
|
||||||
|
* @description: shiro 配置类
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
public class ShiroConfig {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LettuceConnectionFactory lettuceConnectionFactory;
|
||||||
|
@Autowired
|
||||||
|
private Environment env;
|
||||||
|
@Resource
|
||||||
|
private JeecgBaseConfig jeecgBaseConfig;
|
||||||
|
@Autowired(required = false)
|
||||||
|
private RedisProperties redisProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter Chain定义说明
|
||||||
|
*
|
||||||
|
* 1、一个URL可以配置多个Filter,使用逗号分隔
|
||||||
|
* 2、当设置多个过滤器时,全部验证通过,才视为通过
|
||||||
|
* 3、部分过滤器可指定参数,如perms,roles
|
||||||
|
*/
|
||||||
|
@Bean("shiroFilterFactoryBean")
|
||||||
|
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
|
||||||
|
CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();
|
||||||
|
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
||||||
|
// 拦截器
|
||||||
|
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
|
||||||
|
|
||||||
|
//支持yml方式,配置拦截排除
|
||||||
|
if(jeecgBaseConfig!=null && jeecgBaseConfig.getShiro()!=null){
|
||||||
|
String shiroExcludeUrls = jeecgBaseConfig.getShiro().getExcludeUrls();
|
||||||
|
if(oConvertUtils.isNotEmpty(shiroExcludeUrls)){
|
||||||
|
String[] permissionUrl = shiroExcludeUrls.split(",");
|
||||||
|
for(String url : permissionUrl){
|
||||||
|
filterChainDefinitionMap.put(url,"anon");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置不会被拦截的链接 顺序判断
|
||||||
|
filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录
|
||||||
|
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
|
||||||
|
filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
|
||||||
|
filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
|
||||||
|
filterChainDefinitionMap.put("/sys/mLogin", "anon"); //登录接口排除
|
||||||
|
filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
|
||||||
|
filterChainDefinitionMap.put("/sys/thirdLogin/**", "anon"); //第三方登录
|
||||||
|
filterChainDefinitionMap.put("/sys/getEncryptedString", "anon"); //获取加密串
|
||||||
|
filterChainDefinitionMap.put("/sys/sms", "anon");//短信验证码
|
||||||
|
filterChainDefinitionMap.put("/sys/phoneLogin", "anon");//手机登录
|
||||||
|
filterChainDefinitionMap.put("/sys/user/checkOnlyUser", "anon");//校验用户是否存在
|
||||||
|
filterChainDefinitionMap.put("/sys/user/register", "anon");//用户注册
|
||||||
|
filterChainDefinitionMap.put("/sys/user/phoneVerification", "anon");//用户忘记密码验证手机号
|
||||||
|
filterChainDefinitionMap.put("/sys/user/passwordChange", "anon");//用户更改密码
|
||||||
|
filterChainDefinitionMap.put("/auth/2step-code", "anon");//登录验证码
|
||||||
|
filterChainDefinitionMap.put("/sys/common/static/**", "anon");//图片预览 &下载文件不限制token
|
||||||
|
filterChainDefinitionMap.put("/sys/common/pdf/**", "anon");//pdf预览
|
||||||
|
|
||||||
|
//filterChainDefinitionMap.put("/sys/common/view/**", "anon");//图片预览不限制token
|
||||||
|
//filterChainDefinitionMap.put("/sys/common/download/**", "anon");//文件下载不限制token
|
||||||
|
filterChainDefinitionMap.put("/generic/**", "anon");//pdf预览需要文件
|
||||||
|
|
||||||
|
filterChainDefinitionMap.put("/sys/getLoginQrcode/**", "anon"); //登录二维码
|
||||||
|
filterChainDefinitionMap.put("/sys/getQrcodeToken/**", "anon"); //监听扫码
|
||||||
|
filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除
|
||||||
|
|
||||||
|
|
||||||
|
//update-begin--Author:scott Date:20221116 for:排除静态资源后缀
|
||||||
|
filterChainDefinitionMap.put("/", "anon");
|
||||||
|
filterChainDefinitionMap.put("/doc.html", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.js", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.css", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.html", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.svg", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.pdf", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.jpg", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.png", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.gif", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.ico", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.ttf", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.woff", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.woff2", "anon");
|
||||||
|
//update-end--Author:scott Date:20221116 for:排除静态资源后缀
|
||||||
|
|
||||||
|
filterChainDefinitionMap.put("/druid/**", "anon");
|
||||||
|
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
|
||||||
|
filterChainDefinitionMap.put("/swagger**/**", "anon");
|
||||||
|
filterChainDefinitionMap.put("/webjars/**", "anon");
|
||||||
|
filterChainDefinitionMap.put("/v2/**", "anon");
|
||||||
|
|
||||||
|
// update-begin--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
||||||
|
filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
|
||||||
|
// update-end--Author:sunjianlei Date:20210510 for:排除消息通告查看详情页面(用于第三方APP)
|
||||||
|
|
||||||
|
//积木报表排除
|
||||||
|
filterChainDefinitionMap.put("/jmreport/**", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.js.map", "anon");
|
||||||
|
filterChainDefinitionMap.put("/**/*.css.map", "anon");
|
||||||
|
|
||||||
|
//拖拽仪表盘设计器排除
|
||||||
|
filterChainDefinitionMap.put("/drag/view", "anon");
|
||||||
|
filterChainDefinitionMap.put("/drag/page/queryById", "anon");
|
||||||
|
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getAllChartData", "anon");
|
||||||
|
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalData", "anon");
|
||||||
|
filterChainDefinitionMap.put("/drag/mock/json/**", "anon");
|
||||||
|
//大屏模板例子
|
||||||
|
filterChainDefinitionMap.put("/test/bigScreen/**", "anon");
|
||||||
|
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
|
||||||
|
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
|
||||||
|
//filterChainDefinitionMap.put("/test/jeecgDemo/rabbitMqClientTest/**", "anon"); //MQ测试
|
||||||
|
//filterChainDefinitionMap.put("/test/jeecgDemo/html", "anon"); //模板页面
|
||||||
|
//filterChainDefinitionMap.put("/test/jeecgDemo/redis/**", "anon"); //redis测试
|
||||||
|
|
||||||
|
//websocket排除
|
||||||
|
filterChainDefinitionMap.put("/websocket/**", "anon");//系统通知和公告
|
||||||
|
filterChainDefinitionMap.put("/newsWebsocket/**", "anon");//CMS模块
|
||||||
|
filterChainDefinitionMap.put("/vxeSocket/**", "anon");//JVxeTable无痕刷新示例
|
||||||
|
|
||||||
|
//性能监控——安全隐患泄露TOEKN(durid连接池也有)
|
||||||
|
//filterChainDefinitionMap.put("/actuator/**", "anon");
|
||||||
|
//测试模块排除
|
||||||
|
filterChainDefinitionMap.put("/test/seata/**", "anon");
|
||||||
|
|
||||||
|
//错误路径排除
|
||||||
|
filterChainDefinitionMap.put("/error", "anon");
|
||||||
|
// 企业微信证书排除
|
||||||
|
filterChainDefinitionMap.put("/WW_verify*", "anon");
|
||||||
|
|
||||||
|
// 添加自己的过滤器并且取名为jwt
|
||||||
|
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||||
|
//如果cloudServer为空 则说明是单体 需要加载跨域配置【微服务跨域切换】
|
||||||
|
Object cloudServer = env.getProperty(CommonConstant.CLOUD_SERVER_KEY);
|
||||||
|
filterMap.put("jwt", new JwtFilter(cloudServer==null));
|
||||||
|
shiroFilterFactoryBean.setFilters(filterMap);
|
||||||
|
// <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
|
||||||
|
filterChainDefinitionMap.put("/**", "jwt");
|
||||||
|
|
||||||
|
// 未授权界面返回JSON
|
||||||
|
shiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");
|
||||||
|
shiroFilterFactoryBean.setLoginUrl("/sys/common/403");
|
||||||
|
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
|
||||||
|
return shiroFilterFactoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
//update-begin---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean shiroFilterRegistration() {
|
||||||
|
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||||
|
registration.setFilter(new DelegatingFilterProxy("shiroFilterFactoryBean"));
|
||||||
|
registration.setEnabled(true);
|
||||||
|
registration.addUrlPatterns("/*");
|
||||||
|
//支持异步
|
||||||
|
registration.setAsyncSupported(true);
|
||||||
|
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240126 for:【QQYUN-7932】AI助手------------
|
||||||
|
|
||||||
|
@Bean("securityManager")
|
||||||
|
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
|
||||||
|
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||||
|
securityManager.setRealm(myRealm);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 关闭shiro自带的session,详情见文档
|
||||||
|
* http://shiro.apache.org/session-management.html#SessionManagement-
|
||||||
|
* StatelessApplications%28Sessionless%29
|
||||||
|
*/
|
||||||
|
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
|
||||||
|
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
|
||||||
|
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
|
||||||
|
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
|
||||||
|
securityManager.setSubjectDAO(subjectDAO);
|
||||||
|
//自定义缓存实现,使用redis
|
||||||
|
securityManager.setCacheManager(redisCacheManager());
|
||||||
|
return securityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下面的代码是添加注解支持
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@DependsOn("lifecycleBeanPostProcessor")
|
||||||
|
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
|
||||||
|
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
|
||||||
|
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
|
||||||
|
/**
|
||||||
|
* 解决重复代理问题 github#994
|
||||||
|
* 添加前缀判断 不匹配 任何Advisor
|
||||||
|
*/
|
||||||
|
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
|
||||||
|
defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor");
|
||||||
|
return defaultAdvisorAutoProxyCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
|
||||||
|
return new LifecycleBeanPostProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
|
||||||
|
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
|
||||||
|
advisor.setSecurityManager(securityManager);
|
||||||
|
return advisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cacheManager 缓存 redis实现
|
||||||
|
* 使用的是shiro-redis开源插件
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public RedisCacheManager redisCacheManager() {
|
||||||
|
log.info("===============(1)创建缓存管理器RedisCacheManager");
|
||||||
|
RedisCacheManager redisCacheManager = new RedisCacheManager();
|
||||||
|
redisCacheManager.setRedisManager(redisManager());
|
||||||
|
//redis中针对不同用户缓存(此处的id需要对应user实体中的id字段,用于唯一标识)
|
||||||
|
redisCacheManager.setPrincipalIdFieldName("id");
|
||||||
|
//用户权限信息缓存时间
|
||||||
|
redisCacheManager.setExpire(200000);
|
||||||
|
return redisCacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置shiro redisManager
|
||||||
|
* 使用的是shiro-redis开源插件
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public IRedisManager redisManager() {
|
||||||
|
log.info("===============(2)创建RedisManager,连接Redis..");
|
||||||
|
IRedisManager manager;
|
||||||
|
// sentinel cluster redis(【issues/5569】shiro集成 redis 不支持 sentinel 方式部署的redis集群 #5569)
|
||||||
|
if (Objects.nonNull(redisProperties)
|
||||||
|
&& Objects.nonNull(redisProperties.getSentinel())
|
||||||
|
&& !CollectionUtils.isEmpty(redisProperties.getSentinel().getNodes())) {
|
||||||
|
RedisSentinelManager sentinelManager = new RedisSentinelManager();
|
||||||
|
sentinelManager.setMasterName(redisProperties.getSentinel().getMaster());
|
||||||
|
sentinelManager.setHost(String.join(",", redisProperties.getSentinel().getNodes()));
|
||||||
|
sentinelManager.setPassword(redisProperties.getSentinel().getPassword());
|
||||||
|
sentinelManager.setDatabase(redisProperties.getDatabase());
|
||||||
|
|
||||||
|
return sentinelManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// redis 单机支持,在集群为空,或者集群无机器时候使用 add by jzyadmin@163.com
|
||||||
|
if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
|
||||||
|
RedisManager redisManager = new RedisManager();
|
||||||
|
redisManager.setHost(lettuceConnectionFactory.getHostName() + ":" + lettuceConnectionFactory.getPort());
|
||||||
|
//(lettuceConnectionFactory.getPort());
|
||||||
|
redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
|
||||||
|
redisManager.setTimeout(0);
|
||||||
|
if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {
|
||||||
|
redisManager.setPassword(lettuceConnectionFactory.getPassword());
|
||||||
|
}
|
||||||
|
manager = redisManager;
|
||||||
|
}else{
|
||||||
|
// redis集群支持,优先使用集群配置
|
||||||
|
RedisClusterManager redisManager = new RedisClusterManager();
|
||||||
|
Set<HostAndPort> portSet = new HashSet<>();
|
||||||
|
lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().forEach(node -> portSet.add(new HostAndPort(node.getHost() , node.getPort())));
|
||||||
|
//update-begin--Author:scott Date:20210531 for:修改集群模式下未设置redis密码的bug issues/I3QNIC
|
||||||
|
if (oConvertUtils.isNotEmpty(lettuceConnectionFactory.getPassword())) {
|
||||||
|
JedisCluster jedisCluster = new JedisCluster(portSet, 2000, 2000, 5,
|
||||||
|
lettuceConnectionFactory.getPassword(), new GenericObjectPoolConfig());
|
||||||
|
redisManager.setPassword(lettuceConnectionFactory.getPassword());
|
||||||
|
redisManager.setJedisCluster(jedisCluster);
|
||||||
|
} else {
|
||||||
|
JedisCluster jedisCluster = new JedisCluster(portSet);
|
||||||
|
redisManager.setJedisCluster(jedisCluster);
|
||||||
|
}
|
||||||
|
//update-end--Author:scott Date:20210531 for:修改集群模式下未设置redis密码的bug issues/I3QNIC
|
||||||
|
manager = redisManager;
|
||||||
|
}
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,231 @@
|
|||||||
|
package org.jeecg.config.shiro;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.apache.shiro.authc.SimpleAuthenticationInfo;
|
||||||
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
|
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||||
|
import org.apache.shiro.realm.AuthorizingRealm;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.jeecg.common.api.CommonAPI;
|
||||||
|
import org.jeecg.common.config.TenantContext;
|
||||||
|
import org.jeecg.common.constant.CacheConstant;
|
||||||
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
import org.jeecg.common.util.RedisUtil;
|
||||||
|
import org.jeecg.common.util.SpringContextUtils;
|
||||||
|
import org.jeecg.common.util.TokenUtils;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 用户登录鉴权和获取用户授权
|
||||||
|
* @Author: Scott
|
||||||
|
* @Date: 2019-4-23 8:13
|
||||||
|
* @Version: 1.1
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class ShiroRealm extends AuthorizingRealm {
|
||||||
|
@Lazy
|
||||||
|
@Resource
|
||||||
|
private CommonAPI commonApi;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Resource
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必须重写此方法,不然Shiro会报错
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supports(AuthenticationToken token) {
|
||||||
|
return token instanceof JwtToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
|
||||||
|
* 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
|
||||||
|
*
|
||||||
|
* @param principals 身份信息
|
||||||
|
* @return AuthorizationInfo 权限信息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||||
|
log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
|
||||||
|
String username = null;
|
||||||
|
String userId = null;
|
||||||
|
if (principals != null) {
|
||||||
|
LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
|
||||||
|
username = sysUser.getUsername();
|
||||||
|
userId = sysUser.getId();
|
||||||
|
}
|
||||||
|
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||||
|
|
||||||
|
// 设置用户拥有的角色集合,比如“admin,test”
|
||||||
|
Set<String> roleSet = commonApi.queryUserRoles(username);
|
||||||
|
//System.out.println(roleSet.toString());
|
||||||
|
info.setRoles(roleSet);
|
||||||
|
|
||||||
|
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
|
||||||
|
Set<String> permissionSet = commonApi.queryUserAuths(userId);
|
||||||
|
info.addStringPermissions(permissionSet);
|
||||||
|
//System.out.println(permissionSet);
|
||||||
|
log.info("===============Shiro权限认证成功==============");
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息认证是在用户进行登录的时候进行验证(不存redis)
|
||||||
|
* 也就是说验证用户输入的账号和密码是否正确,错误抛出异常
|
||||||
|
*
|
||||||
|
* @param auth 用户登录的账号密码信息
|
||||||
|
* @return 返回封装了用户信息的 AuthenticationInfo 实例
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
|
||||||
|
log.debug("===============Shiro身份认证开始============doGetAuthenticationInfo==========");
|
||||||
|
String token = (String) auth.getCredentials();
|
||||||
|
if (token == null) {
|
||||||
|
HttpServletRequest req = SpringContextUtils.getHttpServletRequest();
|
||||||
|
log.info("————————身份认证失败——————————IP地址: "+ oConvertUtils.getIpAddrByRequest(req) +",URL:"+req.getRequestURI());
|
||||||
|
throw new AuthenticationException("token为空!");
|
||||||
|
}
|
||||||
|
// 校验token有效性
|
||||||
|
LoginUser loginUser = null;
|
||||||
|
try {
|
||||||
|
loginUser = this.checkUserTokenIsEffect(token);
|
||||||
|
} catch (AuthenticationException e) {
|
||||||
|
JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),401,e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new SimpleAuthenticationInfo(loginUser, token, getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验token的有效性
|
||||||
|
*
|
||||||
|
* @param token
|
||||||
|
*/
|
||||||
|
public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
|
||||||
|
// 解密获得username,用于和数据库进行对比
|
||||||
|
String username = JwtUtil.getUsername(token);
|
||||||
|
if (username == null) {
|
||||||
|
throw new AuthenticationException("token非法无效!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户信息
|
||||||
|
log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token);
|
||||||
|
LoginUser loginUser = TokenUtils.getLoginUser(username, commonApi, redisUtil);
|
||||||
|
//LoginUser loginUser = commonApi.getUserByName(username);
|
||||||
|
if (loginUser == null) {
|
||||||
|
throw new AuthenticationException("用户不存在!");
|
||||||
|
}
|
||||||
|
// 判断用户状态
|
||||||
|
if (loginUser.getStatus() != 1) {
|
||||||
|
throw new AuthenticationException("账号已被锁定,请联系管理员!");
|
||||||
|
}
|
||||||
|
// 校验token是否超时失效 & 或者账号密码是否错误
|
||||||
|
if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
|
||||||
|
throw new AuthenticationException(CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||||
|
}
|
||||||
|
//update-begin-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
|
||||||
|
String userTenantIds = loginUser.getRelTenantIds();
|
||||||
|
if(oConvertUtils.isNotEmpty(userTenantIds)){
|
||||||
|
String contextTenantId = TenantContext.getTenant();
|
||||||
|
log.debug("登录租户:" + contextTenantId);
|
||||||
|
log.debug("用户拥有那些租户:" + userTenantIds);
|
||||||
|
//登录用户无租户,前端header中租户ID值为 0
|
||||||
|
String str ="0";
|
||||||
|
if(oConvertUtils.isNotEmpty(contextTenantId) && !str.equals(contextTenantId)){
|
||||||
|
//update-begin-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
|
||||||
|
String[] arr = userTenantIds.split(",");
|
||||||
|
if(!oConvertUtils.isIn(contextTenantId, arr)){
|
||||||
|
boolean isAuthorization = false;
|
||||||
|
//========================================================================
|
||||||
|
// 查询用户信息(如果租户不匹配从数据库中重新查询一次用户信息)
|
||||||
|
String loginUserKey = CacheConstant.SYS_USERS_CACHE + "::" + username;
|
||||||
|
redisUtil.del(loginUserKey);
|
||||||
|
LoginUser loginUserFromDb = commonApi.getUserByName(username);
|
||||||
|
if (oConvertUtils.isNotEmpty(loginUserFromDb.getRelTenantIds())) {
|
||||||
|
String[] newArray = loginUserFromDb.getRelTenantIds().split(",");
|
||||||
|
if (oConvertUtils.isIn(contextTenantId, newArray)) {
|
||||||
|
isAuthorization = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//========================================================================
|
||||||
|
|
||||||
|
//*********************************************
|
||||||
|
if(!isAuthorization){
|
||||||
|
log.info("租户异常——登录租户:" + contextTenantId);
|
||||||
|
log.info("租户异常——用户拥有租户组:" + userTenantIds);
|
||||||
|
throw new AuthenticationException("登录租户授权变更,请重新登陆!");
|
||||||
|
}
|
||||||
|
//*********************************************
|
||||||
|
}
|
||||||
|
//update-end-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//update-end-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
|
||||||
|
return loginUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWTToken刷新生命周期 (实现: 用户在线操作不掉线功能)
|
||||||
|
* 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样),缓存有效期设置为Jwt有效时间的2倍
|
||||||
|
* 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
|
||||||
|
* 3、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
|
||||||
|
* 4、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
|
||||||
|
* 注意: 前端请求Header中设置Authorization保持不变,校验有效性以缓存中的token为准。
|
||||||
|
* 用户过期时间 = Jwt有效时间 * 2。
|
||||||
|
*
|
||||||
|
* @param userName
|
||||||
|
* @param passWord
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean jwtTokenRefresh(String token, String userName, String passWord) {
|
||||||
|
String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
|
||||||
|
if (oConvertUtils.isNotEmpty(cacheToken)) {
|
||||||
|
// 校验token有效性
|
||||||
|
if (!JwtUtil.verify(cacheToken, userName, passWord)) {
|
||||||
|
String newAuthorization = JwtUtil.sign(userName, passWord);
|
||||||
|
// 设置超时时间
|
||||||
|
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
|
||||||
|
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME *2 / 1000);
|
||||||
|
log.debug("——————————用户在线操作,更新token保证不掉线—————————jwtTokenRefresh——————— "+ token);
|
||||||
|
}
|
||||||
|
//update-begin--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
|
||||||
|
// else {
|
||||||
|
// // 设置超时时间
|
||||||
|
// redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
|
||||||
|
// redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
|
||||||
|
// }
|
||||||
|
//update-end--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//redis中不存在此TOEKN,说明token非法返回false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除当前用户的权限认证缓存
|
||||||
|
*
|
||||||
|
* @param principals 权限信息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearCache(PrincipalCollection principals) {
|
||||||
|
super.clearCache(principals);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
package org.jeecg.config.shiro.filters;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||||
|
import org.apache.shiro.web.filter.InvalidRequestFilter;
|
||||||
|
import org.apache.shiro.web.filter.mgt.DefaultFilter;
|
||||||
|
import org.apache.shiro.web.filter.mgt.FilterChainManager;
|
||||||
|
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
|
||||||
|
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
|
||||||
|
import org.apache.shiro.web.mgt.WebSecurityManager;
|
||||||
|
import org.apache.shiro.web.servlet.AbstractShiroFilter;
|
||||||
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
|
import org.springframework.beans.factory.BeanInitializationException;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义ShiroFilterFactoryBean解决资源中文路径问题
|
||||||
|
* @author: jeecg-boot
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean {
|
||||||
|
@Override
|
||||||
|
public Class getObjectType() {
|
||||||
|
return MySpringShiroFilter.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AbstractShiroFilter createInstance() throws Exception {
|
||||||
|
|
||||||
|
SecurityManager securityManager = getSecurityManager();
|
||||||
|
if (securityManager == null) {
|
||||||
|
String msg = "SecurityManager property must be set.";
|
||||||
|
throw new BeanInitializationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(securityManager instanceof WebSecurityManager)) {
|
||||||
|
String msg = "The security manager does not implement the WebSecurityManager interface.";
|
||||||
|
throw new BeanInitializationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterChainManager manager = createFilterChainManager();
|
||||||
|
//Expose the constructed FilterChainManager by first wrapping it in a
|
||||||
|
// FilterChainResolver implementation. The AbstractShiroFilter implementations
|
||||||
|
// do not know about FilterChainManagers - only resolvers:
|
||||||
|
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
|
||||||
|
chainResolver.setFilterChainManager(manager);
|
||||||
|
|
||||||
|
Map<String, Filter> filterMap = manager.getFilters();
|
||||||
|
Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
|
||||||
|
if (invalidRequestFilter instanceof InvalidRequestFilter) {
|
||||||
|
//此处是关键,设置false跳过URL携带中文400,servletPath中文校验bug
|
||||||
|
((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
|
||||||
|
}
|
||||||
|
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
|
||||||
|
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
|
||||||
|
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
|
||||||
|
//injection of the SecurityManager and FilterChainResolver:
|
||||||
|
return new MySpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MySpringShiroFilter extends AbstractShiroFilter {
|
||||||
|
protected MySpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
|
||||||
|
if (webSecurityManager == null) {
|
||||||
|
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
|
||||||
|
} else {
|
||||||
|
this.setSecurityManager(webSecurityManager);
|
||||||
|
if (resolver != null) {
|
||||||
|
this.setFilterChainResolver(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,124 @@
|
|||||||
|
package org.jeecg.config.shiro.filters;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
|
||||||
|
import org.jeecg.common.config.TenantContext;
|
||||||
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
import org.jeecg.config.shiro.JwtToken;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 鉴权登录拦截器
|
||||||
|
* @Author: Scott
|
||||||
|
* @Date: 2018/10/7
|
||||||
|
**/
|
||||||
|
@Slf4j
|
||||||
|
public class JwtFilter extends BasicHttpAuthenticationFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认开启跨域设置(使用单体)
|
||||||
|
* 微服务情况下,此属性设置为false
|
||||||
|
*/
|
||||||
|
private boolean allowOrigin = true;
|
||||||
|
|
||||||
|
public JwtFilter(){}
|
||||||
|
public JwtFilter(boolean allowOrigin){
|
||||||
|
this.allowOrigin = allowOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行登录认证
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @param mappedValue
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||||
|
try {
|
||||||
|
executeLogin(request, response);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
JwtUtil.responseError(response,401,CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||||
|
return false;
|
||||||
|
//throw new AuthenticationException("Token失效,请重新登录", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
|
||||||
|
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||||
|
String token = httpServletRequest.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||||
|
// update-begin--Author:lvdandan Date:20210105 for:JT-355 OA聊天添加token验证,获取token参数
|
||||||
|
if (oConvertUtils.isEmpty(token)) {
|
||||||
|
token = httpServletRequest.getParameter("token");
|
||||||
|
}
|
||||||
|
// update-end--Author:lvdandan Date:20210105 for:JT-355 OA聊天添加token验证,获取token参数
|
||||||
|
|
||||||
|
JwtToken jwtToken = new JwtToken(token);
|
||||||
|
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
|
||||||
|
getSubject(request, response).login(jwtToken);
|
||||||
|
// 如果没有抛出异常则代表登入成功,返回true
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对跨域提供支持
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
|
||||||
|
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||||
|
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||||
|
if(allowOrigin){
|
||||||
|
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, httpServletRequest.getHeader(HttpHeaders.ORIGIN));
|
||||||
|
// 允许客户端请求方法
|
||||||
|
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,OPTIONS,PUT,DELETE");
|
||||||
|
// 允许客户端提交的Header
|
||||||
|
String requestHeaders = httpServletRequest.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
|
||||||
|
if (StringUtils.isNotEmpty(requestHeaders)) {
|
||||||
|
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders);
|
||||||
|
}
|
||||||
|
// 允许客户端携带凭证信息(是否允许发送Cookie)
|
||||||
|
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||||
|
}
|
||||||
|
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
|
||||||
|
if (RequestMethod.OPTIONS.name().equalsIgnoreCase(httpServletRequest.getMethod())) {
|
||||||
|
httpServletResponse.setStatus(HttpStatus.OK.value());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//update-begin-author:taoyan date:20200708 for:多租户用到
|
||||||
|
String tenantId = httpServletRequest.getHeader(CommonConstant.TENANT_ID);
|
||||||
|
TenantContext.setTenant(tenantId);
|
||||||
|
//update-end-author:taoyan date:20200708 for:多租户用到
|
||||||
|
|
||||||
|
return super.preHandle(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JwtFilter中ThreadLocal需要及时清除 #3634
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @param exception
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
|
||||||
|
//log.info("------清空线程中多租户的ID={}------",TenantContext.getTenant());
|
||||||
|
TenantContext.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package org.jeecg.config.shiro.filters;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.web.filter.AccessControlFilter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author Scott
|
||||||
|
* @create 2019-02-01 15:56
|
||||||
|
* @desc 鉴权请求URL访问权限拦截器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ResourceCheckFilter extends AccessControlFilter {
|
||||||
|
|
||||||
|
private String errorUrl;
|
||||||
|
|
||||||
|
public String getErrorUrl() {
|
||||||
|
return errorUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorUrl(String errorUrl) {
|
||||||
|
this.errorUrl = errorUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表示是否允许访问 ,如果允许访问返回true,否则false;
|
||||||
|
*
|
||||||
|
* @param servletRequest
|
||||||
|
* @param servletResponse
|
||||||
|
* @param o 表示写在拦截器中括号里面的字符串 mappedValue 就是 [urls] 配置中拦截器参数部分
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
|
||||||
|
Subject subject = getSubject(servletRequest, servletResponse);
|
||||||
|
String url = getPathWithinApplication(servletRequest);
|
||||||
|
log.info("当前用户正在访问的 url => " + url);
|
||||||
|
return subject.isPermitted(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onAccessDenied:表示当访问拒绝时是否已经处理了; 如果返回 true 表示需要继续处理; 如果返回 false
|
||||||
|
* 表示该拦截器实例已经处理了,将直接返回即可。
|
||||||
|
*
|
||||||
|
* @param servletRequest
|
||||||
|
* @param servletResponse
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
|
||||||
|
log.info("当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied ");
|
||||||
|
|
||||||
|
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||||
|
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||||
|
response.sendRedirect(request.getContextPath() + this.errorUrl);
|
||||||
|
|
||||||
|
// 返回 false 表示已经处理,例如页面跳转啥的,表示不在走以下的拦截器了(如果还有配置的话)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 签名 拦截器配置
|
* 签名 拦截器配置
|
||||||
|
|||||||
@ -4,8 +4,8 @@ package org.jeecg.config.sign.interceptor;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
package org.jeecg.config.sign.util;
|
package org.jeecg.config.sign.util;
|
||||||
|
|
||||||
import jakarta.servlet.ReadListener;
|
import javax.servlet.ReadListener;
|
||||||
import jakarta.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
import jakarta.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import java.util.Map;
|
|||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.common.constant.SymbolConstant;
|
import org.jeecg.common.constant.SymbolConstant;
|
||||||
@ -172,7 +172,11 @@ public class HttpUtils {
|
|||||||
String[] params = param.split("&");
|
String[] params = param.split("&");
|
||||||
for (String s : params) {
|
for (String s : params) {
|
||||||
int index = s.indexOf("=");
|
int index = s.indexOf("=");
|
||||||
result.put(s.substring(0, index), s.substring(index + 1));
|
//update-begin---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
|
if (index != -1) {
|
||||||
|
result.put(s.substring(0, index), s.substring(index + 1));
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -196,7 +200,11 @@ public class HttpUtils {
|
|||||||
String[] params = param.split("&");
|
String[] params = param.split("&");
|
||||||
for (String s : params) {
|
for (String s : params) {
|
||||||
int index = s.indexOf("=");
|
int index = s.indexOf("=");
|
||||||
result.put(s.substring(0, index), s.substring(index + 1));
|
//update-begin---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
|
if (index != -1) {
|
||||||
|
result.put(s.substring(0, index), s.substring(index + 1));
|
||||||
|
}
|
||||||
|
//update-end---author:chenrui ---date:20240222 for:[issues/5879]数据查询传ds=“”造成的异常------------
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,4 +35,5 @@ public class Firewall {
|
|||||||
public void setLowCodeMode(String lowCodeMode) {
|
public void setLowCodeMode(String lowCodeMode) {
|
||||||
this.lowCodeMode = lowCodeMode;
|
this.lowCodeMode = lowCodeMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
package org.jeecg.config.vo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: TODO
|
||||||
|
* @author: scott
|
||||||
|
* @date: 2022年01月21日 14:23
|
||||||
|
*/
|
||||||
|
public class Shiro {
|
||||||
|
private String excludeUrls = "";
|
||||||
|
|
||||||
|
public String getExcludeUrls() {
|
||||||
|
return excludeUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExcludeUrls(String excludeUrls) {
|
||||||
|
this.excludeUrls = excludeUrls;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,8 @@ package org.jeecg.modules.base.service.impl;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.jeecg.common.api.dto.LogDTO;
|
import org.jeecg.common.api.dto.LogDTO;
|
||||||
import org.jeecg.config.security.utils.SecureUtil;
|
|
||||||
import org.jeecg.modules.base.mapper.BaseCommonMapper;
|
import org.jeecg.modules.base.mapper.BaseCommonMapper;
|
||||||
import org.jeecg.modules.base.service.BaseCommonService;
|
import org.jeecg.modules.base.service.BaseCommonService;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
@ -12,8 +12,8 @@ import org.jeecg.common.util.SpringContextUtils;
|
|||||||
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,7 +61,7 @@ public class BaseCommonServiceImpl implements BaseCommonService {
|
|||||||
//获取登录用户信息
|
//获取登录用户信息
|
||||||
if(user==null){
|
if(user==null){
|
||||||
try {
|
try {
|
||||||
user = SecureUtil.currentUser();
|
user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//e.printStackTrace();
|
//e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
org.springframework.boot.SpringApplicationRunListener=\
|
|
||||||
org.jeecg.config.DruidWallConfigRegister
|
|
||||||
@ -56,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="width: 600px; margin: 0 auto; margin-top: 50px; font-size: 12px; -webkit-font-smoothing: subpixel-antialiased; text-size-adjust: 100%;">
|
<div style="width: 600px; margin: 0 auto; margin-top: 50px; font-size: 12px; -webkit-font-smoothing: subpixel-antialiased; text-size-adjust: 100%;">
|
||||||
<p style="text-align: center; line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px !important; color: #7e8890 !important;">
|
<p style="text-align: center; line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px !important; color: #7e8890 !important;">
|
||||||
<span class="appleLinks">Copyright © 2023-2024 北京国炬科技股份有限公司. 保留所有权利。</span>
|
<span class="appleLinks">Copyright © 2023-2024 北京国炬信息技术有限公司. 保留所有权利。</span>
|
||||||
</p>
|
</p>
|
||||||
<p style="text-align: center;line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px; color: #7e8890 !important; margin-top: 10px;">
|
<p style="text-align: center;line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px; color: #7e8890 !important; margin-top: 10px;">
|
||||||
<span class="appleLinks">邮件由系统自动发送,请勿直接回复本邮件!</span>
|
<span class="appleLinks">邮件由系统自动发送,请勿直接回复本邮件!</span>
|
||||||
|
|||||||
@ -6,7 +6,10 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="info-top">
|
<div class="info-top">
|
||||||
<img src="https://jeecgdev.oss-cn-beijing.aliyuncs.com/temp/logo(1)_1697180761742.png" style="float: left; margin: 0 10px 0 0; width: 32px;height:32px" /><div style="color:#fff"><strong>【重要】新数据提醒</strong></div>
|
<img src="https://qiaoqiaoyun.oss-cn-beijing.aliyuncs.com/site/qqyunemaillogo.png" style="width: 35px;height:35px; background: #5e8ee5; border-radius: 5px;" />
|
||||||
|
<div style="color:#fff;">
|
||||||
|
<strong>【重要】新数据提醒</strong>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-wrap">
|
<div class="info-wrap">
|
||||||
<div class="tips" style="padding:15px;">
|
<div class="tips" style="padding:15px;">
|
||||||
@ -23,12 +26,12 @@
|
|||||||
<a style="color: #006eff;" href="${moreLink}" target="_blank" rel="noopener">[查看所有数据]</a>
|
<a style="color: #006eff;" href="${moreLink}" target="_blank" rel="noopener">[查看所有数据]</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">北京国炬平台</div>
|
<div class="footer">敲敲云平台</div>
|
||||||
<div class="footer" id="currentTime"></div>
|
<div class="footer" id="currentTime"></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 600px; margin: 0 auto; margin-top: 50px; font-size: 12px; -webkit-font-smoothing: subpixel-antialiased; text-size-adjust: 100%;">
|
<div style="width: 600px; margin: 0 auto; margin-top: 50px; font-size: 12px; -webkit-font-smoothing: subpixel-antialiased; text-size-adjust: 100%;">
|
||||||
<p style="text-align: center; line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px !important; color: #7e8890 !important;">
|
<p style="text-align: center; line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px !important; color: #7e8890 !important;">
|
||||||
<span class="appleLinks">Copyright © 2023-2024 北京国炬科技股份有限公司. 保留所有权利。</span>
|
<span class="appleLinks">Copyright © 2023-2024 北京敲敲云科技有限公司. 保留所有权利。</span>
|
||||||
</p>
|
</p>
|
||||||
<p style="text-align: center;line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px; color: #7e8890 !important; margin-top: 10px;">
|
<p style="text-align: center;line-height: 20.4px; text-size-adjust: 100%; font-family: 'Microsoft YaHei'!important; padding: 0px !important; margin: 0px; color: #7e8890 !important; margin-top: 10px;">
|
||||||
<span class="appleLinks">邮件由系统自动发送,请勿直接回复本邮件!</span>
|
<span class="appleLinks">邮件由系统自动发送,请勿直接回复本邮件!</span>
|
||||||
@ -46,6 +49,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-top{
|
.info-top{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
padding: 15px 25px;
|
padding: 15px 25px;
|
||||||
border-top-left-radius: 10px;
|
border-top-left-radius: 10px;
|
||||||
border-top-right-radius: 10px;
|
border-top-right-radius: 10px;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>jeecg-boot-parent</artifactId>
|
<artifactId>jeecg-boot-parent</artifactId>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<version>3.6.1</version>
|
<version>3.6.3</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
@ -16,6 +16,12 @@
|
|||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-boot-base-core</artifactId>
|
<artifactId>jeecg-boot-base-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- chatgpt -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
|
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user