Compare commits

..

362 Commits

Author SHA1 Message Date
d988a637b2 替换sas的注解 2025-11-13 09:56:28 +08:00
33cbc6e25b Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-11-10 15:56:21 +08:00
d715c7a0ac rollup版本号固定4.52.5,rollup-plugin-visualizer固定5.14.0,导致打包失败 2025-11-10 15:54:36 +08:00
aca407e1ce AI实战编程教程:JEECG低代码与Cursor+GitHub Copilot实现AI高效编程实战 2025-10-31 11:31:10 +08:00
cfea79a187 修复功能菜单“系统管理”-“部门管理”,用户列表中,编辑用户界面-用户账号为空 #9032 2025-10-30 22:26:55 +08:00
7848d1fb33 lock版本更新 2025-10-28 22:59:47 +08:00
91fa645878 3.8.3-master分支:租户用户 菜单下 新增用户报错 #9039 2025-10-28 13:40:27 +08:00
5bcb649384 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-10-22 09:32:55 +08:00
c9fc948658 更新jimureport和jimubi的版本号至2.1.5 2025-10-21 17:44:28 +08:00
b97d041e7f 更新springboot3版本号 2025-10-17 19:31:53 +08:00
6492f2c99a 更新README.md,修正Sa-Token下载链接格式 2025-10-16 19:13:47 +08:00
bf32385a06 更新README.md,调整下载链接顺序 2025-10-16 19:12:11 +08:00
6ef637c46f 提供SpringBoot3.3 + Sa-Token版本 2025-10-16 19:05:59 +08:00
bc6f336745 issues/8972 通义千问的多模态模型保存激活报错 #55 2025-10-14 22:46:46 +08:00
0d86df8e9e 1 2025-10-14 18:03:51 +08:00
3db673b67d issue格式 2025-10-14 18:03:01 +08:00
3ba5395d33 优化gateway启动报警告 2025-10-14 16:48:17 +08:00
e7eed37470 升级shardingsphere-jdbc版本到5.5.0,需要手工配置ShardingSphere数据源到spring.datasource.dynamic.datasource中,用法更明确 2025-10-14 16:47:05 +08:00
30ac3f7c72 升级shardingsphere-jdbc版本到5.5.0,需要手工配置ShardingSphere数据源到spring.datasource.dynamic.datasource中,用法更明确 2025-10-14 16:02:15 +08:00
03e6c97d80 重构JeecgBizToolsProvider.java,使用JsonObjectSchema替代JsonSchemaProperty,优化参数定义 2025-10-13 14:10:52 +08:00
b9f6f6dc53 升级langchain4j到1.3.0,解决很多模型不支持问题和MCP支持 2025-10-13 11:27:09 +08:00
107e13c8af [issues/8859]online表单java增强失效-- 2025-10-11 11:35:36 +08:00
0512b41b2b 更新README.md,增加对Node.js版本要求的说明,强调不再支持EOL的Node.js 18 2025-10-10 17:34:00 +08:00
d6d880f887 更新说明 2025-10-10 10:20:09 +08:00
d02753ce50 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-10-09 11:48:54 +08:00
b0e974a418 更新README.md,优化平台介绍和技术架构信息,增强AI应用平台描述 2025-10-09 11:15:45 +08:00
388fa9b8c2 v3.8.3大版本发布,全面迈向 SpringBoot3 2025-10-09 11:06:32 +08:00
bc04bd1433 --author:scott--date:20250930--for:使用@PostConstruct注解初始化PrometheusMeterRegistry配置,避免启动后配置延迟 2025-09-30 15:43:24 +08:00
35aba0784d Path Traversal Vulnerability /sys/comment/addFile /sys/upload/uploadMinio endpoint (notice the uploadlocal function is different from the /sys/common/upload ) #8827 2025-09-29 18:24:33 +08:00
c3822ab702 3.8.3版本能正常连接sqlserver数据库,但是无法解析查询代码 #8900 2025-09-29 11:55:37 +08:00
d4487356f0 更新 JeecgSystemApplication.java,排除 MongoAutoConfiguration 以避免未集成 mongo 的报错 2025-09-28 22:34:10 +08:00
0bfe5689cc Merge remote-tracking branch 'origin/springboot3' into springboot3_sas
# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
2025-09-28 21:59:14 +08:00
ae4363dc72 仪表盘大屏分享,提示需要token错误 2025-09-28 18:12:03 +08:00
3e6c7651ee 提供v3.8.3版本数据库脚本 2025-09-28 14:29:52 +08:00
c0ffd14b7a 更新 pom.xml,修改 jimureport 依赖的 artifactId 2025-09-26 16:26:35 +08:00
914875d6a1 更新 pom.xml,升级 jimubi-spring-boot-starter 和 jimureport-nosql-starter 版本 2025-09-26 15:26:30 +08:00
aa55c3a86c Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-09-25 15:22:55 +08:00
2298ee3eed 更新 docker-compose-cloud.yml,添加 jeecg-boot 网络配置到 sentinel 和 xxljob 服务 2025-09-25 13:47:33 +08:00
2a8853b353 docker微服务启动 docker-compose 增加xxljob和sentinel配置 2025-09-25 12:48:25 +08:00
b920c5b794 Oracle11g数据库 多租户管理>>添加租户 报错 #8897 2025-09-25 12:42:53 +08:00
d3fa38a9e6 更新 application-docker.yml,修改 Redis 和 XXL-JOB 配置为使用服务名称 2025-09-25 11:17:53 +08:00
b0df78b06c 配置文件升级到v3.8.3 切换tomcat 2025-09-25 10:23:51 +08:00
6a6d29254b 【v3.8.3版本发布】
Merge remote-tracking branch 'origin/springboot3' into springboot3_sas

# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/CommonAPI.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java
#	jeecg-boot/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java
#	jeecg-boot/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/fallback/SysBaseAPIFallback.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/api/controller/SystemApiController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/LoginController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysLogController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysTenantController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserAgentController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDataLog.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysAnnouncementServiceImpl.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysBaseApiImpl.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysTenantPackServiceImpl.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserDepartServiceImpl.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-docker.yml
#	jeecg-boot/pom.xml
2025-09-24 18:24:19 +08:00
80749098bd 添加 JustAuth 自动配置支持,更新 pom.xml 以包含 jeecg-boot-starter-job 依赖,并修改 application.yml 中的 nacos 服务器地址配置 2025-09-24 16:06:54 +08:00
19b7f2cb29 springboot3 支持 jdk17、jdk21、jdk24 2025-09-24 15:18:32 +08:00
39f5c3a5be 支持jdk21 2025-09-24 13:18:52 +08:00
9ee3a36fbb 版本发布日期更新 2025-09-24 13:17:21 +08:00
8c5cf3a0d9 【v3.8.3开源版本发布】更改groupId从org.jeecgframework.boot为org.jeecgframework.boot3,与springboot2区分 2025-09-24 12:19:01 +08:00
053552c123 【v3.8.3开源版本发布】
Merge remote-tracking branch 'origin/master' into springboot3

# Conflicts:
#	README.md
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FileDownloadUtils.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserAgentController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysDepartService.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysUserService.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysAnnouncementServiceImpl.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-seata/pom.xml
#	jeecg-boot/pom.xml
2025-09-24 12:01:11 +08:00
fc44deca83 Merge remote-tracking branch 'origin/springboot3' into springboot3 2025-09-24 11:18:36 +08:00
9c244bd266 Update @vitejs/plugin-vue-jsx version
Updated version of @vitejs/plugin-vue-jsx to 4.1.1.

 自动下载的依赖 @vitejs/plugin-vue-jsx@4.2.0 是 CJS 格式 , 1.0.0-beta.38 版本的 @rolldown/pluginutils 是 ESM 格式
模块不兼容
2025-09-18 17:31:14 +08:00
e377bf6990 升级版本号 2025-09-05 18:49:20 +08:00
434b42e9ed 部分配置丢失,修复 2025-09-04 10:47:50 +08:00
f1ceb08e16 Merge remote-tracking branch 'origin/master' into springboot3
# Conflicts:
#	README.md
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-seata/pom.xml
#	jeecg-boot/pom.xml
#	jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useFinallyProps.ts
2025-09-04 09:40:57 +08:00
d2eedacc85 优化项目介绍 2025-09-03 19:58:13 +08:00
8791384791 优化项目介绍 2025-09-03 19:21:56 +08:00
b67770ff14 升级miniao到1.10.14,解决SqlServer分页兼容问题 2025-09-03 17:36:32 +08:00
1dae808cf1 升级积木报表和积木BI到最新版 v2.1.3 2025-09-03 16:34:26 +08:00
70d8353219 升级springBoot 3.5.5 2025-09-03 15:53:53 +08:00
3e208de18e 默认主干切换springboot3分支 2025-09-03 15:37:26 +08:00
c4a87d7c58 swagger请求,token无效还是返回了下载文件,期望返回401 #8767 2025-09-02 14:49:14 +08:00
56976e68b4 升级spring-boot到3.5.4、升级spring-cloud到2025.0.0、升级spring-cloud-alibaba到2023.0.3.3 2025-08-20 10:42:43 +08:00
db1ff0268b Squashed commit of the following:
commit b7519d7199
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 15:18:52 2025 +0800

    中文乱码

commit 81ba07c853
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 15:09:21 2025 +0800

    增加代码生成用法文档

commit 92ed296e63
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 14:04:22 2025 +0800

    【issues/8709】LayoutContent样式多出1px

commit c2aff84914
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 14:04:09 2025 +0800

    【issues/8683】DatePicker组件的componentProps使用函数形式时初始值获取不对

commit e002cd3bf3
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 14:03:51 2025 +0800

    【issues/8680】editComponentProps 可接受一个函数传入record

commit 1de07ff3ff
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 14:03:32 2025 +0800

    -- author:liaozhiyang---date:20250813--for:【issues/8690】BasicTable的rowSelection新增onSelect方法 ---

commit 35852d41f1
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 14:03:04 2025 +0800

    jvxeTable表格切换disabled属性时,相邻的两个枚举下拉,如果值是一样的,但是label不一样,会把第二个下拉的显示值渲染到第一个下拉中 #8593

commit a2cb1d9f25
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 14:00:01 2025 +0800

    【issues/8529】setColumns将原本隐藏的列展示后,列配置里却没有勾选该列

commit 2002af54d0
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 13:59:42 2025 +0800

    JVxeTypes.image组件action字段只能定义第1张图片的上传接口,后面图片的接口还是使用公共上传接口 #8628

commit 89747403a2
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 13:59:28 2025 +0800

    JVxeTable组件全选翻页后会被取消选中 #8630

commit 3db0995c3f
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 19 11:23:19 2025 +0800

    [代码生成]前端代码支持直接生成到前端项目、菜单sql会自动生成到start项目的flyway目录

commit 950621dd88
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 18 23:04:59 2025 +0800

    升级代码生成器,一键生成代码,vue3代码会生成到前端项目、菜单升级sql自动迁移到flyway目录重启自动执行(不需要手工迁移前端代码和手工执行升级sql)

commit 033cf51d69
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 18 23:04:52 2025 +0800

    升级代码生成器,一键生成代码,vue3代码会生成到前端项目、菜单升级sql自动迁移到flyway目录重启自动执行(不需要手工迁移前端代码和手工执行升级sql)

commit fb9f367517
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 18 23:02:53 2025 +0800

    代码生成,online自定义按钮无排序报错

commit b2da45d803
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 18 16:23:22 2025 +0800

    演示地址

commit 2840f0d325
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 18 15:37:10 2025 +0800

    默认账号密码

commit 6ace7eae8a
Author: JEECG <445654970@qq.com>
Date:   Sun Aug 17 15:11:45 2025 +0800

    开发环境关闭日志生成,项目启动快;生产环境请按需打开注释

commit 3d88147c59
Author: JEECG <445654970@qq.com>
Date:   Thu Aug 14 23:03:46 2025 +0800

    调整微服务启动文档

commit ba0052d452
Author: JEECG <445654970@qq.com>
Date:   Wed Aug 13 13:55:13 2025 +0800

    支持lazy-initialization启动,项目大了后启动会更快

commit 69fca254f0
Author: JEECG <445654970@qq.com>
Date:   Wed Aug 13 11:47:27 2025 +0800

    补充注释

commit b3de596199
Author: JEECG <445654970@qq.com>
Date:   Wed Aug 13 11:47:16 2025 +0800

    彻底关闭 prettier 校验规则

commit f46273d15e
Author: JEECG <445654970@qq.com>
Date:   Wed Aug 13 10:26:23 2025 +0800

    设置ESLint 的 vue/html-self-closing 自闭合标签警告配置

commit 0fe258dbc2
Author: JEECG <445654970@qq.com>
Date:   Wed Aug 13 09:26:16 2025 +0800

    修复 onExportXls defSort 不生效问题 #7570

commit de7f23c555
Merge: d97e56b2 444c7140
Author: JEECG <zhangdaiscott@163.com>
Date:   Wed Aug 13 09:20:31 2025 +0800

    Merge pull request #8496 from lileiAimee/developer

    解决TableAction中自定义图标颜色不起作用的问题

commit d97e56b2f0
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 12 19:08:43 2025 +0800

    多租户模式下系统系统会给租户默认增加上测试的角色菜单,但是后台获取菜单时异常,无法打开相关页面 #8667

commit c868496b78
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 12 19:02:49 2025 +0800

    映射警告

commit c5150baa69
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 12 18:18:34 2025 +0800

    支持通过用户账号邀请加入租户

commit 3d9f59c69b
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 12 18:17:55 2025 +0800

    邀请用户加入租户,支持通过用户账号

commit 420d6db3fb
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 12 18:06:20 2025 +0800

    登录用户没有部门,不提示警告

commit 473a626039
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 12 14:10:02 2025 +0800

    增加JPopup组件带参数示例

commit 0308b0597c
Author: JEECG <445654970@qq.com>
Date:   Tue Aug 12 14:08:18 2025 +0800

    【issues/8426】解决JPopup组件传参不能接收

commit 2191f5d48c
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 11 22:43:07 2025 +0800

    调整位置

commit 1158b0b6e7
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 11 22:30:39 2025 +0800

    升级seata到1.7.0;升级dynamic-datasource-spring-boot-starter到3.5.2

commit ead2cef1f4
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 11 18:47:48 2025 +0800

    支持多字段默认排序defSort数组、解决多列排序无效 #8659

commit 83bb0a0a6a
Author: JEECG <445654970@qq.com>
Date:   Mon Aug 11 18:47:43 2025 +0800

    支持多字段默认排序defSort数组

commit b474e9e5a5
Author: JEECG <445654970@qq.com>
Date:   Sun Aug 10 17:06:01 2025 +0800

    开发环境安装

commit 422373e300
Author: JEECG <445654970@qq.com>
Date:   Sun Aug 10 16:30:13 2025 +0800

    提供JeecgBoot 运行环境python检查脚本

commit 1cf11a4c2a
Author: JEECG <445654970@qq.com>
Date:   Sat Aug 9 09:41:57 2025 +0800

    提供jeecgboot-oracle11g.dmp

commit 925f163784
Author: JEECG <445654970@qq.com>
Date:   Fri Aug 8 22:07:24 2025 +0800

    引入jeecg-boot-starter-job依赖启动报错 #8694

commit d01c1d7d47
Author: JEECG <445654970@qq.com>
Date:   Thu Aug 7 15:41:21 2025 +0800

    支持lazy-initialization

commit 3576b54945
Author: JEECG <445654970@qq.com>
Date:   Thu Aug 7 15:36:51 2025 +0800

    升级积木报表和积木BI到最新版v2.1.2

commit 444c7140f6
Author: lileiAimee <345697385@qq.com>
Date:   Wed Jun 25 09:55:07 2025 +0800

    解决TableAction中自定义图标颜色不起作用的问题

# Conflicts:
#	README-EN.md
#	README.md
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-seata/pom.xml
#	jeecg-boot/pom.xml
2025-08-19 22:58:12 +08:00
47107a53c2 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas
# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger3Config.java
2025-08-13 18:16:31 +08:00
08f245bdf9 修改遗漏 swagger上选择的接口和实际接口不对应#8705 2025-08-13 18:15:19 +08:00
52cd43d17c sas版本,swagger接口测试token无效返回下载文件,应该返回401错误json 2025-08-13 17:49:36 +08:00
8cc033b86f swagger上选择的接口和实际接口不对应 #8705 2025-08-13 17:22:42 +08:00
7ff70930ef swagger上选择的接口和实际接口不对应 #8705 2025-08-13 17:21:46 +08:00
45f83dff43 # Conflicts:
#	jeecg-boot/jeecg-boot-base-core/pom.xml
2025-08-13 16:45:40 +08:00
6b7542620b swagger上选择的接口和实际接口不对应 #8705 2025-08-13 16:44:16 +08:00
fcbaed2f7f fix(xxljob): 修复因 factoryBeanObjectType 导致的启动失败 #8550 2025-08-13 00:04:28 +08:00
67d9865861 Merge pull request #8550 from TsuGit/fix/xxljob-startup-error
fix(xxljob): 修复因 factoryBeanObjectType 导致的启动失败
2025-08-13 00:02:30 +08:00
4acd5b3464 Squashed commit of the following:
升级seata到1.7.0;升级dynamic-datasource-spring-boot-starter到3.5.2
    支持多字段默认排序defSort数组、解决多列排序无效 #8659
    支持多字段默认排序defSort数组
    提供JeecgBoot 运行环境python检查脚本
    提供jeecgboot-oracle11g.dmp
2025-08-12 09:43:20 +08:00
cd809a6573 Squashed commit of the following:
升级seata到1.7.0;升级dynamic-datasource-spring-boot-starter到3.5.2
    支持多字段默认排序defSort数组、解决多列排序无效 #8659
    支持多字段默认排序defSort数组
    提供JeecgBoot 运行环境python检查脚本
    提供jeecgboot-oracle11g.dmp
2025-08-12 09:25:01 +08:00
e10df20e98 引入jeecg-boot-starter-job依赖启动报错 #8694 2025-08-08 22:23:25 +08:00
ac446691c4 Squashed commit of the following:
commit 925f163784
Author: JEECG <445654970@qq.com>
Date:   Fri Aug 8 22:07:24 2025 +0800

    引入jeecg-boot-starter-job依赖启动报错 #8694

commit d01c1d7d47
Author: JEECG <445654970@qq.com>
Date:   Thu Aug 7 15:41:21 2025 +0800

    支持lazy-initialization

commit 3576b54945
Author: JEECG <445654970@qq.com>
Date:   Thu Aug 7 15:36:51 2025 +0800

    升级积木报表和积木BI到最新版v2.1.2

# Conflicts:
#	jeecg-boot/pom.xml
2025-08-08 22:22:30 +08:00
d0f85ccc7e Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-08-07 18:18:56 +08:00
0feb307e8d Merge remote-tracking branch 'origin/master' into springboot3 2025-08-07 18:11:58 +08:00
781d61e96e swagger请求头部没有X-Access-Token #8676 2025-08-07 18:02:27 +08:00
d12fe9d8de 模型配置-无法添加模型-接口存在问题 #8677 2025-08-06 11:04:54 +08:00
223d374edd Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-08-04 18:45:21 +08:00
e795e03365 【issues/8666】升级mybatisPlus后SqlServer分页使用OFFSET ? ROWS FETCH NEXT ? ROWS ONLY,导致online报表报错 2025-08-04 18:39:35 +08:00
7f011f199e yaml加入security配置 2025-08-03 12:52:40 +08:00
873b8bd823 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-08-03 12:52:21 +08:00
59ece16059 修改springboot3的配置yml 2025-08-03 12:51:32 +08:00
91208a4968 Merge remote-tracking branch 'origin/master' into springboot3 2025-08-03 12:49:26 +08:00
2f3aff9584 yaml加入security配置 2025-08-03 10:37:08 +08:00
1f74cb538f Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-08-03 10:33:40 +08:00
128c2c97f6 修改springboot3的配置yml 2025-08-03 10:32:18 +08:00
424dc33bba 修改springboot3的配置yml 2025-08-03 10:13:47 +08:00
2cce917ab2 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-08-03 10:11:35 +08:00
1cb48b4f0c 【解决SqlServer兼容问题,提供oracle和SqlServer的数据库脚本】
Merge remote-tracking branch 'origin/master' into springboot3

# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java
2025-08-03 10:07:04 +08:00
fd320be203 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-08-01 17:21:25 +08:00
79b182819b Merge remote-tracking branch 'origin/master' into springboot3 2025-08-01 17:20:53 +08:00
6e5c0eebce JEECG Boot 一键docker启动脚本 2025-08-01 17:05:44 +08:00
621781d336 JEECG Boot 一键docker启动脚本 2025-08-01 17:04:39 +08:00
cae8e7e8d2 【合并v3.8.2 docker-compose优化配置】
Squashed commit of the following:

commit e70844ce61
Merge: e00ffa26 676fffa2
Author: JEECG <445654970@qq.com>
Date:   Fri Aug 1 10:54:33 2025 +0800

    【合并v3.8.2 docker-compose优化配置】
    Merge remote-tracking branch 'origin/master' into springboot3

    # Conflicts:
    #	jeecg-boot/jeecg-boot-base-core/pom.xml
    #	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger3Config.java
    #	jeecg-boot/jeecg-module-system/jeecg-system-start/Dockerfile
    #	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/JeecgSystemApplication.java
    #	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-docker.yml
    #	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
    #	jeecg-boot/pom.xml

commit e00ffa2670
Author: JEECG <445654970@qq.com>
Date:   Fri Aug 1 10:46:07 2025 +0800

    升级autopoi到1.4.14最新版

commit 676fffa2c8
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 22:47:01 2025 +0800

    docker-compose增加jeecg-boot-pgvector

commit 23cc569a47
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 18:12:57 2025 +0800

    docker system日志中文乱码解决

commit dafacf153b
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 18:12:41 2025 +0800

    升级版本号到3.8.2

commit 7a9f357510
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 17:56:40 2025 +0800

    更新v3.8.2 功能清单

commit f30a8c658a
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 11:35:16 2025 +0800

    数据库缺少openapi微服务网关配置

commit e84d7726d2
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 10:20:09 2025 +0800

    后台接口地址修改

commit 0f39802698
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 09:56:24 2025 +0800

    docker自动化部署命令

commit a014a3ed0e
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 21:55:16 2025 +0800

    v3.8.2 优化一键docker启动前后端

commit 5720d1a01e
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 19:26:38 2025 +0800

    升级版本号到3.8.2

commit 5eed6ac6d2
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:49:29 2025 +0800

    升级版本号到3.8.2

commit 0cfa1e223a
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:28:10 2025 +0800

    v3.8.2 系统通知改造支持分类

commit 219869f4c0
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:25:58 2025 +0800

    v3.8.2 版本前端代码

commit e6edde963a
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:25:46 2025 +0800

    v3.8.2 版本后端代码

commit c44b66128e
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:23:09 2025 +0800

    XXL-JOB(2.4.0 及以上)已被移除,分片参数获取方式变更。

commit 9356b04741
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 10:57:52 2025 +0800

    升级online到3.8.2-beta

commit d0a094f9a3
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 10:57:31 2025 +0800

    升级mybatis-plus到3.5.12、升级jsqlparser到4.9

commit 73eb625737
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 09:51:34 2025 +0800

    升级jimureport到v2.1.1

commit 74880705b8
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 09:18:46 2025 +0800

    升级online到3.8.2-beta

# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
2025-08-01 10:56:21 +08:00
e70844ce61 【合并v3.8.2 docker-compose优化配置】
Merge remote-tracking branch 'origin/master' into springboot3

# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/pom.xml
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger3Config.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/Dockerfile
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/JeecgSystemApplication.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-docker.yml
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
#	jeecg-boot/pom.xml
2025-08-01 10:54:33 +08:00
e00ffa2670 升级autopoi到1.4.14最新版 2025-08-01 10:46:07 +08:00
ada97acb04 【合并v3.8.2版本代码】 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas
# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-boot/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/fallback/SysBaseAPIFallback.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/ThirdAppController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysBaseApiImpl.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/banner.txt
2025-07-31 16:10:56 +08:00
6c15b45a8c 【合并v3.8.2最新版代码】
Squashed commit of the following:

commit f30a8c658a
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 11:35:16 2025 +0800

    数据库缺少openapi微服务网关配置

commit e84d7726d2
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 10:20:09 2025 +0800

    后台接口地址修改

commit 0f39802698
Author: JEECG <445654970@qq.com>
Date:   Thu Jul 31 09:56:24 2025 +0800

    docker自动化部署命令

commit a014a3ed0e
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 21:55:16 2025 +0800

    v3.8.2 优化一键docker启动前后端

commit 5720d1a01e
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 19:26:38 2025 +0800

    升级版本号到3.8.2

commit 5eed6ac6d2
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:49:29 2025 +0800

    升级版本号到3.8.2

commit 0cfa1e223a
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:28:10 2025 +0800

    v3.8.2 系统通知改造支持分类

commit 219869f4c0
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:25:58 2025 +0800

    v3.8.2 版本前端代码

commit e6edde963a
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:25:46 2025 +0800

    v3.8.2 版本后端代码

commit c44b66128e
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 18:23:09 2025 +0800

    XXL-JOB(2.4.0 及以上)已被移除,分片参数获取方式变更。

commit 9356b04741
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 10:57:52 2025 +0800

    升级online到3.8.2-beta

commit d0a094f9a3
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 10:57:31 2025 +0800

    升级mybatis-plus到3.5.12、升级jsqlparser到4.9

commit 73eb625737
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 09:51:34 2025 +0800

    升级jimureport到v2.1.1

commit 74880705b8
Author: JEECG <445654970@qq.com>
Date:   Wed Jul 30 09:18:46 2025 +0800

    升级online到3.8.2-beta

# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/pom.xml
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger3Config.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/JeecgSystemApplication.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
#	jeecg-boot/pom.xml
2025-07-31 14:23:45 +08:00
01547ef83e Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-07-30 09:14:08 +08:00
f67cfa1bfb 删除重复依赖 2025-07-29 12:00:05 +08:00
8d91caa4e6 升级积木报表和积木BI到最新版 2025-07-28 18:54:21 +08:00
b3ed442b55 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas
# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/pom.xml
2025-07-25 14:10:51 +08:00
0d9f9a04cc 升级knife4j-openapi3-jakarta-spring-boot-starter到4.6.0解决knife4j-production不生效问题 2025-07-25 14:08:00 +08:00
9cca8f8719 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2025-07-25 13:26:18 +08:00
90565fcf79 Merge remote-tracking branch 'origin/master' into springboot3
# Conflicts:
#	jeecg-boot/db/tables_nacos.sql
2025-07-25 13:23:47 +08:00
ae6069882f TODO 暂时注释掉,for:【issues/8638】springboot3分支,knife4j不能正确显示文档,但是swagger-ui和v3/api-docs正常 #8638 2025-07-24 18:36:21 +08:00
cf4d888839 TODO 暂时注释掉,for:【issues/8638】springboot3分支,knife4j不能正确显示文档,但是swagger-ui和v3/api-docs正常 #8638 2025-07-24 18:34:25 +08:00
f0a5edaa49 启动democloud服务时出现循环依赖报错 #8573 2025-07-11 10:45:56 +08:00
f510578cb7 启动democloud服务时出现循环依赖报错 #8573 2025-07-11 10:35:17 +08:00
ab98b26cac 【合并升级v3.8.1】
Merge remote-tracking branch 'origin/springboot3' into springboot3_sas

# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/LoginController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDataSourceController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartPermissionController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysTableWhiteListController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java
2025-07-08 18:32:55 +08:00
2d7c51eadc 【合并升级v3.8.1】
Merge remote-tracking branch 'origin/master' into springboot3

# Conflicts:
#	jeecg-boot/README.md
#	jeecg-boot/db/tables_nacos.sql
#	jeecg-boot/jeecg-boot-base-core/pom.xml
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java
#	jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/pom.xml
#	jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/app/controller/AiragAppController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/monitor/actuator/httptrace/CustomInMemoryHttpTraceRepository.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/service/impl/OpenApiPermissionServiceImpl.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysUserService.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/pom.xml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
#	jeecg-boot/jeecg-server-cloud/jeecg-system-cloud-start/src/main/java/org/jeecg/JeecgSystemCloudApplication.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
#	jeecg-boot/pom.xml
#	jeecgboot-vue3/pnpm-lock.yaml
2025-07-08 16:33:51 +08:00
216d4b9a1f fix: 解决 xxljob 因 factoryBeanObjectType 导致的启动失败 2025-07-06 03:10:33 +08:00
5972c74b43 解决积木报表springboot3 找不到类base64utils #3834和SqlServer兼容问题 2025-06-05 15:51:05 +08:00
64b3c9e42e 解决积木报表springboot3 找不到类base64utils #3834和SqlServer兼容问题 2025-06-05 15:45:31 +08:00
8eb81493ce ai流程脚本节点执行报错 2025-05-28 11:28:55 +08:00
d69cb121fc 解决AI脚本节点执行报错问题 2025-05-28 11:07:33 +08:00
044fc47586 ai ⼯作流使⽤知识库报错"白名单未通过" 2025-05-27 17:58:02 +08:00
10a9edd10b ai⼯作流使⽤知识库报错 "白名单校验未通过" 2025-05-27 17:56:46 +08:00
bbe18c582c 访问Swagger接口不带doc.html后缀,会丢失项目前缀/jeecg-boot/导致测试接口,返回下载文件 2025-05-26 19:17:00 +08:00
c71ff3fbcc 访问Swagger接口不带doc.html后缀,会丢失项目前缀/jeecg-boot/导致测试接口,返回下载文件 2025-05-26 19:14:02 +08:00
b894125b53 【升级到springboot 3.4.5】
Merge remote-tracking branch 'origin/springboot3' into springboot3_sas

# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/pom.xml
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
2025-05-22 11:25:00 +08:00
08612d5bfa springboot3分支的redis配置格式改了 2025-05-22 11:12:27 +08:00
2ecce8f02d 更新nacos配置,增加aigc的配置 2025-05-22 10:59:57 +08:00
62937f14fb 升级shiro到2.0.4 2025-05-22 10:55:16 +08:00
d6ccc4a326 还原库名 2025-05-22 10:50:17 +08:00
1893108136 升级版本号到3.8.1(springboot3升级到3.4.5) 2025-05-22 10:18:25 +08:00
7980915bdc 移除AI大模型管理依赖并更新pom.xml 2025-05-22 09:28:04 +08:00
550997268b 最新版 2025-05-20 10:24:34 +08:00
2e236703b2 升级spring-boot-starter-parent版本至2.7.18 2025-05-19 18:04:16 +08:00
a771d24a57 升级knife4j-spring-boot-starter版本至4.5.0 2025-05-19 16:26:30 +08:00
69f5d12de7 缺少的token排除请求 2025-05-19 16:26:13 +08:00
5b5999e786 shiro排除配置改成security 2025-05-19 16:25:49 +08:00
9e7d40a080 升级springboot3.4.5 2025-05-19 16:09:09 +08:00
fc3fe39d95 升级mybatis为springboot3版本 2025-05-19 15:28:07 +08:00
2c38db456b 合并redis配置错误 2025-05-19 13:58:09 +08:00
48e23aafab 【v3.8.0合并】Merge remote-tracking branch 'origin/springboot3' into springboot3_sas
# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/pom.xml
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/encryption/AesEncryptUtil.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/firewall/interceptor/LowCodeModeInterceptor.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDataSourceController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartPermissionController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysTableWhiteListController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysTenantPackServiceImpl.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/tree/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/system/test/InsertDemoTest.java
#	jeecg-boot/pom.xml
#	jeecgboot-vue3/pnpm-lock.yaml
2025-05-19 13:52:08 +08:00
e52538d304 升级spring3.4.5后,会有很多警告BeanPostProcessorChecker:437 - Bean 'org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration' of type [org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [defaultAdvisorAutoProxyCreator]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE. 2025-05-18 16:40:55 +08:00
e91cbd5cd8 Merge pull request #8297 from MuShan-bit/springboot3_upgrade344-fix-warn
logging(level): 设置 PostProcessorRegistrationDelegate 日志级别为 error
2025-05-18 16:33:14 +08:00
70cec8b5c6 logging(level): 设置 PostProcessorRegistrationDelegate 日志级别为 error
- 在 application-dev.yml 文件中添加了 org.springframework.context.support.PostProcessorRegistrationDelegate 的日志级别配置
- 此修改旨在减少不必要的日志输出,提高日志的可读性和性能
2025-05-16 22:29:00 +08:00
d2365088ce AIGC大模型应用功能 2025-05-16 11:27:48 +08:00
a679571a5a 基于AK和SK认证鉴权OpenAPI 2025-05-16 10:41:24 +08:00
b9c74e549f 移除javax.annotation.Resource导入,改为使用jakarta.annotation.Resource 2025-05-16 10:31:49 +08:00
81c1724016 升级online到v3.8.0版本 2025-05-16 10:31:39 +08:00
56d59eb589 修改springboot3 v3.8.0发布时间 2025-05-16 09:58:36 +08:00
a00fcae3a3 【v3.8.0 合并】Merge remote-tracking branch 'origin/master' into springboot3
# Conflicts:
#	README.md
#	jeecg-boot/db/tables_nacos.sql
#	jeecg-boot/jeecg-boot-base-core/pom.xml
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/oConvertUtils.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger2Config.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger3Config.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java
#	jeecg-boot/jeecg-boot-module/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/controller/JeecgDemoController.java
#	jeecg-boot/jeecg-boot-module/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/entity/JeecgDemo.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiLogController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/controller/OpenApiPermissionController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApi.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApiAuth.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApiHeader.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApiLog.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/entity/OpenApiParam.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/filter/ApiAuthFilter.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/mapper/OpenApiLogMapper.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/openapi/service/OpenApiLogService.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/DuplicateCheckController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/LoginController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysCommentController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDataSourceController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartPermissionController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictItemController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysGatewayRouteController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysTableWhiteListController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysCheckRule.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysComment.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDataSource.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDepartPermission.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDepartRole.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDepartRolePermission.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysDepartRoleUser.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysFillRule.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysFormFile.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysGatewayRoute.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysPackPermission.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysPosition.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysRoleIndex.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysTableWhiteList.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysTenantPack.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysTenantPackUser.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysThirdAccount.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysThirdAppConfig.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysUserPosition.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysUserTenant.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/model/DuplicateCheckVo.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/springframework/boot/autoconfigure/mongo/MongoAutoConfiguration.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/one/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/tree/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/default/tree/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/erp/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/erp/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/erp/onetomany/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/inner-table/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/inner-table/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/inner-table/onetomany/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/inner-table/onetomany/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/jvxe/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/jvxe/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/jvxe/onetomany/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/tab/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/tab/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/tab/onetomany/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template-online/tab/onetomany/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/one/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/one/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/one2/java/${bussiPackage}/controller/${entityPackage}/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany2/java/${bussiPackage}/${entityPackage}/controller/${entityName}Controller.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany2/java/${bussiPackage}/${entityPackage}/entity/${entityName}.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany2/java/${bussiPackage}/${entityPackage}/entity/[1-n]Entity.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/resources/jeecg/code-template/onetomany2/java/${bussiPackage}/${entityPackage}/vo/${entityName}Page.javai
#	jeecg-boot/jeecg-module-system/jeecg-system-start/pom.xml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/java/org/jeecg/config/flyway/FlywayConfig.java
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml
#	jeecg-boot/jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/system/test/SampleTest.java
#	jeecg-boot/jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/handler/swagger/SwaggerResourceController.java
#	jeecg-boot/jeecg-server-cloud/jeecg-cloud-gateway/src/main/java/org/jeecg/loader/DynamicRouteLoader.java
#	jeecg-boot/jeecg-server-cloud/jeecg-cloud-gateway/src/main/resources/application.yml
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-more/src/main/java/org/jeecg/modules/test/feign/controller/JeecgTestFeignController.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-rocketmq/src/main/java/org/jeecg/modules/test/rocketmq/controller/JeecgMqTestController.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-seata/jeecg-cloud-test-seata-order/src/main/java/org/jeecg/modules/test/seata/order/controller/SeataOrderController.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
#	jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
#	jeecg-boot/pom.xml
2025-05-15 20:01:54 +08:00
286d10a50f Merge branch 'springboot3_upgrade344' of https://github.com/jeecgboot/jeecg-boot into springboot3_upgrade344 2025-05-14 15:19:10 +08:00
68f36cb1e5 升级online 2025-05-14 13:59:56 +08:00
78454d3434 Merge pull request #8273 from MuShan-bit/springboot3_upgrade344-upgrade-shiro2.0.4
feat: 升级 shiro 到 2.0.4 版本,解决 ShiroRequestMappingConfig 获取 RequestMappingHandlerMapping Bean 冲突
2025-05-14 09:21:36 +08:00
56fbc2ed8f feat: 升级 shiro 到 2.0.4 版本,解决Shiro获取 requestMappingHandlerMapping 时 spring-boot-autoconfigure:3.4.5 和 spring-boot-actuator-autoconfigure:3.4.5 Bean 依赖冲突, 2025-05-13 22:48:48 +08:00
197d7adaaf Merge pull request #8256 from MuShan-bit/springboot3-chore-upgrade-shiro
upgrade shiro to 2.0.4
2025-05-11 10:34:23 +08:00
e952518d71 feat: 升级 shiro 到 2.0.4 版本 2025-05-10 22:14:02 +08:00
1e259c805e fastjson升级到2.0.57;jimureport升级到1.9.5;minidao升级到1.10.8 2025-05-08 22:39:57 +08:00
8a82141c95 升级jsqlparser到4.9 2025-05-08 16:47:46 +08:00
888a032266 优化bean无法被所有beanpostprocessor处理 2025-04-30 10:00:02 +08:00
309c76d268 修复swagger接口文档正常显示 2025-04-25 18:08:45 +08:00
f78eabfc66 使用minidao适配jsqlparser 2025-04-25 16:54:55 +08:00
748331d649 处理jsqlparser兼容问题 2025-04-22 16:00:17 +08:00
b70e709e53 升级spring boot 3.4.4 2025-04-16 16:18:32 +08:00
2ba17648c4 Merge pull request #8116 from EightMonth/springboot3
优化swagger文档改造
2025-04-15 11:41:44 +08:00
36caab37e2 Update application-mysql.yml 2025-04-15 11:07:54 +08:00
6e721e4120 归集spring-doc默认配置
(cherry picked from commit d4d0c884f0)
2025-04-15 10:39:05 +08:00
a17b403675 优化swagger文档架构改造 2025-04-03 17:46:11 +08:00
632fd72d79 Merge pull request #8053 from EightMonth/springboot3
排除部分接口文档,为免登录接口排除token校验请求头
2025-04-01 21:28:44 +08:00
15fc262675 排除部分接口文档,为免登录接口排除token校验请求头 2025-04-01 18:05:04 +08:00
6768d65e1e Merge pull request #8008 from EightMonth/springboot3
修复 CVE-2023-6378
2025-03-25 16:02:57 +08:00
657b84d3cf Merge pull request #8007 from EightMonth/springboot3_sas
修复 CVE-2023-6378
2025-03-25 16:02:45 +08:00
2021bf39f8 修复 CVE-2023-6378 2025-03-25 16:00:14 +08:00
410ab7bcc3 修复 CVE-2023-6378 2025-03-25 15:58:19 +08:00
174f1ae432 Merge pull request #8004 from EightMonth/springboot3
jeewx-api修改成weixin4j
2025-03-25 14:19:42 +08:00
fdeb37c3d0 Merge pull request #8005 from EightMonth/springboot3_sas
jeewx-api修改成weixin4j
2025-03-25 14:19:33 +08:00
f9123208e1 jeewx-api修改成weixin4j 2025-03-25 14:15:05 +08:00
eef2f7e269 jeewx-api修改成weixin4j 2025-03-25 14:13:45 +08:00
6a0ec66d3d Merge branch 'springboot3' of https://github.com/jeecgboot/jeecg-boot into springboot3 2025-03-25 14:10:17 +08:00
163b0b531f 视频介绍 2025-03-18 10:08:05 +08:00
accb8f2f9f Merge pull request #7950 from EightMonth/springboot3_sas
解决严重bug,War包方式部署,服务启动报错
2025-03-12 15:28:44 +08:00
d1af49a33f Merge pull request #7949 from EightMonth/springboot3
解决严重bug,War包方式部署,服务启动报错
2025-03-12 15:28:07 +08:00
c643994546 解决严重bug,War包方式部署,服务启动报错 2025-03-12 14:16:50 +08:00
03265691e6 解决严重bug,War包方式部署,服务启动报错 2025-03-12 14:12:00 +08:00
6934a0adee Merge pull request #7875 from EightMonth/springboot3_sas
修复 #7613
2025-03-03 17:05:12 +08:00
de9cc2f30d Merge pull request #7874 from EightMonth/springboot3
修复 #7613
2025-03-03 17:05:01 +08:00
93e32a7177 Merge branch 'springboot3' into springboot3_sas 2025-03-03 14:30:40 +08:00
26887959cd 修复 #7613 2025-03-03 14:27:16 +08:00
c9f5bb4409 【3.7.3 版本合并升级】
Merge remote-tracking branch 'origin/springboot3' into springboot3_sas

# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysTenantPackServiceImpl.java
#	jeecgboot-vue3/package.json
2025-02-24 18:24:51 +08:00
7e15e81218 版本合并,升级springboot3分支到3.7.3 2025-02-20 17:56:16 +08:00
10b68858d6 Merge pull request #7796 from EightMonth/springboot3_sas
固定vue-router版本号
2025-02-11 14:49:17 +08:00
8b0e0367c7 Merge pull request #7797 from EightMonth/springboot3
固定vue-router版本号
2025-02-11 14:49:06 +08:00
da72e8f9c5 Update package.json 2025-02-11 14:27:40 +08:00
334f7dbb62 Update package.json 2025-02-11 14:24:18 +08:00
e9ddd21286 固定vue-router版本号 2025-02-11 09:53:29 +08:00
73e86686dc 固定vue-router版本号 2025-02-11 09:25:59 +08:00
458526075e Merge remote-tracking branch 'origin/springboot3' into springboot3 2024-12-24 15:44:51 +08:00
a1b55f0d40 解决vue-router升级版本报错问题 2024-12-24 15:44:27 +08:00
f43d0d486b Merge pull request #7378 from EightMonth/springboot3_sas
分布式事务demo修复
2024-11-20 17:42:55 +08:00
65bde3331b 修复#7459,修复sas分支 @ignoreauth注解无效 2024-11-20 11:08:53 +08:00
2f0a3bcd87 Merge pull request #7379 from EightMonth/springboot3
分布式事务demo修复
2024-11-20 10:31:10 +08:00
b60942aa86 分布式事务demo修复 2024-10-24 10:48:00 +08:00
197b267e71 分布式事务demo修复 2024-10-24 09:19:49 +08:00
30d3a9f17b 分布式事务demo修复 2024-10-24 09:15:06 +08:00
03739f2837 【springboot3分支 issues/7353】The bean 'dataSource', defined in class path resource #7353 2024-10-19 20:18:35 +08:00
79f7134bd5 【springboot3分支 issues/7353】The bean 'dataSource', defined in class path resource #7353 2024-10-19 19:30:05 +08:00
6d432bc186 Merge pull request #7332 from EightMonth/springboot3_sas
修复redis序列化认证信息问题
2024-10-11 16:33:11 +08:00
415307eb9f 修复redis序列化认证信息问题 2024-10-11 16:27:28 +08:00
48e20b2af5 Merge pull request #7329 from EightMonth/springboot3_sas
Revert "修复redis序列化认证信息问题"
2024-10-11 15:03:12 +08:00
b7924b9ca8 Revert "修复redis序列化认证信息问题"
This reverts commit 4aa88189ed.
2024-10-11 14:59:11 +08:00
a10a2e0a9d Merge pull request #7327 from EightMonth/springboot3_sas
修复redis序列化认证信息问题
2024-10-11 13:44:54 +08:00
4aa88189ed 修复redis序列化认证信息问题 2024-10-11 11:43:05 +08:00
fdb05443c2 【版本合并】springboot3 sas升级到3.7.1 2024-10-10 22:48:10 +08:00
d9e8bd2bc8 Merge pull request #7317 from EightMonth/springboot3
修改docker镜像base为JDK17
2024-10-09 16:08:38 +08:00
81eef5a838 修改docker镜像base为JDK17 2024-10-09 16:05:22 +08:00
f528f72903 升级仪表盘到最新版 2024-10-08 22:33:29 +08:00
918286c144 升级online模块和autopoi 2024-10-08 21:18:18 +08:00
512234a804 【版本合并】 branch 'origin/master' into springboot3 2024-10-08 19:30:14 +08:00
65d737db6d Merge remote-tracking branch 'origin/springboot3_sas' into springboot3_sas 2024-08-21 15:22:11 +08:00
f04f7f9abf Merge pull request #6899 from EightMonth/springboot3_sas
缩短token长度,适配主分支前端页面登录
2024-08-21 15:14:01 +08:00
935e118d15 sas分支前端同步master分支 2024-08-21 12:46:58 +08:00
e218367332 登出及强退逻辑完善 2024-08-21 11:22:13 +08:00
3a3f3cf367 升级积木报表到v1.8.0 2024-08-20 16:41:14 +08:00
0e762b4157 缩短token长度,适配主分支前端页面登录 2024-07-15 17:11:05 +08:00
f4712baa39 Merge pull request #6879 from EightMonth/springboot3_sas
修复访问仪表盘401
2024-07-10 16:23:48 +08:00
7d8b653d6e 修复访问仪表盘401 2024-07-10 16:22:10 +08:00
cf7f3f94be Merge pull request #6878 from EightMonth/springboot3_sas
修复访问仪表盘401问题
2024-07-10 16:03:19 +08:00
49f63b92ac 修复访问仪表盘401问题 2024-07-10 16:01:57 +08:00
5670a15b20 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2024-07-08 12:28:17 +08:00
cacc59b8fd 升级jimureport到最新版1.7.8 2024-07-08 12:18:09 +08:00
c744633139 升级jimureport到最新版1.7.7 2024-07-06 22:20:36 +08:00
9e9ef20b7c 改法不兼容,暂时先还原 2024-07-03 14:35:44 +08:00
0c034031d1 Merge branch 'springboot3_sas' of https://github.com/jeecgboot/jeecg-boot into springboot3_sas 2024-07-03 12:23:13 +08:00
491a038b5a 增加仪表盘的请求排除 2024-07-03 12:22:43 +08:00
8a4fcb0023 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2024-07-03 12:02:32 +08:00
0e4d304878 升级仪表盘 2024-07-03 11:56:53 +08:00
17a8964487 更新online模块为3.7.0最新依赖 2024-07-03 11:09:15 +08:00
e93dcc1a7e Merge pull request #6660 from EightMonth/springboot3_sas
修复redis反序列化时间问题
2024-06-27 18:33:16 +08:00
383cbf250f 修复redis反序列化时间问题 2024-06-27 18:09:54 +08:00
9fe1450ac9 Merge pull request #6646 from EightMonth/springboot3_sas
sas实现免登录注解
2024-06-25 17:38:48 +08:00
88b9b12998 sas实现免登录注解 2024-06-25 17:32:57 +08:00
9e25566271 【重要更新】vue3前端为了适配springboot3 sas分支(Spring Authorization Server)特定修改 2024-06-23 11:25:55 +08:00
8e54e06978 Merge remote-tracking branch 'origin/springboot3' into springboot3_sas 2024-06-23 11:23:34 +08:00
8ac6989d2c Merge remote-tracking branch 'origin/master' into springboot3 2024-06-23 11:22:06 +08:00
e5c082ae13 版本合并前端和后端放到一起 2024-06-23 10:35:53 +08:00
96ab98ac3e Merge remote-tracking branch 'origin/springboot3' into springboot3_sas
# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgCaptchaException.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/JwtToken.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/CustomShiroFilterFactoryBean.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/ResourceCheckFilter.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/Shiro.java
2024-06-23 10:29:34 +08:00
402ab0ffc4 补充合并丢失的代码 2024-06-23 10:27:33 +08:00
7778ede90e Merge remote-tracking branch 'origin/master' into springboot3
# Conflicts:
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger3Config.java
#	jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/UndertowCustomizer.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysFilesController.java
#	jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysFiles.java
2024-06-23 10:23:56 +08:00
1632c241ee 修改nacos命名空间springboot3的id也是springboot3,方便理解 2024-06-22 23:23:55 +08:00
06144206df 修改nacos命名空间springboot3的id也是springboot3,方便理解 2024-06-22 23:23:05 +08:00
e9d05b0e75 访问积木报表提示token无权限,原因TokenUtils.getLoginUser方法报错 2024-06-22 22:59:34 +08:00
6ade7e22f8 积木报表扩展实现类放开注释 2024-06-22 20:06:13 +08:00
43d47c08cb 【jeecgboot 3.7.0里程碑版本发布——合并springboot3sas分支】
Merge remote-tracking branch 'origin/springboot3' into springboot3_sas

# Conflicts:
#	.gitignore
#	db/tables_nacos.sql
#	jeecg-boot-base-core/pom.xml
#	jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java
#	jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
#	jeecg-boot-base-core/src/main/java/org/jeecg/config/DruidWallConfigRegister.java
#	jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java
#	jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java
#	jeecg-boot-base-core/src/main/java/org/jeecg/modules/base/service/impl/BaseCommonServiceImpl.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/LoginController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysAnnouncementController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDepartRoleController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysPermissionController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysTableWhiteListController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysTenantController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysAnnouncementServiceImpl.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysDepartServiceImpl.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysTenantPackServiceImpl.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysTenantServiceImpl.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysUserDepartServiceImpl.java
#	jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
#	jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
#	jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml
#	pom.xml
2024-06-22 19:55:35 +08:00
3d3b5850ad online的依赖升级个小版本号 2024-06-22 17:18:59 +08:00
816eeb9225 3.7.0版本合并springboot3分支
本次提交未升级【minidao、仪表盘、online的依赖】
2024-06-22 17:03:43 +08:00
0b42efbbbf Merge remote-tracking branch 'origin/springboot3' into springboot3 2024-06-22 14:58:29 +08:00
e616c5d8fe Merge pull request #6243 from EightMonth/springboot3_sas
sas兼容shiro处理
2024-06-20 16:09:29 +08:00
cddf23c787 sas 兼容shiro处理 2024-05-31 14:29:21 +08:00
70a37309dd sas兼容shiro处理 2024-05-22 18:08:31 +08:00
48555b5219 Merge pull request #6201 from EightMonth/springboot3_sas
增加bug修复注释
2024-04-30 14:25:10 +08:00
06d58f202f 增加bug修复注释 2024-04-30 14:07:59 +08:00
628870af9b Merge pull request #6199 from EightMonth/springboot3_sas
修复#6168\#6169\websocket连接问题
2024-04-30 14:02:22 +08:00
b8e0d4391d Merge pull request #6200 from EightMonth/springboot3
修复 #6169
2024-04-30 13:52:00 +08:00
72b34d082b 修复 #6169 2024-04-30 11:53:59 +08:00
b46a6438e6 修复#6168\#6169\websocket连接问题 2024-04-30 11:47:51 +08:00
5488f99723 Merge pull request #6194 from EightMonth/springboot3_sas
修复#6150,同时修复online表单无法加载问题
2024-04-29 17:34:06 +08:00
6bc1fe8d21 修复#6150,同时修复online表单无法加载问题 2024-04-29 17:27:25 +08:00
7cac16320c Merge pull request #6182 from EightMonth/springboot3_sas
修复Online同步数据库
2024-04-26 12:59:43 +08:00
24dbd1db39 修复Online同步数据库 2024-04-26 12:53:22 +08:00
7112649a21 Merge branch 'springboot3' of https://github.com/jeecgboot/jeecg-boot into springboot3 2024-04-26 11:06:35 +08:00
fbc312c35d Merge pull request #6173 from EightMonth/springboot3
修复#6127 #6130
2024-04-25 20:04:26 +08:00
b8162a4a6d 修复#6127 #6130 2024-04-25 16:01:58 +08:00
28404d2fd3 Merge pull request #6091 from EightMonth/springboot3
升级druid v1.2.22版本兼容处理
2024-04-08 15:26:49 +08:00
46b026b989 Merge pull request #6092 from EightMonth/springboot3_sas
升级druid v1.2.22版本兼容处理
2024-04-08 15:26:41 +08:00
94c45f5e0f 升级druid v1.2.22版本兼容处理 2024-04-08 14:11:39 +08:00
c92c9be49a 升级druid v1.2.22版本兼容处理 2024-04-08 14:04:07 +08:00
58e85e0569 Merge pull request #6081 from EightMonth/springboot3
升级druid1.2.22版本兼容处理
2024-04-03 16:35:30 +08:00
6fc34d8a39 升级druid1.2.22版本兼容处理 2024-04-03 16:18:31 +08:00
790df934b5 Merge branch 'springboot3' of https://github.com/jeecgboot/jeecg-boot into springboot3
 Conflicts:
	pom.xml
2024-03-30 23:39:54 +08:00
8aee4011a2 Merge pull request #6036 from EightMonth/springboot3
合并master变更,升级 3.6.3
2024-03-25 15:32:01 +08:00
8950e19d4e Merge pull request #6037 from EightMonth/springboot3_sas
修复 #5936
2024-03-25 15:00:06 +08:00
99eb88f71c 修复 #5936 2024-03-25 14:44:23 +08:00
6e0277c60a 升级druid版本,修复 #5936 2024-03-25 14:37:00 +08:00
e923654161 升级jimu版本至1.7.3,屏蔽flyway 2024-03-25 10:52:37 +08:00
06b41ae479 Merge branch 'master' into springboot3
# Conflicts:
#	db/tables_nacos.sql
#	jeecg-boot-base-core/pom.xml
#	jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger2Config.java
#	jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysFilesController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleIndexController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/entity/SysFiles.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysPermissionServiceImpl.java
#	jeecg-module-system/jeecg-system-start/pom.xml
#	jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
#	jeecg-server-cloud/jeecg-cloud-gateway/pom.xml
#	jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
#	pom.xml
2024-03-25 09:39:13 +08:00
824d7839d8 Merge pull request #6014 from EightMonth/springboot3_sas
支持手动生成token
2024-03-22 16:18:29 +08:00
c88f9d95d4 支持手动生成token 2024-03-19 17:16:12 +08:00
beb0bc2f64 Merge pull request #5995 from EightMonth/springboot3_sas
移除权限不足异常堆栈,权限加载加入缓存
2024-03-15 17:06:05 +08:00
f741db874c 移除权限不足异常堆栈,权限加载加入缓存 2024-03-15 13:55:58 +08:00
d684c09392 Merge pull request #5965 from EightMonth/springboot3_sas
新增token校验、客户端便捷工具类、修复登录缺乏租户信息、强退功能失效
2024-03-12 14:27:11 +08:00
364be22dd0 新增token校验、客户端便捷工具类、修复登录缺乏租户信息、强退功能失效 2024-03-08 16:30:23 +08:00
11af85d87a springboot3, 积木报表 聚合分组查询失败 #2398 2024-03-04 21:12:35 +08:00
4caff75cce Merge pull request #5935 from EightMonth/springboot3_config
修正spring boot3默认配置
2024-03-01 16:24:33 +08:00
20efa3bf9a Merge pull request #5934 from EightMonth/springboot3_sas
修正springboot3 sas默认配置
2024-03-01 16:23:13 +08:00
c7977dda3d 添加nacos sql自动创建nacos库 2024-03-01 16:17:30 +08:00
811861a957 添加nacos sql自动创建nacos库 2024-03-01 16:15:06 +08:00
24623ba4b0 梳理服务配置信息 2024-03-01 16:06:12 +08:00
7c68b46943 添加springboot3的配置变更 2024-03-01 16:04:57 +08:00
c27c5a9a9b 梳理服务配置信息 2024-03-01 15:49:28 +08:00
0ab280f812 添加springboot3的配置变更 2024-03-01 14:59:40 +08:00
c3066dac17 修改认证异常类拦截 2024-03-01 14:51:16 +08:00
b650d512b3 修复 #5903 ,完善在微服务的sas认证 2024-03-01 14:51:09 +08:00
7c34161369 删除无用文件 2024-02-29 17:41:13 +08:00
bc52aa918d gateway的配置改坏了,导致命名空间等不好使 2024-02-29 17:30:04 +08:00
925ec9447d Merge pull request #5819 from EightMonth/springboot3_sas
打通三方登录&移除shiro
2024-01-17 13:55:58 +08:00
411a73c1bf 移除shiro 2024-01-16 19:49:42 +08:00
84077e6e24 移除shiro 2024-01-16 19:49:15 +08:00
184cf97304 打通三方登录 2024-01-16 19:09:56 +08:00
9dfdd47b36 springboot3版本的仪表盘依赖有问题,升级一个版本 2024-01-12 11:32:26 +08:00
272a7540eb 仪表盘升级为springboot3版本 2024-01-12 11:00:51 +08:00
ad796f079f flywaydb兼容springboot3报错,先注释掉 2024-01-12 11:00:37 +08:00
e7e7716d05 Merge pull request #5782 from EightMonth/springboot3
同步主干分支版本代码,并升级jedis至3.8.0
2024-01-12 10:39:24 +08:00
5f425b49b2 Merge pull request #5761 from EightMonth/springboot3_sas
升级 spring authorization server
2024-01-12 09:40:48 +08:00
3ac8ee304a 完全替换shiro权限注解,新增手机登录、APP登录 2024-01-12 09:26:30 +08:00
c5d620d2b2 升级jedis版本至3.8.0 2024-01-08 13:54:04 +08:00
cdea05ebb0 Merge branch 'master' into springboot3
# Conflicts:
#	jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
#	jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/entity/JeecgDemo.java
#	jeecg-module-system/jeecg-system-biz/pom.xml
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysTableWhiteListController.java
#	jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/WechatVerifyController.java
#	jeecg-module-system/jeecg-system-start/pom.xml
#	jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml
#	pom.xml
2024-01-08 13:52:33 +08:00
ca9a433f3c Merge pull request #5765 from hoperunChen/springboot3-fix-#5723
fix issue for springboot3 #5723: 指定jaxb-runtime版本,添加修改记录
2024-01-04 21:41:11 +08:00
2be6052cd4 Merge pull request #5766 from hoperunChen/springboot3-fix-#5742
fix issue for springboot3 #5742: 修改代码生成时的schema注解参数
2024-01-04 21:40:53 +08:00
68ed67ee49 shiro 无法使用 spring boot 3.X 自带的jedis,降版本处理 Merge pull request #5767 from hoperunChen/springboot3-fix-#5741
fix issue for springboot3 #5741: shiro 无法使用 spring boot 3.X 自带的jedis,降版本处理
2024-01-04 21:40:31 +08:00
d5903ba52a Merge remote-tracking branch 'upstream/springboot3' into springboot3-fix-#5741 2024-01-04 20:37:37 +08:00
3ee635eddf fix issue for springboot3 #5723: 指定jaxb-runtime版本,添加修改记录 2024-01-04 20:32:26 +08:00
21bc68fb53 fix issue for springboot3 #5741: shiro 无法使用 spring boot 3.X 自带的jedis,降版本处理 2024-01-04 20:19:46 +08:00
0faac01bb7 sas升级脚本 2024-01-04 11:27:33 +08:00
74d88a8fcc springboot sas升级 2024-01-04 11:27:23 +08:00
f532e57862 解决升级到springboot3, 表单excel导出失败,找不到 javax/servlet/ServletOutputStream #5738 2024-01-03 17:26:41 +08:00
da08adbea1 fix issue for springboot3 #5742: 修改代码生成时的schema注解参数 2024-01-02 13:41:54 +08:00
46e3e62b59 fix issue for springboot3 #5741: shiro 无法使用 spring boot 3.X 自带的jedis,降版本处理 2024-01-02 13:31:57 +08:00
3656264f8a 提供积木报表fastjson2版本 2023-12-28 22:34:23 +08:00
3361d48cd4 Merge branch 'springboot3' of https://github.com/zhangdaiscott/jeecg-boot into springboot3 2023-12-28 11:03:26 +08:00
ed86ea3da1 默认不需要nosql支持包 2023-12-28 11:03:10 +08:00
3deb0e5487 Merge pull request #5730 from EightMonth/springboot3
修改自动生成接口文档范围
2023-12-28 10:49:10 +08:00
9e4792941e 修改自动秣接口文档范围 2023-12-28 10:43:58 +08:00
b5fd5fe782 Merge pull request #5716 from EightMonth/springboot3
升级fastjson至2.0.43,替换tomcat为undertow
2023-12-26 17:21:30 +08:00
33c0104a02 增加undertow配置到test\prod 环境 2023-12-26 17:11:54 +08:00
81ed5100af 补充注释 2023-12-26 16:42:24 +08:00
87f9dc0064 去除无意义内容 2023-12-26 15:17:50 +08:00
b311fedc6b 升级fastjson至2.0.43,替换tomcat为undertow 2023-12-26 15:03:35 +08:00
e321a0405f 升级aliyun.oss和minio的依赖 2023-12-26 10:01:57 +08:00
d8bc74794d 仪表盘也支持springboot3 2023-12-21 15:31:12 +08:00
732f05dc74 提供springboot3版本的online依赖支持 2023-12-21 14:57:14 +08:00
6ce92798c6 Merge pull request #5704 from EightMonth/springboot3
升级jeecg 3.6.1版本
2023-12-21 11:46:59 +08:00
f4454e9348 Merge branch 'springboot3' into springboot3 2023-12-21 09:52:14 +08:00
d9134ae0c8 Update WechatVerifyController.java 2023-12-21 09:46:52 +08:00
25180e41c8 更新minidao支持springboot3版本 2023-12-21 09:29:16 +08:00
a99e3f2268 更新积木报表支持springboot3版本 2023-12-21 09:28:00 +08:00
d27c354bf1 修改错误的配置 2023-12-21 09:26:40 +08:00
d818b1dd9d 更新jeecg-boot-starter3依赖 2023-12-21 09:26:39 +08:00
bcdbec0091 更新jeecg-boot-starter3依赖 2023-12-21 09:26:39 +08:00
098bb12b9e 更改jeecg-boot-starter3依赖 2023-12-21 09:26:39 +08:00
4a6c750b19 为注释内容添加注释原因 2023-12-21 09:26:39 +08:00
d396e5304a Update pom.xml 2023-12-21 09:26:38 +08:00
9bed25be8c spring3 2023-12-21 09:26:30 +08:00
7109b42092 Merge pull request #5698 from EightMonth/springboot3
更新积木报表、Minidao支持Springboot3版本
2023-12-20 10:10:51 +08:00
1667b14194 更新minidao支持springboot3版本 2023-12-20 10:00:14 +08:00
e9514873d2 更新积木报表支持springboot3版本 2023-12-19 14:31:17 +08:00
0ee090664e 修改错误的配置 2023-11-13 20:03:53 +08:00
4a9eda4ab0 Merge pull request #5567 from EightMonth/spring3
更新jeecg-boot-starter3依赖
2023-11-13 18:45:02 +08:00
2416c8b251 更新jeecg-boot-starter3依赖 2023-11-13 16:19:22 +08:00
5b056f9dd6 更新jeecg-boot-starter3依赖 2023-11-13 16:12:46 +08:00
a93998dc56 Merge pull request #5566 from EightMonth/spring3
更改jeecg-boot-starter3依赖
2023-11-13 15:43:21 +08:00
268c27a782 更改jeecg-boot-starter3依赖 2023-11-13 15:34:25 +08:00
23ace2712a Merge pull request #5563 from EightMonth/spring3
Spring Boot3 & JDK 17
2023-11-13 09:49:04 +08:00
157feeb925 为注释内容添加注释原因 2023-11-06 14:16:02 +08:00
4e25d4162f Update pom.xml 2023-11-06 14:11:23 +08:00
47a68f31e1 spring3 2023-11-06 12:41:57 +08:00
306 changed files with 7299 additions and 4043 deletions

View File

@ -9,6 +9,7 @@ assignees: getActivity
##### 版本号:
##### 分支:

View File

@ -8,6 +8,7 @@ assignees: getActivity
##### 版本号:
##### 分支:

3
.gitignore vendored
View File

@ -13,3 +13,6 @@ os_del.cmd
os_del_doc.cmd
.svn
derby.log
*.log
.cursor
.history

View File

@ -12,7 +12,7 @@ Current version: 3.8.3 (Release date: 2025-10-09)
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-guojusoft-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/version-3.8.3-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![](https://img.shields.io/badge/version-3.8.2-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)

View File

@ -4,7 +4,6 @@ JeecgBoot AI低代码平台
当前最新版本: 3.8.3发布日期2025-10-09
> 重要提醒: v3.8.3 是 SpringBoot2 的最终版本,后续将不再维护。建议大家尽快升级至基于 SpringBoot3版本以获得更好的性能和支持。
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/jeecgboot/JeecgBoot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](https://jeecg.com)
@ -20,16 +19,18 @@ JeecgBoot AI低代码平台
<h3 align="center">企业级AI低代码平台</h3>
JeecgBoot是一款企业级低代码平台集成了AI应用平台功能旨在帮助开发者快速实现低代码开发和构建、部署个性化的 AI 应用。
前后端分离架构Ant Design4、Vue3SpringBoot2/3SpringCloud AlibabaMybatis-plusShiro/SpringAuthorizationServer,强大代码生成器前后端代码一键生成,无需写任何代码;提供强大的报表和大屏工具,满足企业级数据产品需求!
引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE 帮助Java项目解决80%重复工作,让开发更多关注业务,提高效率节省成本,同时又不失灵活性低代码能力Online表单、表单设计、流程设计、Online报表、大屏/仪表盘设计、报表设计; AI应用平台功能AI知识库问答、AI模型管理、AI流程编排、AI聊天等支持含ChatGPT、DeepSeek、Ollama等多种AI大模型
JeecgBoot 是一款基于BPM流程和代码生成的AI低代码平台助力企业快速实现低代码开发和构建AI应用。
采用前后端分离架构Ant Design&Vue3SpringBoot3SpringCloud AlibabaMybatis-plus,强大代码生成器实现前后端一键生成,无需手写代码。
平台引领AI低代码开发模式AI生成→在线编码→代码生成→手工合并,解决Java项目80%重复工作,提升效率节省成本,兼顾灵活性。
具备强大且颗粒化的权限控制支持按钮权限和数据权限设置满足大型业务系统需求。功能涵盖在线表单、表单设计、流程设计、门户设计、报表与大屏设计、OA办公、AI应用、AI知识库、大模型管理、AI流程编排、AI聊天支持ChatGPT、DeepSeek、Ollama等多种AI大模型。
`AI赋能报表:` 积木报表是一款自主研发的强大开源企业级Web报表与大屏工具。它通过零编码的拖拽式操作赋能用户如同搭积木般轻松构建各类复杂报表和数据大屏全面满足企业数据可视化与分析需求助力企业级数据产品的高效打造与应用。
`AI赋能低代码:` 提供一套成熟AI应用平台功能:包含AI应用管理、AI模型管理、AI对话助手、AI知识库问答、AI流程编排、AI流程设计器AI建表等功能; 支持各种AI大模型ChatGPT、DeepSeek、Ollama、智普、千问等.
`AI赋能低代码:` 提供完善成熟AI应用平台,涵盖AI应用管理、AI模型管理、智能对话助手、知识库问答、流程编排设计器AI建表等多项功能。平台兼容多种主流大模型,包括ChatGPT、DeepSeek、Ollama、智普、千问等助力企业高效构建智能化应用推动低代码开发与AI深度融合。
`JEECG宗旨是:` 简单功能由OnlineCoding零代码搭建做到`零代码开发`复杂功能代码生成器生成进行手工Merge 实现`低代码开发`,既保证了`智能`又兼顾`灵活`,解决了当前低代码产品普遍不灵活的弊端!
`JEECG宗旨是:` JEECG旨在通过OnlineCoding平台实现简单功能的零代码快速搭建,同时针对复杂功能采用代码生成器生成代码并手工合并,打造智能且灵活的低代码开发模式,有效解决了当前低代码产品普遍缺乏灵活性的问题,提升开发效率的同时兼顾系统的扩展性和定制化能力。
`JEECG业务流程:` 采用工作流来实现、扩展任务接口供开发编写业务逻辑,表单提供多种解决方案: 表单设计器、online配置表单编码表单。同时实现了流程与表单的分离设计(松耦合)、并支持任务节点灵活配置,既保证了公司流程的保密性,又减少了开发人员的工作量。
`JEECG业务流程:` JEECG业务流程采用BPM工作流引擎实现业务审批扩展任务接口供开发人员编写业务逻辑,表单提供表单设计器、在线配置表单编码表单等多种解决方案。通过流程与表单的分离设计(松耦合)任务节点灵活配置,既保障了企业流程的安全性与保密性,又大幅降低了开发人员的工作量。
@ -37,8 +38,8 @@ JeecgBoot是一款企业级低代码平台集成了AI应用平台功能旨在
适用项目
-----------------------------------
JeecgBoot低代码平台,可以应用在任何J2EE项目开发,支持信创国产化。尤其适合SAAS项目、企业信息管理系统MIS、内部办公系统OA、企业资源计划系统ERP、客户关系管理系统CRMAI知识库等其半智能手工Merge开发式,可显著提高开发效率70%以上,极大降低开发成本
又是一个全栈式 AI 开发平台,快速帮助企业构建和部署个性化的 AI 应用。
JeecgBoot低代码平台兼容所有J2EE项目开发支持信创国产化,特别适用于SAAS、企业信息管理系统MIS、内部办公系统OA、企业资源计划系统ERP、客户关系管理系统CRMAI知识库等场景。其半智能手工Merge开发式,可显著提升70%以上的开发效率极大降低开发成本。同时JeecgBoot还是一款全栈式AI开发平台助力企业快速构建和部署个性化AI应用。
**信创兼容说明**
- 操作系统:国产麒麟、银河麒麟等国产系统几乎都是基于 Linux 内核,因此它们具有良好的兼容性。
@ -49,13 +50,13 @@ JeecgBoot低代码平台可以应用在任何J2EE项目的开发中支持
版本说明
-----------------------------------
|下载 | JDK17 + SpringBoot3.3 + Shiro |JDK17 + SpringBoot3.3+ SpringAuthorizationServer | JDK17/JDK8 + SpringBoot2.7 |
|------|----------------------------------------------------|-----------------------------------------------------------------------------|--------------------------------------------|
| Github | [`springboot3`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3) | [`springboot3_sas`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3_sas) 分支 |[`master`](https://github.com/jeecgboot/JeecgBoot) 分支|
| Gitee | [`springboot3`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3/) | [`springboot3_sas`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3_sas) 分支 |[`master`](https://gitee.com/jeecg/JeecgBoot) 分支 |
|下载 | SpringBoot3.5 + Shiro |SpringBoot3.5+ SpringAuthorizationServer | SpringBoot3.5 + Sa-Token | SpringBoot2.7(JDK17/JDK8) |
|------|----------------|----------------------------|-------------------|--------------------------------------------|
| Github | [`springboot3`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3) | [`springboot3_sas`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3_sas) 分支 | [`springboot3-satoken`](https://github.com/jeecgboot/JeecgBoot/tree/springboot3-satoken) 分支|[`master`](https://github.com/jeecgboot/JeecgBoot) 分支|
| Gitee | [`springboot3`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3/) | [`springboot3_sas`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3_sas) 分支| [`springboot3-satoken`](https://gitee.com/jeecg/JeecgBoot/tree/springboot3-satoken) 分支|[`master`](https://gitee.com/jeecg/JeecgBoot) 分支 |
- `jeecg-boot` 是后端JAVA源码项目支持单体和微服务切换.
- `jeecg-boot` 是后端JAVA源码项目Springboot3+SpringCloudAlibaba(支持单体和微服务切换).
- `jeecgboot-vue3` 是前端VUE3源码项目vue3+vite6+ts最新技术栈.
- `JeecgUniapp` 是[配套APP框架](https://github.com/jeecgboot/JeecgUniapp) 适配多个终端支持APP、小程序、H5、鸿蒙、鸿蒙Next.
- 参考 [文档](https://help.jeecg.com/ui/2dev/mini) 可以删除不需要的demo制作一个精简版本
@ -82,7 +83,7 @@ JeecgBoot低代码平台可以应用在任何J2EE项目的开发中支持
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
- 在线演示: [平台演示](https://boot3.jeecg.com) | [APP演示](https://jeecg.com/appIndex)
- 入门指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [代码生成使用](https://help.jeecg.com/java/codegen/online) | [开发文档](https://help.jeecg.com) | [AI应用手册](https://help.jeecg.com/aigc) | [视频教程](http://jeecg.com/doc/video)
- AI编程 [JEECG低代码+AI编程Cursor+GitHub Copilot实现高效编程](https://www.bilibili.com/video/BV11XyaBVEoH)
- AI编程实战视频 [JEECG低代码Cursor+GitHub Copilot实现AI高效编程实战](https://www.bilibili.com/video/BV11XyaBVEoH)
- 技术支持: [反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md) | [低代码体验一分钟](https://jeecg.blog.csdn.net/article/details/106079007)
- QQ交流群 964611995、⑩716488839(满)、⑨808791225(满)、其他(满)
@ -104,7 +105,7 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块是一套类
为什么选择JeecgBoot?
-----------------------------------
- 1.采用最新主流前后分离框架Spring Boot3 + MyBatis + Shiro//SpringAuthorizationServer + Ant Design4 + Vue3容易上手代码生成器依赖性低灵活的扩展能力可快速实现二次开发。
- 1.采用最新主流前后分离框架Spring Boot3 + MyBatis + Shiro/SpringAuthorizationServer + Ant Design4 + Vue3容易上手代码生成器依赖性低灵活的扩展能力可快速实现二次开发。
- 2.前端大版本换代,最新版采用 Vue3.0 + TypeScript + Vite6 + Ant Design Vue4 等新技术方案。
- 3.支持微服务Spring Cloud AlibabaNacos、Gateway、Sentinel、Skywalking提供简易机制支持单体和微服务自由切换这样可以满足各类项目需求
- 4.开发效率高支持在线建表和AI建表提供强大代码生成器单表、树列表、一对多、一对一等数据模型增删改查功能一键生成菜单配置直接使用。
@ -158,7 +159,7 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块是一套类
- 前端环境要求Node.js要求`Node 20+` 版本以上、pnpm 要求`9+` 版本以上
> Vite 不再支持已结束生命周期EOL的 Node.js 18。现在需要使用 Node.js 20.19+ 或 22.12+
` ( Vite 不再支持已结束生命周期EOL的 Node.js 18。现在需要使用 Node.js 20.19+ 或 22.12+)`
- 依赖管理node、npm、pnpm
- 前端IDE建议IDEA、WebStorm、Vscode
@ -169,16 +170,16 @@ JeecgBoot平台提供了一套完善的AI应用管理系统模块是一套类
#### 后端
- IDE建议 IDEA (必须安装lombok插件 )
- 语言Java 默认jdk17(支持jdk8、jdk21)
- 语言Java 默认jdk17(jdk21、jdk24)
- 依赖管理Maven
- 基础框架Spring Boot 3.5.5/2.7.18
- 微服务框架: Spring Cloud Alibaba 2021.0.6.2
- 持久层框架MybatisPlus 3.5.3.2
- 基础框架Spring Boot 3.5.5
- 微服务框架: Spring Cloud Alibaba 2023.0.3.3
- 持久层框架MybatisPlus 3.5.12
- 报表工具: JimuReport 2.1.3
- 安全框架Apache Shiro/SpringAuthorizationServerJwt 4.5.0
- 安全框架Apache Shiro 2.0.4Jwt 4.5.0
- 微服务技术栈Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
- 数据库连接池阿里巴巴Druid 1.1.24
- AI大模型支持 `ChatGPT` `DeepSeek`切换
- 数据库连接池阿里巴巴Druid 1.2.24
- AI大模型支持 `ChatGPT` `DeepSeek` `千问`等各种常规模式
- 日志打印logback
- 缓存Redis
- 其他autopoi, fastjsonpoiSwagger-uiquartz, lombok简化代码等。
@ -513,4 +514,4 @@ AI写文章
如果觉得还不错,请作者喝杯咖啡吧 ☺
![](https://static.oschina.net/uploads/img/201903/08155608_0EFX.png)
![](https://static.oschina.net/uploads/img/201903/08155608_0EFX.png)

View File

@ -125,7 +125,7 @@ services:
hostname: jeecg-boot-sentinel
networks:
- jeecg-boot
jeecg-boot-xxljob:
build:
context: ./jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob
@ -135,7 +135,7 @@ services:
hostname: jeecg-boot-xxljob
networks:
- jeecg-boot
jeecg-vue:
build:
context: ./jeecgboot-vue3

View File

@ -13,3 +13,4 @@ os_del.cmd
os_del_doc.cmd
.svn
derby.log
*.log

View File

@ -2,7 +2,7 @@
JeecgBoot 低代码开发平台
===============
当前最新版本: 3.8.3发布日期2025-09-22
当前最新版本: 3.8.3发布日期2025-10-09
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
@ -16,43 +16,127 @@ JeecgBoot 低代码开发平台
项目介绍
-----------------------------------
<h3 align="center">Java Low Code Platform for Enterprise web applications</h3>
<h3 align="center">企业级AI低代码平台</h3>
JeecgBoot 是一款基于BPM流程和代码生成的AI低代码平台助力企业快速实现低代码开发和构建AI应用。
采用前后端分离架构Ant Design&Vue3SpringBoot3SpringCloud AlibabaMybatis-plus强大代码生成器实现前后端一键生成无需手写代码。
平台引领AI低代码开发模式AI生成→在线编码→代码生成→手工合并解决Java项目80%重复工作,提升效率,节省成本,兼顾灵活性。
具备强大且颗粒化的权限控制支持按钮权限和数据权限设置满足大型业务系统需求。功能涵盖在线表单、表单设计、流程设计、门户设计、报表与大屏设计、OA办公、AI应用、AI知识库、大模型管理、AI流程编排、AI聊天支持ChatGPT、DeepSeek、Ollama等多种AI大模型。
`AI赋能报表:` 积木报表是一款自主研发的强大开源企业级Web报表与大屏工具。它通过零编码的拖拽式操作赋能用户如同搭积木般轻松构建各类复杂报表和数据大屏全面满足企业数据可视化与分析需求助力企业级数据产品的高效打造与应用。
`AI赋能低代码:` 提供完善成熟的AI应用平台涵盖AI应用管理、AI模型管理、智能对话助手、知识库问答、流程编排与设计器、AI建表等多项功能。平台兼容多种主流大模型包括ChatGPT、DeepSeek、Ollama、智普、千问等助力企业高效构建智能化应用推动低代码开发与AI深度融合。
`JEECG宗旨是:` JEECG旨在通过OnlineCoding平台实现简单功能的零代码快速搭建同时针对复杂功能采用代码生成器生成代码并手工合并打造智能且灵活的低代码开发模式有效解决了当前低代码产品普遍缺乏灵活性的问题提升开发效率的同时兼顾系统的扩展性和定制化能力。
`JEECG业务流程:` JEECG业务流程采用BPM工作流引擎实现业务审批扩展任务接口供开发人员编写业务逻辑表单提供表单设计器、在线配置表单和编码表单等多种解决方案。通过流程与表单的分离设计松耦合及任务节点的灵活配置既保障了企业流程的安全性与保密性又大幅降低了开发人员的工作量。
适用项目
-----------------------------------
JeecgBoot低代码平台兼容所有J2EE项目开发支持信创国产化特别适用于SAAS、企业信息管理系统MIS、内部办公系统OA、企业资源计划系统ERP、客户关系管理系统CRM及AI知识库等场景。其半智能手工Merge开发模式可显著提升70%以上的开发效率极大降低开发成本。同时JeecgBoot还是一款全栈式AI开发平台助力企业快速构建和部署个性化AI应用。。
**信创兼容说明**
- 操作系统:国产麒麟、银河麒麟等国产系统几乎都是基于 Linux 内核,因此它们具有良好的兼容性。
- 数据库达梦、人大金仓、TiDB
- 中间件:东方通 TongWeb、TongRDS宝兰德 AppServer、CacheDB, [信创配置文档](https://help.jeecg.com/java/tongweb-deploy/)
JeecgBoot 是一款基于代码生成器的`低代码开发平台`!前后端分离架构 SpringBoot2.x和3.xSpringCloudAnt Design Vue3Mybatis-plusShiroJWT支持微服务。强大的代码生成器让前后端代码一键生成实现低代码开发! JeecgBoot 引领新的低代码开发模式(OnlineCoding-> 代码生成器-> 手工MERGE) 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省研发成本,同时又不失灵活性!
#### 项目说明
| 项目名 | 说明 |
|--------------------|------------------------|
| `jeecg-boot` | 后端源码JAVASpringBoot微服务架构 |
| `jeecgboot-vue3` | 前端源码VUE3vue3+vite5+ts最新技术栈 |
技术文档
-----------------------------------
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
- 新手指南: [快速入门](http://www.jeecg.com/doc/quickstart)
- QQ交流群 964611995、⑩716488839(满)、⑨808791225(满)、其他(满)
- 在线演示 [在线演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex)
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
| 项目名 | 说明 |
|--------------------|------------------------------------|
| `jeecg-boot` | 后端源码JAVASpringBoot3微服务架构) |
| `jeecgboot-vue3` | 前端源码VUE3vue3+vite6+antd4+ts最新技术栈 |
启动项目
-----------------------------------
- [IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup)
- [Docker一键启动前后端](https://help.jeecg.com/java/docker/quick)
> 默认账号密码: admin/123456
- [开发环境搭建](https://help.jeecg.com/java/setup/tools)
- [IDEA启动前后端(单体模式)](https://help.jeecg.com/java/setup/idea/startup)
- [Docker一键启动(单体模式)](https://help.jeecg.com/java/docker/quick)
- [IDEA启动前后端(微服务方式)](https://help.jeecg.com/java/springcloud/switchcloud/monomer)
- [Docker一键启动(微服务方式)](https://help.jeecg.com/java/docker/quickcloud)
微服务启动
技术文档
-----------------------------------
- [单体快速切换微服务](https://help.jeecg.com/java/springcloud/switchcloud/monomer)
- [Docker启动微服务后台](https://help.jeecg.com/java/docker/springcloud)
- 官方网站: [http://www.jeecg.com](http://www.jeecg.com)
- 在线演示: [平台演示](https://boot3.jeecg.com) | [APP演示](https://jeecg.com/appIndex)
- 入门指南: [快速入门](http://www.jeecg.com/doc/quickstart) | [代码生成使用](https://help.jeecg.com/java/codegen/online) | [开发文档](https://help.jeecg.com) | [AI应用手册](https://help.jeecg.com/aigc) | [视频教程](http://jeecg.com/doc/video)
- 技术支持: [反馈问题](https://github.com/jeecgboot/JeecgBoot/issues/new?template=bug_report.md) | [低代码体验一分钟](https://jeecg.blog.csdn.net/article/details/106079007)
- QQ交流群 964611995、⑩716488839(满)、⑨808791225(满)、其他(满)
AI 应用平台介绍
-----------------------------------
一个全栈式 AI 开发平台,旨在帮助开发者快速构建和部署个性化的 AI 应用。
JeecgBoot平台提供了一套完善的AI应用管理系统模块是一套类似`Dify``AIGC应用开发平台`+`知识库问答`是一款基于LLM大语言模型AI应用平台和 RAG 的知识库问答系统。
其直观的界面结合了 AI 流程编排、RAG 管道、知识库管理、模型管理、对接向量库、实时运行可观察等让您可以快速从原型到生产拥有AI服务能力。
- [详细专题介绍,请点击查看](README-AI.md)
- AI视频介绍
[![](https://jeecgos.oss-cn-beijing.aliyuncs.com/files/jeecg_aivideo.png)](https://www.bilibili.com/video/BV1zmd7YFE4w)
为什么选择JeecgBoot?
-----------------------------------
- 1.采用最新主流前后分离框架Spring Boot3 + MyBatis + Shiro/SpringAuthorizationServer + Ant Design4 + Vue3容易上手代码生成器依赖性低灵活的扩展能力可快速实现二次开发。
- 2.前端大版本换代,最新版采用 Vue3.0 + TypeScript + Vite6 + Ant Design Vue4 等新技术方案。
- 3.支持微服务Spring Cloud AlibabaNacos、Gateway、Sentinel、Skywalking提供简易机制支持单体和微服务自由切换这样可以满足各类项目需求
- 4.开发效率高支持在线建表和AI建表提供强大代码生成器单表、树列表、一对多、一对一等数据模型增删改查功能一键生成菜单配置直接使用。
- 5.代码生成器提供强大模板机制,支持自定义模板,目前提供四套风格模板(单表两套、树模型一套、一对多三套)。
- 6.提供强大的报表和大屏可视化工具,支持丰富的数据源连接,能够通过拖拉拽方式快速制作报表、大屏和门户设计;支持多种图表类型:柱形图、折线图、散点图、饼图、环形图、面积图、漏斗图、进度图、仪表盘、雷达图、地图等。
- 7.低代码能力在线表单无需编码通过在线配置表单实现表单的增删改查支持单表、树、一对多、一对一等模型实现人人皆可编码在线配置零代码开发、所见即所得支持23种类控件。
- 8.低代码能力:在线报表、在线图表(无需编码,通过在线配置方式,实现数据报表和图形报表,可以快速抽取数据,减轻开发压力,实现人人皆可编码)。
- 9.Online支持在线增强开发提供在线代码编辑器支持代码高亮、代码提示等功能支持多种语言Java、SQL、JavaScript等
- 10.封装完善的用户、角色、菜单、组织机构、数据字典、在线定时任务等基础功能,支持访问授权、按钮权限、数据权限等功能。
- 11.前端UI提供丰富的组件库支持各种常用组件如表格、树形控件、下拉框、日期选择器等满足各种复杂的业务需求 [UI组件库文档](https://help.jeecg.com/category/ui%E7%BB%84%E4%BB%B6%E5%BA%93)。
- 12.提供APP配套框架一份多代码多终端适配一份代码多终端适配小程序、H5、安卓、iOS、鸿蒙Next。
- 13.新版APP框架采用Uniapp、Vue3.0、Vite、Wot-design-uni、TypeScript等最新技术栈包括二次封装组件、路由拦截、请求拦截等功能。实现了与JeecgBoot完美对接目前已经实现登录、用户信息、通讯录、公告、移动首页、九宫格、聊天、Online表单、仪表盘等功能提供了丰富的组件。
- 14.提供了一套成熟的AI应用平台功能从AI模型、知识库到AI应用搭建助力企业快速落地AI服务加速智能化升级。
- 15.AI能力目前JeecgBoot支持AI大模型chatgpt和deepseek现在最新版默认使用deepseek速度更快质量更高。目前提供了AI对话助手、AI知识库、AI应用、AI建表、AI报表等功能。
- 16.提供新行编辑表格JVXETable轻松满足各种复杂ERP布局拥有更高的性能、更灵活的扩展、更强大的功能。
- 17.平台首页风格,提供多种组合模式,支持自定义风格;支持门户设计,支持自定义首页。
- 18.常用共通封装各种工具类定时任务、短信接口、邮件发送、Excel导入导出等基本满足80%项目需求。
- 19.简易Excel导入导出支持单表导出和一对多表模式导出生成的代码自带导入导出功能。
- 20.集成智能报表工具报表打印、图像报表和数据导出非常方便可极其方便地生成PDF、Excel、Word等报表。
- 21.采用前后分离技术页面UI风格精美针对常用组件做了封装时间、行表格控件、截取显示控件、报表组件、编辑器等。
- 22.查询过滤器查询功能自动生成后台动态拼SQL追加查询条件支持多种匹配方式全匹配/模糊查询/包含查询/不匹配查询)。
- 23.数据权限(精细化数据权限控制,控制到行级、列表级、表单字段级,实现不同人看不同数据,不同人对同一个页面操作不同字段)。
- 24.接口安全机制可细化控制接口授权非常简便实现不同客户端只看自己数据等控制也提供了基于AK和SK认证鉴权的OpenAPI功能。
- 25.活跃的社区支持;近年来,随着网络威胁的日益增加,团队在安全和漏洞管理方面积累了丰富的经验,能够为企业提供全面的安全解决方案。
- 26.权限控制采用RBACRole-Based Access Control基于角色的访问控制
- 27.页面校验自动生成(必须输入、数字校验、金额校验、时间空间等)。
- 28.支持SaaS服务模式提供SaaS多租户架构方案。
- 29.分布式文件服务集成MinIO、阿里OSS等优秀的第三方提供便捷的文件上传与管理同时也支持本地存储。
- 30.主流数据库兼容一套代码完全兼容MySQL、PostgreSQL、Oracle、SQL Server、MariaDB、达梦、人大金仓等主流数据库。
- 31.集成工作流Flowable并实现了只需在页面配置流程转向可极大简化BPM工作流的开发用BPM的流程设计器画出了流程走向一个工作流基本就完成了只需写很少量的Java代码。
- 32.低代码能力在线流程设计采用开源Flowable流程引擎实现在线画流程、自定义表单、表单挂靠、业务流转。
- 33.多数据源:极其简易的使用方式,在线配置数据源配置,便捷地从其他数据抓取数据。
- 34.提供单点登录CAS集成方案项目中已经提供完善的对接代码。
- 35.低代码能力表单设计器支持用户自定义表单布局支持单表、一对多表单支持select、radio、checkbox、textarea、date、popup、列表、宏等控件。
- 36.专业接口对接机制统一采用RESTful接口方式集成Swagger-UI在线接口文档JWT token安全验证方便客户端对接。
- 37.高级组合查询功能,在线配置支持主子表关联查询,可保存查询历史。
- 38.提供各种系统监控实时跟踪系统运行情况监控Redis、Tomcat、JVM、服务器信息、请求追踪、SQL监控
- 39.消息中心支持短信、邮件、微信推送等集成WebSocket消息通知机制。
- 40.支持多语言,提供国际化方案。
- 41.数据变更记录日志,可记录数据每次变更内容,通过版本对比功能查看历史变化。
- 42.提供简单易用的打印插件支持谷歌、火狐、IE11+等各种浏览器。
- 43.后端采用Maven分模块开发方式前端支持菜单动态路由。
- 44.提供丰富的示例代码,涵盖了常用的业务场景,便于学习和参考。
技术架构:
@ -61,28 +145,33 @@ JeecgBoot 是一款基于代码生成器的`低代码开发平台`!前后端
#### 后端
- IDE建议 IDEA (必须安装lombok插件 )
- 语言Java 8+ (支持17)
- 语言Java 默认jdk17(jdk21、jdk24)
- 依赖管理Maven
- 基础框架Spring Boot 2.7.18
- 微服务框架: Spring Cloud Alibaba 2021.0.1.0
- 持久层框架MybatisPlus 3.5.3.2
- 报表工具: JimuReport 1.9.4
- 安全框架Apache Shiro 1.12.0Jwt 3.11.0
- 基础框架Spring Boot 3.5.5
- 微服务框架: Spring Cloud Alibaba 2023.0.3.3
- 持久层框架MybatisPlus 3.5.12
- 报表工具: JimuReport 2.1.3
- 安全框架Apache Shiro 2.0.4Jwt 4.5.0
- 微服务技术栈Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
- 数据库连接池阿里巴巴Druid 1.1.24
- 数据库连接池阿里巴巴Druid 1.2.24
- AI大模型支持 `ChatGPT` `DeepSeek` `千问`等各种常规模式
- 日志打印logback
- 缓存Redis
- 其他autopoi, fastjsonpoiSwagger-uiquartz, lombok简化代码等。
- 默认数据库脚本:MySQL5.7+
- 默认提供MySQL5.7+数据库脚本
- [其他数据库,需要自己转](https://my.oschina.net/jeecg/blog/4905722)
#### 前端
- 前端IDE建议WebStorm、Vscode
- 采用 Vue3.0+TypeScript+Vite+Ant-Design-Vue等新技术方案包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能
- 最新技术栈Vue3.0 + TypeScript + Vite5 + ant-design-vue4 + pinia + echarts + unocss + vxe-table + qiankun + es6
- 前端环境要求Node.js要求`Node 20+` 版本以上、pnpm 要求`9+` 版本以上
` ( Vite 不再支持已结束生命周期EOL的 Node.js 18。现在需要使用 Node.js 20.19+ 或 22.12+)`
- 依赖管理node、npm、pnpm
- 前端IDE建议IDEA、WebStorm、Vscode
- 采用 Vue3.0+TypeScript+Vite6+Ant-Design-Vue4等新技术方案包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能
- 最新技术栈Vue3.0 + TypeScript + Vite6 + ant-design-vue4 + pinia + echarts + unocss + vxe-table + qiankun + es6

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
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]}');

View File

@ -3,6 +3,7 @@
> JeecgBoot属于平台级产品每次升级改动较大目前做不到平滑升级。
### 增量升级方案
#### 1.代码合并
本地通过svn或git做好主干在分支上做业务开发jeecg每次版本发布可以手工覆盖主干的代码对比合并代码
@ -11,5 +12,12 @@
- 其他库请手工执行SQL, 目录: `jeecg-module-system\jeecg-system-start\src\main\resources\flyway\sql\mysql`
> 注意: 升级sql只提供mysql版本如果有权限升级, 还需要手工角色授权,退出重新登录才好使。
#### 3.兼容问题
#### 3.其他数据库脚本说明
原先官方默认提供oracle和SqlServer的脚本但是维护成本太高未提供脚本的数据库可以参考下面的文档自己转
https://my.oschina.net/jeecg/blog/4905722
注意定时任务的表qrtz_*,需要删掉用原始的脚本重新执行一下)
quartz-2.2.3-distribution.tar.gz放到百度网盘中大家自己下载执行所需数据库脚本
https://pan.baidu.com/s/1WrmZdUuAPg3iBwJ-LoHWyg?pwd=8mdz
#### 4.兼容问题
每次发版,会针对不兼容地方重点说明。

View File

@ -2,7 +2,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-parent</artifactId>
<version>3.8.3</version>
</parent>
@ -42,7 +42,7 @@
<dependencies>
<!--jeecg-tools-->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-common</artifactId>
</dependency>
<!--集成springmvc框架并实现自动配置 -->
@ -98,7 +98,7 @@
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
@ -107,22 +107,22 @@
<version>${mybatis-plus.version}</version>
</dependency>
<!-- minidao -->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>minidao-spring-boot-starter-jsqlparser-4.9</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>minidao-spring-boot-starter-jsqlparser-4.9</artifactId>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- 动态数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic-datasource-spring-boot-starter.version}</version>
</dependency>
@ -187,35 +187,38 @@
<version>${java-jwt.version}</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>${shiro.version}</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
<!-- shiro-redis -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>${shiro-redis.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
<exclusion>
<artifactId>checkstyle</artifactId>
<groupId>com.puppycrawl.tools</groupId>
</exclusion>
</exclusions>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- 添加spring security cas支持 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
</dependency>
<!-- knife4j -->
<!-- <dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j-spring-boot-starter.version}</version>
</dependency>-->
<!-- knife4j 升级springboot3.4.5报错 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<artifactId>knife4j-openapi3-ui</artifactId>
<version>${knife4j-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!-- 代码生成器 -->
<!-- 如下载失败,请参考此文档 https://help.jeecg.com/java/setup/maven.html -->
@ -237,7 +240,7 @@
<!-- AutoPoi Excel工具类-->
<dependency>
<groupId>org.jeecgframework</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>autopoi-web</artifactId>
</dependency>
<dependency>
@ -275,6 +278,16 @@
<dependency>
<groupId>com.xkcoding.justauth</groupId>
<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>
<groupId>com.squareup.okhttp3</groupId>
@ -300,8 +313,8 @@
</dependency>
<!-- chatgpt -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@ -0,0 +1,21 @@
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();
}
};
}
}

View File

@ -0,0 +1,14 @@
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();
}
}

View File

@ -1,5 +1,6 @@
package org.jeecg.common.api;
import com.alibaba.fastjson.JSONObject;
import org.jeecg.common.api.dto.AiragFlowDTO;
import org.jeecg.common.system.vo.*;
@ -65,6 +66,13 @@ public interface CommonAPI {
*/
public String getUserIdByName(String username);
/**
* 5根据用户手机号查询用户信息
* @param username
* @return
*/
public LoginUser getUserByPhone(String phone);
/**
* 6字典表的 翻译
@ -156,4 +164,31 @@ public interface CommonAPI {
*/
Object runAiragFlow(AiragFlowDTO airagFlowDTO);
/**
* 登录加载系统字典
* @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);
}

View File

@ -2,7 +2,7 @@ package org.jeecg.common.api.dto;
import lombok.Data;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.io.Serializable;
/**

View File

@ -1,5 +1,6 @@
package org.jeecg.common.aspect;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import org.apache.shiro.SecurityUtils;
@ -15,19 +16,21 @@ import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.enums.ModuleType;
import org.jeecg.common.constant.enums.OperateTypeEnum;
import org.jeecg.config.security.utils.SecureUtil;
import org.jeecg.modules.base.service.BaseCommonService;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.IpUtils;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
@ -100,7 +103,7 @@ public class AutoLogAspect {
//设置IP地址
dto.setIp(IpUtils.getIpAddr(request));
//获取登录用户信息
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
LoginUser sysUser = SecureUtil.currentUser();
if(sysUser!=null){
dto.setUserid(sysUser.getUsername());
dto.setUsername(sysUser.getRealname());
@ -143,7 +146,7 @@ public class AutoLogAspect {
// https://my.oschina.net/mengzhang6/blog/2395893
Object[] arguments = new Object[paramsArray.length];
for (int i = 0; i < paramsArray.length; i++) {
if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile || paramsArray[i] instanceof MultipartFile[]) {
if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
//ServletRequest不能序列化从入参里排除否则报异常java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
//ServletResponse不能序列化 从入参里排除否则报异常java.lang.IllegalStateException: getOutputStream() has already been called for this response
continue;
@ -172,7 +175,7 @@ public class AutoLogAspect {
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
StandardReflectionParameterNameDiscoverer u=new StandardReflectionParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
for (int i = 0; i < args.length; i++) {

View File

@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;

View File

@ -90,7 +90,7 @@ public interface CommonConstant {
/** 登录用户Shiro权限缓存KEY前缀 */
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
/** 登录用户Token令牌缓存KEY前缀 */
String PREFIX_USER_TOKEN = "prefix_user_token:";
String PREFIX_USER_TOKEN = "token::jeecg-client::";
// /** Token缓存时间3600秒即一小时 */
// int TOKEN_EXPIRE_TIME = 3600;

View File

@ -1,11 +1,11 @@
package org.jeecg.common.exception;
import cn.hutool.core.util.ObjectUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.jeecg.common.api.dto.LogDTO;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
@ -22,6 +22,8 @@ import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.connection.PoolException;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@ -33,8 +35,6 @@ import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.stream.Collectors;
@ -47,9 +47,27 @@ import java.util.stream.Collectors;
@RestControllerAdvice
@Slf4j
public class JeecgBootExceptionHandler {
@Resource
@Resource
BaseCommonService baseCommonService;
/**
* 验证码错误异常
*/
@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(MethodArgumentNotValidException.class)
public Result<?> handleValidationExceptions(MethodArgumentNotValidException e) {
@ -112,8 +130,8 @@ public class JeecgBootExceptionHandler {
return Result.error("数据库中已存在该记录");
}
@ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
public Result<?> handleAuthorizationException(AuthorizationException e){
@ExceptionHandler(AccessDeniedException.class)
public Result<?> handleAuthorizationException(AccessDeniedException e){
log.error(e.getMessage(), e);
return Result.noauth("没有权限,请联系管理员分配权限!");
}

View File

@ -0,0 +1,28 @@
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);
}
}

View File

@ -1,5 +1,6 @@
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.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -12,6 +13,7 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.config.security.utils.SecureUtil;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
@ -20,13 +22,14 @@ import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.jeecgframework.poi.handler.inter.IExcelExportServer;
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.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
@ -52,7 +55,7 @@ public class JeecgController<T, S extends IService<T>> {
protected ModelAndView exportXls(HttpServletRequest request, T object, Class<T> clazz, String title) {
// Step.1 组装查询条件
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
LoginUser sysUser = SecureUtil.currentUser();
// 过滤选中数据
String selections = request.getParameter("selections");
@ -90,7 +93,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) {
// Step.1 组装查询条件
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
LoginUser sysUser = SecureUtil.currentUser();
// Step.2 计算分页sheet数据
double total = service.count();
int count = (int)Math.ceil(total/pageNum);

View File

@ -5,7 +5,7 @@ import org.jeecg.common.system.vo.SysUserCacheInfo;
import org.jeecg.common.util.SpringContextUtils;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,24 +1,28 @@
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.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.stream.Stream;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant;
@ -30,6 +34,22 @@ import org.jeecg.common.system.vo.SysUserCacheInfo;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.SpringContextUtils;
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
@ -43,30 +63,33 @@ public class JwtUtil {
public static final long EXPIRE_TIME = (7 * 12) * 60 * 60 * 1000;
static final String WELL_NUMBER = SymbolConstant.WELL_NUMBER + SymbolConstant.LEFT_CURLY_BRACKET;
public static final String DEFAULT_CLIENT = "jeecg-client";
/**
*
* @param response
* @param code
* @param errorMsg
*/
public static void responseError(HttpServletResponse response, Integer code, String errorMsg) {
try {
Result jsonResult = new Result(code, errorMsg);
jsonResult.setSuccess(false);
// 设置响应头和内容类型
response.setStatus(code);
response.setHeader("Content-type", "text/html;charset=UTF-8");
response.setContentType("application/json;charset=UTF-8");
// 使用 ObjectMapper 序列化为 JSON 字符串
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(jsonResult);
response.getWriter().write(json);
response.getWriter().flush();
} catch (IOException e) {
public static void responseError(ServletResponse response, Integer code, String errorMsg) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// issues/I4YH95浏览器显示乱码问题
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
response.setContentType("application/json;charset=UTF-8");
Result jsonResult = new Result(code, errorMsg);
jsonResult.setSuccess(false);
OutputStream os = null;
try {
os = httpServletResponse.getOutputStream();
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setStatus(code);
os.write(new ObjectMapper().writeValueAsString(jsonResult).getBytes("UTF-8"));
os.flush();
os.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
* 校验token是否正确
@ -78,10 +101,9 @@ public class JwtUtil {
public static boolean verify(String token, String username, String secret) {
try {
// 根据密码生成JWT效验器
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
JwtDecoder jwtDecoder = SpringContextUtils.getBean(JwtDecoder.class);
// 效验TOKEN
DecodedJWT jwt = verifier.verify(token);
jwtDecoder.decode(token);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
@ -105,17 +127,25 @@ public class JwtUtil {
}
/**
* 生成签名,5min后过期
* 生成token
*
* @param username 用户名
* @param secret 用户的密码
* @return 加密的token
*/
public static String sign(String username, String secret) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
Map<String, Object> additionalParameter = new HashMap<>();
additionalParameter.put("username", username);
RegisteredClientRepository registeredClientRepository = SpringContextUtils.getBean(RegisteredClientRepository.class);
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();
}
@ -180,7 +210,7 @@ public class JwtUtil {
//2.通过shiro获取登录用户信息
LoginUser sysUser = null;
try {
sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
sysUser = SecureUtil.currentUser();
} catch (Exception e) {
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
}

View File

@ -1,13 +1,18 @@
package org.jeecg.common.system.vo;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecg.common.desensitization.annotation.SensitiveField;
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.Set;
/**
* <p>
@ -20,8 +25,10 @@ import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class LoginUser {
public class LoginUser implements Serializable {
private static final long serialVersionUID = -7143159031677245866L;
/**
* 登录人id
*/
@ -148,4 +155,30 @@ public class LoginUser {
* 主岗位
*/
private String mainDepPostId;
@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);
}
}

View File

@ -5,7 +5,7 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
/**
*

View File

@ -19,7 +19,7 @@ import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.io.File;

View File

@ -1,5 +1,6 @@
package org.jeecg.common.util;
import jakarta.servlet.http.HttpServletResponse;
import cn.hutool.core.io.IoUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
@ -10,7 +11,6 @@ import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

View File

@ -4,13 +4,13 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.handler.IFillRuleHandler;
import org.jeecg.common.system.query.QueryGenerator;
import javax.servlet.http.HttpServletRequest;
/**

View File

@ -1,6 +1,6 @@
package org.jeecg.common.util;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;

View File

@ -4,7 +4,7 @@ import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
@ -56,12 +56,22 @@ public class RestUtil {
private final static RestTemplate RT;
static {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
//update-begin---author:chenrui ---date:20251011 for[issues/8859]online表单java增强失效------------
// 使用 Apache HttpClient 避免 JDK HttpURLConnection 的 too many bytes written 问题
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
//update-end---author:chenrui ---date:20251011 for[issues/8859]online表单java增强失效------------
requestFactory.setConnectTimeout(30000);
requestFactory.setReadTimeout(30000);
RT = new RestTemplate(requestFactory);
// 解决乱码问题
RT.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
//update-begin---author:chenrui ---date:20251011 for[issues/8859]online表单java增强失效------------
// 解决乱码问题(替换 StringHttpMessageConverter 为 UTF-8
for (int i = 0; i < RT.getMessageConverters().size(); i++) {
if (RT.getMessageConverters().get(i) instanceof StringHttpMessageConverter) {
RT.getMessageConverters().set(i, new StringHttpMessageConverter(StandardCharsets.UTF_8));
break;
}
}
//update-end---author:chenrui ---date:20251011 for[issues/8859]online表单java增强失效------------
}
public static RestTemplate getRestTemplate() {
@ -250,12 +260,21 @@ public class RestUtil {
// 创建自定义RestTemplate如果需要设置超时
RestTemplate restTemplate = RT;
if (timeout > 0) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
//update-begin---author:chenrui ---date:20251011 for[issues/8859]online表单java增强失效------------
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
//update-end---author:chenrui ---date:20251011 for[issues/8859]online表单java增强失效------------
requestFactory.setConnectTimeout(timeout);
requestFactory.setReadTimeout(timeout);
restTemplate = new RestTemplate(requestFactory);
// 解决乱码问题
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
//update-begin---author:chenrui ---date:20251011 for[issues/8859]online表单java增强失效------------
// 解决乱码问题(替换 StringHttpMessageConverter 为 UTF-8
for (int i = 0; i < restTemplate.getMessageConverters().size(); i++) {
if (restTemplate.getMessageConverters().get(i) instanceof StringHttpMessageConverter) {
restTemplate.getMessageConverters().set(i, new StringHttpMessageConverter(StandardCharsets.UTF_8));
break;
}
}
//update-end---author:chenrui ---date:20251011 for[issues/8859]online表单java增强失效------------
}
// 请求体

View File

@ -1,17 +1,14 @@
package org.jeecg.common.util;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.concurrent.*;
/**
* @date 2025-09-04
* @author scott
*
* @Description: 支持shiro的API获取当前登录人方法的线程池
*
* @Description: 支持Spring Security的API获取当前登录人方法的线程池
*/
public class ShiroThreadPoolExecutor extends ThreadPoolExecutor {
@ -21,16 +18,14 @@ public class ShiroThreadPoolExecutor extends ThreadPoolExecutor {
@Override
public void execute(Runnable command) {
Subject subject = SecurityUtils.getSubject();
SecurityManager securityManager = SecurityUtils.getSecurityManager();
SecurityContext context = SecurityContextHolder.getContext();
super.execute(() -> {
SecurityContext previousContext = SecurityContextHolder.getContext();
try {
ThreadContext.bind(securityManager);
ThreadContext.bind(subject);
SecurityContextHolder.setContext(context);
command.run();
} finally {
ThreadContext.unbindSubject();
ThreadContext.unbindSecurityManager();
SecurityContextHolder.setContext(previousContext);
}
});
}

View File

@ -1,7 +1,7 @@
package org.jeecg.common.util;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.ServiceNameConstants;

View File

@ -1,5 +1,6 @@
package org.jeecg.common.util;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.api.CommonAPI;
@ -11,8 +12,6 @@ import org.jeecg.common.exception.JeecgBoot401Exception;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import javax.servlet.http.HttpServletRequest;
/**
* @Author scott
* @Date 2019/9/23 14:12
@ -110,8 +109,8 @@ public class TokenUtils {
}
// 查询用户信息
LoginUser user = TokenUtils.getLoginUser(username, commonApi, redisUtil);
//LoginUser user = commonApi.getUserByName(username);
//LoginUser user = TokenUtils.getLoginUser(username, commonApi, redisUtil);
LoginUser user = commonApi.getUserByName(username);
if (user == null) {
throw new JeecgBoot401Exception("用户不存在!");
}
@ -162,10 +161,11 @@ public class TokenUtils {
//【重要】此处通过redis原生获取缓存用户是为了解决微服务下system服务挂了其他服务互调不通问题---
if (redisUtil.hasKey(loginUserKey)) {
try {
loginUser = (LoginUser) redisUtil.get(loginUserKey);
Object obj = redisUtil.get(loginUserKey);
loginUser = (LoginUser) obj;
//解密用户
SensitiveInfoUtil.handlerObject(loginUser, false);
} catch (IllegalAccessException e) {
} catch (Exception e) {
e.printStackTrace();
}
} else {

View File

@ -1,10 +1,9 @@
package org.jeecg.common.util.encryption;
import org.apache.shiro.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/**
* @Description: AES 加密
@ -49,7 +48,7 @@ public class AesEncryptUtil {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return Base64.encodeToString(encrypted);
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
e.printStackTrace();
@ -67,7 +66,7 @@ public class AesEncryptUtil {
*/
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、解密报错 ---解码失败应该把异常抛出去,在外面处理
byte[] encrypted1 = Base64.decode(data);
byte[] encrypted1 = Base64.getDecoder().decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");

View File

@ -10,7 +10,7 @@ import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.springframework.beans.BeanUtils;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

View File

@ -3,7 +3,7 @@ package org.jeecg.config;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.system.vo.DictModel;

View File

@ -2,7 +2,9 @@ package org.jeecg.config;
import java.io.IOException;
import javax.servlet.*;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
import jakarta.servlet.*;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -11,8 +13,6 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
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;
/**

View File

@ -12,6 +12,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* @author eightmonth@qq.com
* 启动程序修改DruidWallConfig配置
* 允许SELECT语句的WHERE子句是一个永真条件
* @author eightmonth

View File

@ -1,7 +1,10 @@
package org.jeecg.config;
import org.jeecg.config.vo.*;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Role;
import org.springframework.stereotype.Component;
@ -11,6 +14,7 @@ import org.springframework.stereotype.Component;
*/
@Component("jeecgBaseConfig")
@ConfigurationProperties(prefix = "jeecg")
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class JeecgBaseConfig {
/**
* 签名密钥串(字典等敏感接口)
@ -36,10 +40,6 @@ public class JeecgBaseConfig {
*/
private Firewall firewall;
/**
* shiro拦截排除
*/
private Shiro shiro;
/**
* 上传文件配置
*/
@ -104,14 +104,6 @@ public class JeecgBaseConfig {
this.signatureSecret = signatureSecret;
}
public Shiro getShiro() {
return shiro;
}
public void setShiro(Shiro shiro) {
this.shiro = shiro;
}
public Path getPath() {
return path;
}

View File

@ -10,11 +10,13 @@ import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springdoc.core.filters.GlobalOpenApiMethodFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -61,28 +63,55 @@ public class Swagger3Config implements WebMvcConfigurer {
}
@Bean
public GlobalOpenApiCustomizer globalOpenApiCustomizer() {
return openApi -> {
// 全局添加鉴权参数
if (openApi.getPaths() != null) {
openApi.getPaths().forEach((path, pathItem) -> {
//log.debug("path: {}", path);
// 检查当前路径是否在排除列表中
boolean isExcluded = excludedPaths.stream().anyMatch(
excludedPath -> excludedPath.equals(path) || (excludedPath.endsWith("**") && path.startsWith(excludedPath.substring(0, excludedPath.length() - 2)))
);
if (!isExcluded) {
// 接口添加鉴权参数
pathItem.readOperations().forEach(operation ->
operation.addSecurityItem(new SecurityRequirement().addList(CommonConstant.X_ACCESS_TOKEN))
);
}
});
public OperationCustomizer operationCustomizer() {
return (operation, handlerMethod) -> {
String path = getFullPath(handlerMethod);
if (!isExcludedPath(path)) {
operation.addSecurityItem(new SecurityRequirement().addList(CommonConstant.X_ACCESS_TOKEN));
}else{
log.info("忽略加入 X_ACCESS_TOKEN 的 PATH:" + path);
}
return operation;
};
}
private String getFullPath(HandlerMethod handlerMethod) {
StringBuilder fullPath = new StringBuilder();
// 获取类级别的路径
RequestMapping classMapping = handlerMethod.getBeanType().getAnnotation(RequestMapping.class);
if (classMapping != null && classMapping.value().length > 0) {
fullPath.append(classMapping.value()[0]);
}
// 获取方法级别的路径
RequestMapping methodMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
if (methodMapping != null && methodMapping.value().length > 0) {
String methodPath = methodMapping.value()[0];
// 确保路径正确拼接,处理斜杠
if (!fullPath.toString().endsWith("/") && !methodPath.startsWith("/")) {
fullPath.append("/");
}
fullPath.append(methodPath);
}
return fullPath.toString();
}
private boolean isExcludedPath(String path) {
return excludedPaths.stream()
.anyMatch(pattern -> {
if (pattern.endsWith("/**")) {
// 处理通配符匹配
String basePath = pattern.substring(0, pattern.length() - 3);
return path.startsWith(basePath);
}
// 精确匹配
return pattern.equals(path);
});
}
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
@ -90,11 +119,15 @@ public class Swagger3Config implements WebMvcConfigurer {
.title("JeecgBoot 后台服务API接口文档")
.version("3.8.3")
.contact(new Contact().name("北京国炬信息技术有限公司").url("www.jeccg.com").email("jeecgos@163.com"))
.description( "后台API接口")
.description("后台API接口")
.termsOfService("NO terms of service")
.license(new License().name("Apache 2.0").url("http://www.apache.org/licenses/LICENSE-2.0.html")))
.addSecurityItem(new SecurityRequirement().addList(CommonConstant.X_ACCESS_TOKEN))
.components(new Components().addSecuritySchemes(CommonConstant.X_ACCESS_TOKEN,
new SecurityScheme().name(CommonConstant.X_ACCESS_TOKEN).type(SecurityScheme.Type.HTTP)));
new SecurityScheme()
.name(CommonConstant.X_ACCESS_TOKEN)
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER) // 关键:指定为 header
));
}
}

View File

@ -0,0 +1,19 @@
//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);
// });
// }
//}

View File

@ -10,17 +10,18 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.EventListener;
import org.springframework.http.CacheControl;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@ -32,7 +33,6 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
@ -47,6 +47,7 @@ import java.util.concurrent.TimeUnit;
* @Author qinfeng
*
*/
@Slf4j
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@ -154,16 +155,17 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
/**
* 监听应用启动完成事件,确保 PrometheusMeterRegistry 已经初始化
* 在Bean初始化完成后立即配置PrometheusMeterRegistry避免在Meter注册后才配置MeterFilter
* for [QQYUN-12558]【监控】系统监控的头两个tab不好使接口404
* @param event
* @author chenrui
* @date 2025/5/26 16:46
*/
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
if(null != meterRegistryPostProcessor){
meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "");
@PostConstruct
public void initPrometheusMeterRegistry() {
// 确保在应用启动早期就配置MeterFilter避免警告
if (null != meterRegistryPostProcessor && null != prometheusMeterRegistry) {
meterRegistryPostProcessor.postProcessAfterInitialization(prometheusMeterRegistry, "prometheusMeterRegistry");
log.info("PrometheusMeterRegistry配置完成");
}
}

View File

@ -3,8 +3,8 @@ package org.jeecg.config.filter;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.config.sign.util.BodyReaderHttpServletRequestWrapper;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
/**

View File

@ -7,9 +7,9 @@ import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.common.util.oConvertUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**

View File

@ -1,6 +1,9 @@
package org.jeecg.config.firewall.interceptor;
import com.alibaba.fastjson.JSON;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.CommonAPI;
@ -14,9 +17,6 @@ import org.jeecg.config.JeecgBaseConfig;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;

View File

@ -6,13 +6,13 @@ import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.TenantConstant;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.security.utils.SecureUtil;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
@ -192,7 +192,7 @@ public class MybatisInterceptor implements Interceptor {
private LoginUser getLoginUser() {
LoginUser sysUser = null;
try {
sysUser = SecurityUtils.getSubject().getPrincipal() != null ? (LoginUser) SecurityUtils.getSubject().getPrincipal() : null;
sysUser = SecureUtil.currentUser() != null ? SecureUtil.currentUser() : null;
} catch (Exception e) {
//e.printStackTrace();
sysUser = null;

View File

@ -11,7 +11,7 @@ import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.config.mybatis.ThreadLocalDataHelper;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**

View File

@ -6,8 +6,8 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 动态数据源切换拦截器

View File

@ -1,5 +1,6 @@
package org.jeecg.config.oss;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
@ -28,7 +29,7 @@ public class MinioConfig {
@Value(value = "${jeecg.minio.bucketName}")
private String bucketName;
@Bean
@PostConstruct
public void initMinio(){
if(!minioUrl.startsWith(CommonConstant.STR_HTTP)){
minioUrl = "http://" + minioUrl;

View File

@ -1,5 +1,6 @@
package org.jeecg.config.oss;
import jakarta.annotation.PostConstruct;
import org.jeecg.common.util.oss.OssBootUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -28,7 +29,7 @@ public class OssConfiguration {
private String staticDomain;
@Bean
@PostConstruct
public void initOssBootConfiguration() {
OssBootUtil.setEndPoint(endpoint);
OssBootUtil.setAccessKeyId(accessKeyId);

View File

@ -0,0 +1,90 @@
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);
}
}

View File

@ -0,0 +1,80 @@
package org.jeecg.config.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.*;
/**
* 仪盘表请求query体携带的token
* @author eightmonth
* @date 2024/7/3 14:04
*/
@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class CopyTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 以下为undertow定制代码如切换其它servlet容器需要同步更换
String token = request.getHeader("Authorization");
String bearerToken = request.getParameter("token");
String headerBearerToken = request.getHeader("X-Access-Token");
String finalToken;
log.debug("【仪盘表请求query体携带的token】CopyTokenFilter token: {}, bearerToken: {}, headerBearerToken: {}", token, bearerToken, headerBearerToken);
if (StringUtils.hasText(token)) {
finalToken = "bearer " + token;
} else if (StringUtils.hasText(bearerToken)) {
finalToken = "bearer " + bearerToken;
} else if (StringUtils.hasText(headerBearerToken)) {
finalToken = "bearer " + headerBearerToken;
} else {
finalToken = null;
}
if (finalToken != null) {
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
@Override
public String getHeader(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return finalToken;
}
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaders(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return Collections.enumeration(Collections.singleton(finalToken));
}
return super.getHeaders(name);
}
@Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
if (!names.contains("Authorization")) {
names.add("Authorization");
}
return Collections.enumeration(names);
}
};
filterChain.doFilter(wrapper, response);
} else {
filterChain.doFilter(request, response);
}
}
}

View File

@ -0,0 +1,34 @@
package org.jeecg.config.security;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.system.vo.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
* token只存储用户名与过期时间
* 这里通过取用户名转全量用户信息存储到Security中
* @author eightmonth@qq.com
* @date 2024/7/15 11:05
*/
@Component
public class JeecgAuthenticationConvert implements Converter<Jwt, AbstractAuthenticationToken> {
@Lazy
@Autowired
private CommonAPI commonAPI;
@Override
public AbstractAuthenticationToken convert(Jwt source) {
String username = source.getClaims().get("username").toString();
LoginUser loginUser = commonAPI.getUserByName(username);
return new UsernamePasswordAuthenticationToken(loginUser, null, new ArrayList<>());
}
}

View File

@ -0,0 +1,135 @@
package org.jeecg.config.security;
import org.jeecg.common.system.util.JwtUtil;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalUnit;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author eightmonth@qq.com
* @date 2024/7/11 17:10
*/
public class JeecgOAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {
private final JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
public JeecgOAuth2AccessTokenGenerator(JwtEncoder jwtEncoder) {
this.jwtEncoder = jwtEncoder;
}
@Nullable
@Override
public OAuth2AccessToken generate(OAuth2TokenContext context) {
if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
return null;
}
String issuer = null;
if (context.getAuthorizationServerContext() != null) {
issuer = context.getAuthorizationServerContext().getIssuer();
}
RegisteredClient registeredClient = context.getRegisteredClient();
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plusMillis(JwtUtil.EXPIRE_TIME);
OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder();
if (StringUtils.hasText(issuer)) {
claimsBuilder.issuer(issuer);
}
claimsBuilder
.subject(context.getPrincipal().getName())
.audience(Collections.singletonList(registeredClient.getClientId()))
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.notBefore(issuedAt)
.id(UUID.randomUUID().toString());
if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {
claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes());
}
if (this.accessTokenCustomizer != null) {
OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder)
.registeredClient(context.getRegisteredClient())
.principal(context.getPrincipal())
.authorizationServerContext(context.getAuthorizationServerContext())
.authorizedScopes(context.getAuthorizedScopes())
.tokenType(context.getTokenType())
.authorizationGrantType(context.getAuthorizationGrantType());
if (context.getAuthorization() != null) {
accessTokenContextBuilder.authorization(context.getAuthorization());
}
if (context.getAuthorizationGrant() != null) {
accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant());
}
OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build();
this.accessTokenCustomizer.customize(accessTokenContext);
}
OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build();
OAuth2AuthorizationGrantAuthenticationToken oAuth2ResourceOwnerBaseAuthenticationToken = context.getAuthorizationGrant();
String username = (String) oAuth2ResourceOwnerBaseAuthenticationToken.getAdditionalParameters().get("username");
String tokenValue = jwtEncoder.encode(JwtEncoderParameters.from(JwsHeader.with(SignatureAlgorithm.ES256).keyId("jeecg").build(),
JwtClaimsSet.builder().claim("username", username).expiresAt(expiresAt).build())).getTokenValue();
//此处可以做改造将tokenValue随机数换成用户信息方便后续多系统token互通认证通过解密token得到username
return new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER, tokenValue,
accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(),
accessTokenClaimsSet.getClaims());
}
/**
* Sets the {@link OAuth2TokenCustomizer} that customizes the
* {@link OAuth2TokenClaimsContext#getClaims() claims} for the
* {@link OAuth2AccessToken}.
* @param accessTokenCustomizer the {@link OAuth2TokenCustomizer} that customizes the
* claims for the {@code OAuth2AccessToken}
*/
public void setAccessTokenCustomizer(OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer) {
Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null");
this.accessTokenCustomizer = accessTokenCustomizer;
}
private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {
private final Map<String, Object> claims;
private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
Set<String> scopes, Map<String, Object> claims) {
super(tokenType, tokenValue, issuedAt, expiresAt, scopes);
this.claims = claims;
}
@Override
public Map<String, Object> getClaims() {
return this.claims;
}
}
}

View File

@ -0,0 +1,104 @@
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
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")
@Slf4j
public class JeecgPermissionService {
private final String SPLIT = "::";
private final String PERM_PREFIX = "jps" + SPLIT;
@Lazy
@Autowired
private CommonAPI commonAPI;
@Autowired
private RedisUtil redisUtil;
/**
* 判断接口是否有任意xxxxxx权限
* @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.getId()));
Set<String> permissionList;
if (Objects.nonNull(cache)) {
permissionList = (Set<String>) cache;
} else {
permissionList = commonAPI.queryUserAuths(loginUser.getId());
redisUtil.set(buildKey("permission", loginUser.getId()), permissionList);
}
boolean pass = permissionList.stream().filter(StringUtils::hasText)
.anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
if (!pass) {
log.error("权限不足,缺少权限:"+ Arrays.toString(permissions));
}
return pass;
}
/**
* 判断接口是否有任意xxxxxx角色
* @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;
}
}

View File

@ -0,0 +1,54 @@
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());
}
}

View File

@ -0,0 +1,192 @@
package org.jeecg.config.security;
import cn.hutool.core.collection.CollUtil;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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
public class JeecgRedisOAuth2AuthorizationService implements OAuth2AuthorizationService{
private final static Long TIMEOUT = 10L;
private static final String AUTHORIZATION = "token";
private final RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 因为保存sas的认证信息至redis无法使用jeecg对redisTemplate的某些设置。
* 如果在使用时修改redisTemplate属性会发生线程安全问题最终容易引起系统无法正常运行。
* 所以重新建了一个redis client给到sas操作redis并且该redis实例不注入spring 容器中
*/
@PostConstruct
public void initSasRedis() {
redisTemplate.setValueSerializer(RedisSerializer.java());
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.afterPropertiesSet();
}
@Override
public void save(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
if (isState(authorization)) {
String token = authorization.getAttribute("state");
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.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.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.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");
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);
}
}
}

View File

@ -0,0 +1,38 @@
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";
}

View File

@ -0,0 +1,49 @@
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);
}
}

View File

@ -0,0 +1,310 @@
package org.jeecg.config.security;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.util.JwtUtil;
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.jeecg.config.shiro.ignore.InMemoryIgnoreAuth;
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.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* spring authorization server核心配置
* @author eightmonth@qq.com
* @date 2024/1/2 9:29
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@AllArgsConstructor
@Slf4j
public class SecurityConfig {
private JdbcTemplate jdbcTemplate;
private OAuth2AuthorizationService authorizationService;
private JeecgAuthenticationConvert jeecgAuthenticationConvert;
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
// 使用新的配置方式替代弃用的applyDefaultSecurity
http.securityMatcher(new AntPathRequestMatcher("/oauth2/**"))
.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated()
)
.csrf(csrf -> csrf.disable())
.with(new OAuth2AuthorizationServerConfigurer(), oauth2 -> {
oauth2
.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());
});
//请求接口异常处理无Token和Token无效的情况
http.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint((request, response, authException) -> {
// 记录详细的异常信息 - 未认证
log.error("接口访问失败(未认证),请求路径:{},错误信息:{}", request.getRequestURI(), authException.getMessage(), authException);
JwtUtil.responseError(response, 401, "Token格式错误或已过期");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
// 记录详细的异常信息 - token无效或权限不足
log.error("接口访问失败(token无效或权限不足),请求路径:{},错误信息:{}", request.getRequestURI(), accessDeniedException.getMessage(), accessDeniedException);
JwtUtil.responseError(response, 403, "权限不足");
})
);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
//设置所有请求都需要认证未认证的请求都被重定向到login页面进行登录
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(InMemoryIgnoreAuth.get().stream().map(AntPathRequestMatcher::antMatcher).toList().toArray(new AntPathRequestMatcher[0])).permitAll()
.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("/api/getUserInfo")).permitAll()
//积木报表排除
.requestMatchers(AntPathRequestMatcher.antMatcher("/jmreport/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.js.map")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/**/*.css.map")).permitAll()
//积木BI大屏和仪表盘排除
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/view")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getLoginUser")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/page/queryById")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/page/addVisitsNumber")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/page/queryTemplateList")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/share/view/**")).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("/jimubi/view")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/jimubi/share/view/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getMapDataByCode")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getTotalDataByCompId")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/queryAllById")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/drag/onlDragDatasetHead/getDictByCodes")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/dragChannelSocket/**")).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()
.requestMatchers(AntPathRequestMatcher.antMatcher("/openapi/call/**")).permitAll()
// APP版本信息
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/version/app3version")).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)
// 配置OAuth2资源服务器并添加JWT异常处理
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jeecgAuthenticationConvert))
.authenticationEntryPoint((request, response, authException) -> {
// 处理JWT解析失败的情况
log.error("JWT验证失败请求路径{},错误信息:{}", request.getRequestURI(), authException.getMessage(), authException);
JwtUtil.responseError(response, 401, "Token格式错误或已过期");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
// 处理权限不足的情况
log.error("权限验证失败,请求路径:{},错误信息:{}", request.getRequestURI(), accessDeniedException.getMessage(), accessDeniedException);
JwtUtil.responseError(response, 403, "权限不足");
})
)
// 全局异常处理
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint((request, response, authException) -> {
// 记录详细的异常信息 - 未认证
log.error("接口访问失败(未认证),请求路径:{},错误信息:{}", request.getRequestURI(), authException.getMessage(), authException);
JwtUtil.responseError(response, 401, "Token格式错误或已过期");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
// 记录详细的异常信息 - token无效或权限不足
log.error("接口访问失败(token无效或权限不足),请求路径:{},错误信息:{}", request.getRequestURI(), accessDeniedException.getMessage(), accessDeniedException);
JwtUtil.responseError(response, 403, "权限不足");
})
);
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
@SneakyThrows
public JWKSource<SecurityContext> jwkSource() {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
// 如果不设置secureRandom会存在一个问题当应用重启后原有的token将会全部失效因为重启的keyPair与之前已经不同
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
// 重要!生产环境需要修改!
secureRandom.setSeed("jeecg".getBytes());
keyPairGenerator.initialize(256, secureRandom);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
ECKey jwk = new ECKey.Builder(Curve.P_256, publicKey)
.privateKey(privateKey)
.keyID("jeecg")
.build();
JWKSet jwkSet = new JWKSet(jwk);
return new ImmutableJWKSet<>(jwkSet);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
/**
* 配置jwt解析器
*/
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
/**
*配置token生成器
*/
@Bean
OAuth2TokenGenerator<?> tokenGenerator() {
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource()));
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
new JeecgOAuth2AccessTokenGenerator(new NimbusJwtEncoder(jwkSource())),
new OAuth2RefreshTokenGenerator()
);
}
}

View File

@ -0,0 +1,81 @@
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;
}
}

View File

@ -0,0 +1,320 @@
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.context.annotation.Lazy;
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;
@Lazy
@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 forif条件永远为falsebug------------
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
//update-end---author:王帅 Date:20200601 forif条件永远为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("该用户已冻结");
}
}
}

View File

@ -0,0 +1,21 @@
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);
}
}

View File

@ -0,0 +1,82 @@
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;
}
}

View File

@ -0,0 +1,319 @@
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.context.annotation.Lazy;
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;
@Lazy
@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 forif条件永远为falsebug------------
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
//update-end---author:王帅 Date:20200601 forif条件永远为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("该用户已冻结");
}
}
}

View File

@ -0,0 +1,21 @@
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);
}
}

View File

@ -0,0 +1,77 @@
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;
}
}

View File

@ -0,0 +1,292 @@
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.context.annotation.Lazy;
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;
@Lazy
@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 forif条件永远为falsebug------------
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
//update-end---author:王帅 Date:20200601 forif条件永远为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("该用户已冻结");
}
}
}

View File

@ -0,0 +1,21 @@
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);
}
}

View File

@ -0,0 +1,187 @@
package org.jeecg.config.security.self;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBoot401Exception;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.modules.base.service.BaseCommonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
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;
@Lazy
@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();
// 通过用户名获取用户信息
// LoginUser loginUser = commonAPI.getUserByName(username);
// 检查用户可行性
// checkUserIsEffective(loginUser);
//由于在上面已验证过用户名、密码现在构建一个已认证的对象UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(username,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) {
throw new JeecgBoot401Exception("无法生成刷新token请联系管理员。");
}
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);
// 返回access_token、refresh_token以及其它信息给到前端
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken);
}
@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 forif条件永远为falsebug------------
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
//update-end---author:王帅 Date:20200601 forif条件永远为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("该用户已冻结");
}
}
}

View File

@ -0,0 +1,19 @@
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);
}
}

View File

@ -0,0 +1,81 @@
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;
}
}

View File

@ -0,0 +1,278 @@
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.context.annotation.Lazy;
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;
@Lazy
@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 forif条件永远为falsebug------------
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
//update-end---author:王帅 Date:20200601 forif条件永远为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("该用户已冻结");
}
}
}

View File

@ -0,0 +1,21 @@
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);
}
}

View File

@ -0,0 +1,23 @@
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);
}
}

View File

@ -1,28 +0,0 @@
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;
}
}

View File

@ -1,381 +0,0 @@
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.beans.factory.config.BeanDefinition;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.type.filter.AnnotationTypeFilter;
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.bind.annotation.*;
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.lang.reflect.Method;
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、部分过滤器可指定参数如permsroles
*/
@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/smsCheckCaptcha", "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"); //授权接口排除
filterChainDefinitionMap.put("/openapi/call/**", "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");
filterChainDefinitionMap.put("/**/*.glb", "anon");
filterChainDefinitionMap.put("/**/*.wasm", "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("/v3/**", "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");
//积木BI大屏和仪表盘排除
filterChainDefinitionMap.put("/drag/view", "anon");
filterChainDefinitionMap.put("/drag/page/queryById", "anon");
filterChainDefinitionMap.put("/drag/page/addVisitsNumber", "anon");
filterChainDefinitionMap.put("/drag/page/queryTemplateList", "anon");
filterChainDefinitionMap.put("/drag/share/view/**", "anon");
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getAllChartData", "anon");
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalData", "anon");
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getMapDataByCode", "anon");
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getTotalDataByCompId", "anon");
filterChainDefinitionMap.put("/drag/mock/json/**", "anon");
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/getDictByCodes", "anon");
filterChainDefinitionMap.put("/drag/onlDragDatasetHead/queryAllById", "anon");
filterChainDefinitionMap.put("/jimubi/view", "anon");
filterChainDefinitionMap.put("/jimubi/share/view/**", "anon");
//大屏模板例子
filterChainDefinitionMap.put("/test/bigScreen/**", "anon");
filterChainDefinitionMap.put("/bigscreen/template1/**", "anon");
filterChainDefinitionMap.put("/bigscreen/template2/**", "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无痕刷新示例
//App vue3版本查询版本接口
filterChainDefinitionMap.put("/sys/version/app3version", "anon");
//仪表盘(按钮通信)
filterChainDefinitionMap.put("/dragChannelSocket/**","anon");
//App vue3版本查询版本接口
filterChainDefinitionMap.put("/sys/version/app3version", "anon");
//性能监控——安全隐患泄露TOEKNdurid连接池也有
//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助手------------
/**
* spring过滤装饰器 <br/>
* 因为shiro的filter不支持异步请求,导致所有的异步请求都会报错. <br/>
* 所以需要用spring的FilterRegistrationBean再代理一下shiro的filter.为他扩展异步支持. <br/>
* 后续所有异步的接口都需要再这里增加registration.addUrlPatterns("/xxx/xxx");
* @return
* @author chenrui
* @date 2024/12/3 19:49
*/
@Bean
public FilterRegistrationBean shiroFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new DelegatingFilterProxy("shiroFilterFactoryBean"));
registration.setEnabled(true);
//update-begin---author:chenrui ---date:20241202 for[issues/7491]运行时间好长,效率慢 ------------
registration.addUrlPatterns("/test/ai/chat/send");
//update-end---author:chenrui ---date:20241202 for[issues/7491]运行时间好长,效率慢 ------------
registration.addUrlPatterns("/airag/flow/run");
registration.addUrlPatterns("/airag/flow/debug");
registration.addUrlPatterns("/airag/chat/send");
registration.addUrlPatterns("/airag/app/debug");
registration.addUrlPatterns("/airag/app/prompt/generate");
registration.addUrlPatterns("/airag/chat/receive/**");
//支持异步
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;
}
/**
* RedisConfig在项目starter项目中
* jeecg-boot-starter-github\jeecg-boot-common\src\main\java\org\jeecg\common\modules\redis\config\RedisConfig.java
*
* 配置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.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;
}
private List<String> rebuildUrl(String[] bases, String[] uris) {
List<String> urls = new ArrayList<>();
for (String base : bases) {
for (String uri : uris) {
urls.add(prefix(base)+prefix(uri));
}
}
return urls;
}
private String prefix(String seg) {
return seg.startsWith("/") ? seg : "/"+seg;
}
}

View File

@ -1,234 +0,0 @@
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.jeecg.config.mybatis.MybatisPlusSaasConfig;
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.queryUserRolesById(userId);
//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) {
log.error("—————校验 check token 失败——————————"+ e.getMessage(), e);
JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),401,e.getMessage());
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(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && 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);
//update-begin---author:scott ---date::2024-06-18 for【TV360X-1320】分配权限必须退出重新登录才生效造成很多用户困扰---
super.clearCachedAuthorizationInfo(principals);
//update-end---author:scott ---date::2024-06-18 for【TV360X-1320】分配权限必须退出重新登录才生效造成很多用户困扰---
}
}

View File

@ -1,77 +0,0 @@
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携带中文400servletPath中文校验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);
}
}
}
}
}

View File

@ -1,130 +0,0 @@
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.jeecg.config.shiro.ignore.InMemoryIgnoreAuth;
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 {
// 判断当前路径是不是注解了@IngoreAuth路径如果是则放开验证
if (InMemoryIgnoreAuth.contains(((HttpServletRequest) request).getServletPath())) {
return true;
}
executeLogin(request, response);
return true;
} catch (Exception e) {
JwtUtil.responseError((HttpServletResponse)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 forJT-355 OA聊天添加token验证获取token参数
if (oConvertUtils.isEmpty(token)) {
token = httpServletRequest.getParameter("token");
}
// update-end--Author:lvdandan Date:20210105 forJT-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();
}
}

View File

@ -1,67 +0,0 @@
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;
}
}

View File

@ -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.WebMvcConfigurer;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
/**
* 签名 拦截器配置

View File

@ -4,8 +4,8 @@ package org.jeecg.config.sign.interceptor;
import java.io.PrintWriter;
import java.util.SortedMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;

View File

@ -1,10 +1,10 @@
package org.jeecg.config.sign.util;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;

View File

@ -10,7 +10,7 @@ import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.SymbolConstant;

View File

@ -1,18 +0,0 @@
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;
}
}

View File

@ -2,10 +2,10 @@ package org.jeecg.modules.base.service.impl;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.dto.LogDTO;
import org.jeecg.common.constant.enums.ClientTerminalTypeEnum;
import org.jeecg.common.util.BrowserUtils;
import org.jeecg.config.security.utils.SecureUtil;
import org.jeecg.modules.base.mapper.BaseCommonMapper;
import org.jeecg.modules.base.service.BaseCommonService;
import org.jeecg.common.system.vo.LoginUser;
@ -14,8 +14,8 @@ import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import java.util.*;
/**
@ -35,7 +35,7 @@ public class BaseCommonServiceImpl implements BaseCommonService {
logDTO.setId(String.valueOf(IdWorker.getId()));
}
//保存日志异常捕获处理防止数据太大存储失败导致业务失败JT-238
try {
try {
logDTO.setCreateTime(new Date());
baseCommonMapper.saveLog(logDTO);
} catch (Exception e) {
@ -74,7 +74,7 @@ public class BaseCommonServiceImpl implements BaseCommonService {
//获取登录用户信息
if(user==null){
try {
user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
user = SecureUtil.currentUser();
} catch (Exception e) {
//e.printStackTrace();
}

View File

@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-module</artifactId>
<version>3.8.3</version>
</parent>
@ -31,31 +31,49 @@
</repositories>
<properties>
<langchain4j.version>0.35.0</langchain4j.version>
<apache-tika.version>2.9.1</apache-tika.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>1.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>1.3.0-beta9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- system单体 api-->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-system-local-api</artifactId>
</dependency>
<!-- 微服务starter和system微服务 api
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-system-cloud-api</artifactId>
</dependency>-->
<!-- aiflow依赖 -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-aiflow</artifactId>
<version>1.2.0</version>
<version>3.8.3.1</version>
</dependency>
<!-- beigin 这两个依赖太多每个包50M左右如果你发布需要使用请把<scope>provided</scope>删掉 -->
@ -72,7 +90,6 @@
<scope>provided</scope>
</dependency>
<!-- end 这两个依赖太多每个包50M左右如果你发布需要使用请把<scope>provided</scope>删掉 -->
<!-- aiflow 脚本依赖 -->
<dependency>
<groupId>com.yomahub</groupId>
@ -105,17 +122,19 @@
</exclusions>
</dependency>
<!-- aiflow 脚本依赖 -->
<!-- langChain4j model support -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>${langchain4j.version}</version>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-zhipu-ai</artifactId>
<version>${langchain4j.version}</version>
<artifactId>langchain4j-ollama</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-zhipu-ai</artifactId>
<exclusions>
<exclusion>
<artifactId>checker-qual</artifactId>
@ -129,13 +148,11 @@
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qianfan</artifactId>
<version>${langchain4j.version}</version>
<artifactId>langchain4j-community-qianfan</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-dashscope</artifactId>
<version>${langchain4j.version}</version>
<artifactId>langchain4j-community-dashscope</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@ -151,7 +168,7 @@
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>langchain4j-pgvector</artifactId>
<version>${langchain4j.version}</version>
<version>1.3.0-beta9</version>
</dependency>
<!-- langChain4j Document Parser -->
<dependency>

View File

@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
@ -18,10 +17,12 @@ import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @Description: AI应用
@ -66,7 +67,7 @@ public class AiragAppController extends JeecgController<AiragApp, IAiragAppServi
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
@RequiresPermissions("airag:app:edit")
@PreAuthorize("@jps.requiresPermissions('airag:app:edit')")
public Result<String> edit(@RequestBody AiragApp airagApp) {
AssertUtils.assertNotEmpty("参数异常", airagApp);
AssertUtils.assertNotEmpty("请输入应用名称", airagApp.getName());
@ -105,7 +106,7 @@ public class AiragAppController extends JeecgController<AiragApp, IAiragAppServi
* @return
*/
@DeleteMapping(value = "/delete")
@RequiresPermissions("airag:app:delete")
@PreAuthorize("@jps.requiresPermissions('airag:app:delete')")
public Result<String> delete(HttpServletRequest request,@RequestParam(name = "id", required = true) String id) {
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下判断当前租户id是否是当前租户下的

View File

@ -1,5 +1,7 @@
package org.jeecg.modules.airag.app.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
@ -15,8 +17,6 @@ import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**

View File

@ -71,7 +71,7 @@ public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> i
AtomicBoolean isThinking = new AtomicBoolean(false);
String requestId = UUIDGenerator.generate();
// ai聊天响应逻辑
tokenStream.onNext((String resMessage) -> {
tokenStream.onPartialResponse((String resMessage) -> {
// 兼容推理模型
if ("<think>".equals(resMessage)) {
isThinking.set(true);
@ -99,9 +99,9 @@ public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> i
throw new RuntimeException(e);
}
})
.onComplete((responseMessage) -> {
.onCompleteResponse((responseMessage) -> {
// 记录ai的回复
AiMessage aiMessage = responseMessage.content();
AiMessage aiMessage = responseMessage.aiMessage();
FinishReason finishReason = responseMessage.finishReason();
String respText = aiMessage.text();
if (FinishReason.STOP.equals(finishReason) || null == finishReason) {
@ -114,9 +114,6 @@ public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> i
throw new RuntimeException(e);
}
closeSSE(emitter, eventData);
} else if (FinishReason.TOOL_EXECUTION.equals(finishReason)) {
// 需要执行工具
// TODO author: chenrui for: date:2025/3/7
} else {
// 异常结束
log.error("调用模型异常:" + respText);

View File

@ -40,7 +40,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
@ -860,7 +860,7 @@ public class AiragChatServiceImpl implements IAiragChatService {
*/
AtomicBoolean isThinking = new AtomicBoolean(false);
// ai聊天响应逻辑
chatStream.onNext((String resMessage) -> {
chatStream.onPartialResponse((String resMessage) -> {
// 兼容推理模型
if ("<think>".equals(resMessage)) {
isThinking.set(true);
@ -886,12 +886,12 @@ public class AiragChatServiceImpl implements IAiragChatService {
return;
}
sendMessage2Client(emitter, eventData);
}).onComplete((responseMessage) -> {
}).onCompleteResponse((responseMessage) -> {
// 打印流程耗时日志
printChatDuration(requestId, "LLM输出消息完成");
AiragLocalCache.remove(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId);
// 记录ai的回复
AiMessage aiMessage = responseMessage.content();
AiMessage aiMessage = responseMessage.aiMessage();
FinishReason finishReason = responseMessage.finishReason();
String respText = aiMessage.text();
// sse

View File

@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.AssertUtils;
@ -18,11 +17,12 @@ import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeDocService;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -77,7 +77,7 @@ public class AiragKnowledgeController {
* @date 2025/2/18 17:09
*/
@PostMapping(value = "/add")
@RequiresPermissions("airag:knowledge:add")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:add')")
public Result<String> add(@RequestBody AiragKnowledge airagKnowledge) {
airagKnowledge.setStatus(LLMConsts.STATUS_ENABLE);
airagKnowledgeService.save(airagKnowledge);
@ -94,7 +94,7 @@ public class AiragKnowledgeController {
*/
@Transactional(rollbackFor = Exception.class)
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
@RequiresPermissions("airag:knowledge:edit")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:edit')")
public Result<String> edit(@RequestBody AiragKnowledge airagKnowledge) {
AiragKnowledge airagKnowledgeEntity = airagKnowledgeService.getById(airagKnowledge.getId());
if (airagKnowledgeEntity == null) {
@ -118,7 +118,7 @@ public class AiragKnowledgeController {
* @date 2025/3/12 17:05
*/
@PutMapping(value = "/rebuild")
@RequiresPermissions("airag:knowledge:rebuild")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:rebuild')")
public Result<?> rebuild(@RequestParam("knowIds") String knowIds) {
String[] knowIdArr = knowIds.split(",");
for (String knowId : knowIdArr) {
@ -137,7 +137,7 @@ public class AiragKnowledgeController {
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/delete")
@RequiresPermissions("airag:knowledge:delete")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:delete')")
public Result<String> delete(HttpServletRequest request, @RequestParam(name = "id", required = true) String id) {
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下判断当前租户id是否是当前租户下的
@ -204,7 +204,7 @@ public class AiragKnowledgeController {
* @date 2025/2/18 15:47
*/
@PostMapping(value = "/doc/edit")
@RequiresPermissions("airag:knowledge:doc:edit")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:doc:edit')")
public Result<?> addDocument(@RequestBody AiragKnowledgeDoc airagKnowledgeDoc) {
return airagKnowledgeDocService.editDocument(airagKnowledgeDoc);
}
@ -217,7 +217,7 @@ public class AiragKnowledgeController {
* @date 2025/3/20 11:29
*/
@PostMapping(value = "/doc/import/zip")
@RequiresPermissions("airag:knowledge:doc:zip")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:doc:zip')")
public Result<?> importDocumentFromZip(@RequestParam(name = "knowId", required = true) String knowId,
@RequestParam(name = "file", required = true) MultipartFile file) {
return airagKnowledgeDocService.importDocumentFromZip(knowId,file);
@ -244,7 +244,7 @@ public class AiragKnowledgeController {
* @date 2025/2/18 15:47
*/
@PutMapping(value = "/doc/rebuild")
@RequiresPermissions("airag:knowledge:doc:rebuild")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:doc:rebuild')")
public Result<?> rebuildDocument(@RequestParam("docIds") String docIds) {
return airagKnowledgeDocService.rebuildDocument(docIds);
}
@ -259,7 +259,7 @@ public class AiragKnowledgeController {
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/doc/deleteBatch")
@RequiresPermissions("airag:knowledge:doc:deleteBatch")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:doc:deleteBatch')")
public Result<String> deleteDocumentBatch(HttpServletRequest request, @RequestParam(name = "ids", required = true) String ids) {
List<String> idsList = Arrays.asList(ids.split(","));
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
@ -287,7 +287,7 @@ public class AiragKnowledgeController {
*/
@Transactional(rollbackFor = Exception.class)
@DeleteMapping(value = "/doc/deleteAll")
@RequiresPermissions("airag:knowledge:doc:deleteAll")
@PreAuthorize("@jps.requiresPermissions('airag:knowledge:doc:deleteAll')")
public Result<?> deleteDocumentAll(HttpServletRequest request, @RequestParam(name = "knowId") String knowId) {
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下判断当前租户id是否是当前租户下的

View File

@ -7,7 +7,6 @@ import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.embedding.EmbeddingModel;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.ai.factory.AiModelFactory;
import org.jeecg.ai.factory.AiModelOptions;
import org.jeecg.common.api.vo.Result;
@ -23,11 +22,12 @@ import org.jeecg.modules.airag.llm.handler.AIChatHandler;
import org.jeecg.modules.airag.llm.handler.EmbeddingHandler;
import org.jeecg.modules.airag.llm.service.IAiragModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Collections;
@ -72,7 +72,7 @@ public class AiragModelController extends JeecgController<AiragModel, IAiragMode
* @return
*/
@PostMapping(value = "/add")
@RequiresPermissions("airag:model:add")
@PreAuthorize("@jps.requiresPermissions('airag:model:add')")
public Result<String> add(@RequestBody AiragModel airagModel) {
// 验证 模型名称/模型类型/基础模型
AssertUtils.assertNotEmpty("模型名称不能为空", airagModel.getName());
@ -94,7 +94,7 @@ public class AiragModelController extends JeecgController<AiragModel, IAiragMode
* @return
*/
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
@RequiresPermissions("airag:model:edit")
@PreAuthorize("@jps.requiresPermissions('airag:model:edit')")
public Result<String> edit(@RequestBody AiragModel airagModel) {
airagModelService.updateById(airagModel);
return Result.OK("编辑成功!");
@ -107,7 +107,7 @@ public class AiragModelController extends JeecgController<AiragModel, IAiragMode
* @return
*/
@DeleteMapping(value = "/delete")
@RequiresPermissions("airag:model:delete")
@PreAuthorize("@jps.requiresPermissions('airag:model:delete')")
public Result<String> delete(HttpServletRequest request, @RequestParam(name = "id", required = true) String id) {
//update-begin---author:chenrui ---date:20250606 for[issues/8337]关于ai工作列表的数据权限问题 #8337------------
//如果是saas隔离的情况下判断当前租户id是否是当前租户下的

View File

@ -105,14 +105,14 @@ public class AIChatHandler implements IAIChatHandler {
// langchain4j 异常友好提示
String errMsg = "调用大模型接口失败,详情请查看后台日志。";
if (oConvertUtils.isNotEmpty(e.getMessage())) {
// // 根据常见异常关键字做细致翻译
// for (Map.Entry<String, String> entry : MODEL_ERROR_MAP.entrySet()) {
// String key = entry.getKey();
// String value = entry.getValue();
// if (errMsg.contains(key)) {
// errMsg = value;
// }
// }
// 根据常见异常关键字做细致翻译
for (Map.Entry<String, String> entry : MODEL_ERROR_MAP.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (errMsg.contains(key)) {
errMsg = value;
}
}
}
log.error("AI模型调用异常: {}", errMsg, e);
throw new JeecgBootException(errMsg);

View File

@ -9,7 +9,6 @@ import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiTokenizer;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.query.router.DefaultQueryRouter;
@ -167,7 +166,7 @@ public class EmbeddingHandler implements IEmbeddingHandler {
// 删除旧数据
embeddingStore.removeAll(metadataKey(EMBED_STORE_METADATA_DOCID).isEqualTo(doc.getId()));
// 分段器
DocumentSplitter splitter = DocumentSplitters.recursive(DEFAULT_SEGMENT_SIZE, DEFAULT_OVERLAP_SIZE, new OpenAiTokenizer());
DocumentSplitter splitter = DocumentSplitters.recursive(DEFAULT_SEGMENT_SIZE, DEFAULT_OVERLAP_SIZE);
// 分段并存储
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)

View File

@ -27,7 +27,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

View File

@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jeecg-boot-module</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<version>3.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -13,7 +13,7 @@
<dependencies>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-base-core</artifactId>
</dependency>
</dependencies>

Some files were not shown because too many files have changed in this diff Show More