From b76ae7dc545223858bf408ff2604380f0af27c7c Mon Sep 17 00:00:00 2001 From: zhengyouxian Date: Wed, 8 Jan 2025 06:08:56 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=AE=89=E5=85=A8=E6=95=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/dm/ruoyi-vue-pro-dm8.sql | 2 ++ sql/kingbase/ruoyi-vue-pro.sql | 2 ++ sql/mysql/ruoyi-vue-pro.sql | 1 + sql/oracle/ruoyi-vue-pro.sql | 2 ++ sql/sqlserver/ruoyi-vue-pro.sql | 8 +++++ .../controller/admin/file/FileController.java | 7 ++++ .../app/auth/vo/AppAuthLoginReqVO.java | 4 ++- .../vo/AppMemberUserResetPasswordReqVO.java | 3 +- .../vo/AppMemberUserUpdatePasswordReqVO.java | 3 +- .../admin/auth/vo/AuthLoginReqVO.java | 3 +- .../admin/auth/vo/AuthRegisterReqVO.java | 3 +- .../tenant/vo/tenant/TenantSaveReqVO.java | 3 +- .../UserProfileUpdatePasswordReqVO.java | 7 ++-- .../admin/user/vo/user/UserSaveReqVO.java | 4 ++- .../user/vo/user/UserUpdatePasswordReqVO.java | 5 ++- .../dal/dataobject/permission/UserRoleDO.java | 5 ++- .../dal/mysql/user/AdminUserMapper.java | 10 +++++- .../system/dal/redis/RedisKeyConstants.java | 4 +++ .../config/DataPermissionConfiguration.java | 2 ++ .../service/auth/AdminAuthServiceImpl.java | 32 ++++++++++++++++- .../permission/PermissionServiceImpl.java | 3 ++ .../system/service/user/AdminUserService.java | 16 +++++++++ .../service/user/AdminUserServiceImpl.java | 34 +++++++++++++++++-- 23 files changed, 147 insertions(+), 16 deletions(-) diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql index 0cf97945a..7c5b26b81 100644 --- a/sql/dm/ruoyi-vue-pro-dm8.sql +++ b/sql/dm/ruoyi-vue-pro-dm8.sql @@ -4074,6 +4074,7 @@ CREATE TABLE system_user_role ( id bigint NOT NULL PRIMARY KEY IDENTITY, user_id bigint NOT NULL, role_id bigint NOT NULL, + dept_id bigint NOT NULL, creator varchar(64) DEFAULT '' NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP NULL, updater varchar(64) DEFAULT '' NULL, @@ -4085,6 +4086,7 @@ CREATE TABLE system_user_role ( COMMENT ON COLUMN system_user_role.id IS '自增编号'; COMMENT ON COLUMN system_user_role.user_id IS '用户ID'; COMMENT ON COLUMN system_user_role.role_id IS '角色ID'; +COMMENT ON COLUMN system_user_role.dept_id IS '部门ID'; COMMENT ON COLUMN system_user_role.creator IS '创建者'; COMMENT ON COLUMN system_user_role.create_time IS '创建时间'; COMMENT ON COLUMN system_user_role.updater IS '更新者'; diff --git a/sql/kingbase/ruoyi-vue-pro.sql b/sql/kingbase/ruoyi-vue-pro.sql index 37f3b9b66..6283d85a5 100644 --- a/sql/kingbase/ruoyi-vue-pro.sql +++ b/sql/kingbase/ruoyi-vue-pro.sql @@ -4339,6 +4339,7 @@ CREATE TABLE system_user_role id int8 NOT NULL, user_id int8 NOT NULL, role_id int8 NOT NULL, + dept_id int8 NOT NULL, creator varchar(64) NULL DEFAULT '', create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP, updater varchar(64) NULL DEFAULT '', @@ -4353,6 +4354,7 @@ ALTER TABLE system_user_role COMMENT ON COLUMN system_user_role.id IS '自增编号'; COMMENT ON COLUMN system_user_role.user_id IS '用户ID'; COMMENT ON COLUMN system_user_role.role_id IS '角色ID'; +COMMENT ON COLUMN system_user_role.dept_id IS '部门ID'; COMMENT ON COLUMN system_user_role.creator IS '创建者'; COMMENT ON COLUMN system_user_role.create_time IS '创建时间'; COMMENT ON COLUMN system_user_role.updater IS '更新者'; diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 0c810bef8..264b17894 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -3581,6 +3581,7 @@ CREATE TABLE `system_user_role` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增编号', `user_id` bigint NOT NULL COMMENT '用户ID', `role_id` bigint NOT NULL COMMENT '角色ID', + `dept_id` bigint NOT NULL COMMENT '部门ID', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', diff --git a/sql/oracle/ruoyi-vue-pro.sql b/sql/oracle/ruoyi-vue-pro.sql index 9fbf8d5ad..07264f5fc 100644 --- a/sql/oracle/ruoyi-vue-pro.sql +++ b/sql/oracle/ruoyi-vue-pro.sql @@ -4218,6 +4218,7 @@ CREATE TABLE system_user_role id number NOT NULL, user_id number NOT NULL, role_id number NOT NULL, + dept_id number NOT NULL, creator varchar2(64) DEFAULT '' NULL, create_time date DEFAULT CURRENT_TIMESTAMP NULL, updater varchar2(64) DEFAULT '' NULL, @@ -4232,6 +4233,7 @@ ALTER TABLE system_user_role COMMENT ON COLUMN system_user_role.id IS '自增编号'; COMMENT ON COLUMN system_user_role.user_id IS '用户ID'; COMMENT ON COLUMN system_user_role.role_id IS '角色ID'; +COMMENT ON COLUMN system_user_role.dept_id IS '部门ID'; COMMENT ON COLUMN system_user_role.creator IS '创建者'; COMMENT ON COLUMN system_user_role.create_time IS '创建时间'; COMMENT ON COLUMN system_user_role.updater IS '更新者'; diff --git a/sql/sqlserver/ruoyi-vue-pro.sql b/sql/sqlserver/ruoyi-vue-pro.sql index 9368057ff..b2e0527fd 100644 --- a/sql/sqlserver/ruoyi-vue-pro.sql +++ b/sql/sqlserver/ruoyi-vue-pro.sql @@ -10352,6 +10352,7 @@ CREATE TABLE system_user_role id bigint NOT NULL PRIMARY KEY IDENTITY, user_id bigint NOT NULL, role_id bigint NOT NULL, + dept_id bigint NOT NULL, creator nvarchar(64) DEFAULT '' NULL, create_time datetime2 DEFAULT CURRENT_TIMESTAMP NULL, updater nvarchar(64) DEFAULT '' NULL, @@ -10382,6 +10383,13 @@ EXEC sp_addextendedproperty 'COLUMN', N'role_id' GO +EXEC sp_addextendedproperty + 'MS_Description', N'部门ID', + 'SCHEMA', N'dbo', + 'TABLE', N'system_user_role', + 'COLUMN', N'dept_id' +GO + EXEC sp_addextendedproperty 'MS_Description', N'创建者', 'SCHEMA', N'dbo', diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index 2f92adc0e..cd1698227 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -24,6 +24,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; @@ -42,6 +43,12 @@ public class FileController { public CommonResult uploadFile(FileUploadReqVO uploadReqVO) throws Exception { MultipartFile file = uploadReqVO.getFile(); String path = uploadReqVO.getPath(); + String extname = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")).toLowerCase(); + if(StrUtil.isEmpty(extname)){ + return error(3379,"只能上传图片文件!"); + } + if(!".bmp,.jpg,.jpeg,.png".contains(extname)) + return error(3379,"只能上传图片文件!"); return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()))); } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java index 463f3bb21..471f79f42 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.Mobile; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -28,7 +29,8 @@ public class AppAuthLoginReqVO { @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") @NotEmpty(message = "密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String password; // ========== 绑定社交登录时,需要传递如下参数 ========== diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java index 043c881fd..54e4f5ace 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java @@ -21,7 +21,8 @@ public class AppMemberUserResetPasswordReqVO { @Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") @NotEmpty(message = "新密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String password; @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java index 5319d727e..a533a6579 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java @@ -19,7 +19,8 @@ public class AppMemberUserUpdatePasswordReqVO { @Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") @NotEmpty(message = "新密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String password; @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java index 43b1ea9a4..ca5eb68e4 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java @@ -28,7 +28,8 @@ public class AuthLoginReqVO { @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") @NotEmpty(message = "密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String password; // ========== 图片验证码相关 ========== diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java index fb88a077e..89cd9dc19 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java @@ -26,7 +26,8 @@ public class AuthRegisterReqVO { @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") @NotEmpty(message = "密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String password; // ========== 图片验证码相关 ========== diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java index 175afa34b..f496e3526 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java @@ -57,7 +57,8 @@ public class TenantSaveReqVO { private String username; @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String password; @AssertTrue(message = "用户账号、密码不能为空") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileUpdatePasswordReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileUpdatePasswordReqVO.java index cfe37f620..3cba12f94 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileUpdatePasswordReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileUpdatePasswordReqVO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.controller.admin.user.vo.profile; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.Length; @@ -12,12 +13,14 @@ public class UserProfileUpdatePasswordReqVO { @Schema(description = "旧密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") @NotEmpty(message = "旧密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String oldPassword; @Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "654321") @NotEmpty(message = "新密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String newPassword; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java index bdcbe0a4d..7cadd243f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -67,7 +67,9 @@ public class UserSaveReqVO { // ========== 仅【创建】时,需要传递的字段 ========== @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String password; @AssertTrue(message = "密码不能为空") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserUpdatePasswordReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserUpdatePasswordReqVO.java index 065d0d91f..0c2db2f8c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserUpdatePasswordReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserUpdatePasswordReqVO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.controller.admin.user.vo.user; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.Length; @@ -17,7 +18,9 @@ public class UserUpdatePasswordReqVO { @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") @NotEmpty(message = "密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + + @Length(min = 8, max = 16, message = "密码长度为 8-16 位") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,20}$", message = "用户密码由 数字、字母 特殊字符(@#$%^&+=!之一)8-16位组成") private String password; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/UserRoleDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/UserRoleDO.java index 010184066..317f7aa9a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/UserRoleDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/UserRoleDO.java @@ -31,5 +31,8 @@ public class UserRoleDO extends BaseDO { * 角色 ID */ private Long roleId; - + /** + * 部门 ID + */ + private Long deptId; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/AdminUserMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/AdminUserMapper.java index c0c9be8e4..0303738b3 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/AdminUserMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/user/AdminUserMapper.java @@ -5,8 +5,9 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; -import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.*; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; @@ -47,5 +48,12 @@ public interface AdminUserMapper extends BaseMapperX { default List selectListByDeptIds(Collection deptIds) { return selectList(AdminUserDO::getDeptId, deptIds); } + @Select("SELECT update_time FROM system_user_password_time WHERE user_id = #{userId}") + LocalDateTime getPasswordUpdateRecord(@Param("userId") Long userId); + @Update("UPDATE system_user_password_time SET update_time = CURRENT_TIMESTAMP WHERE user_id = #{userId}") + void update_password_updatetime(@Param("userId") Long userId); + + @Insert("INSERT INTO system_user_password_time(user_id,update_time) values(#{userId},CURRENT_TIMESTAMP)") + void insertPasswordUpdatetime(@Param("userId") Long userId); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java index 1cd58306b..7ef57f395 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java @@ -107,4 +107,8 @@ public interface RedisKeyConstants { */ String WXA_SUBSCRIBE_TEMPLATE = "wxa_subscribe_template"; + /** + * 用户登录错误次数 + */ + String LOGIN_ERROR_TIMES = "user_login_error_times"; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/datapermission/config/DataPermissionConfiguration.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/datapermission/config/DataPermissionConfiguration.java index 136866ca0..2d88b7b65 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/datapermission/config/DataPermissionConfiguration.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/datapermission/config/DataPermissionConfiguration.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.framework.datapermission.config; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; +import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer; import org.springframework.context.annotation.Bean; @@ -20,6 +21,7 @@ public class DataPermissionConfiguration { // dept rule.addDeptColumn(AdminUserDO.class); rule.addDeptColumn(DeptDO.class, "id"); + rule.addDeptColumn(UserRoleDO.class); // user rule.addUserColumn(AdminUserDO.class, "id"); }; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 45b9ee1f5..9eb7adce7 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.module.system.service.auth; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; @@ -14,6 +16,7 @@ import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants; @@ -31,9 +34,13 @@ import jakarta.annotation.Resource; import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.Objects; +import java.util.concurrent.TimeUnit; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; @@ -64,6 +71,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { private CaptchaService captchaService; @Resource private SmsCodeApi smsCodeApi; + @Resource + private StringRedisTemplate stringRedisTemplate; // WxMpService 需要使用到,所以在 Service 注入了它 /** * 验证码的开关,默认为 true @@ -76,9 +85,30 @@ public class AdminAuthServiceImpl implements AdminAuthService { final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; // 校验账号是否存在 AdminUserDO user = userService.getUserByUsername(username); + LocalDateTime localDateTime = LocalDateTime.now(); + LocalDateTime localDateTime1= userService.getPasswordUpdatetime(user.getId()); + if (ChronoUnit.DAYS.between(localDateTime1, localDateTime) >= 90) { + //密码超过90天未修改 + userService.updateUserStatus(user.getId(), 1); + throw exception(new ErrorCode(-333, "超过90天未修改密码,请联系管理员解锁")); + } + if (user == null) { createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); - throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + //用户登录密码错误次数限制 + String key = RedisKeyConstants.LOGIN_ERROR_TIMES + ":" + user.getTenantId() + ":" + username; + String times = stringRedisTemplate.opsForValue().get(key); + if (StrUtil.isEmpty(times)) { + stringRedisTemplate.opsForValue().increment(key); + stringRedisTemplate.expire(key, 1, TimeUnit.DAYS); //一天内 + } + + long _times = stringRedisTemplate.opsForValue().increment(key); + if (_times >= 6) { + userService.updateUserStatus(user.getId(), 1); + throw exception(new ErrorCode(1_002_000_099, "账号密码不正确次数过多,账号已锁定!")); + } else + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); } if (!userService.isPasswordMatch(password, user.getPassword())) { createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java index 2d9e12fe6..f4acfcdeb 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper; import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper; import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; @@ -219,6 +220,8 @@ public class PermissionServiceImpl implements PermissionService { UserRoleDO entity = new UserRoleDO(); entity.setUserId(userId); entity.setRoleId(roleId); + AdminUserDO adminUserDO = userService.getUser(userId); + entity.setDeptId(adminUserDO.getDeptId()); return entity; })); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java index 15564408d..eb21177ba 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import jakarta.validation.Valid; import java.io.InputStream; +import java.time.LocalDateTime; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -215,5 +216,20 @@ public interface AdminUserService { * @return 是否匹配 */ boolean isPasswordMatch(String rawPassword, String encodedPassword); + /** + * 取得密码修改的时间 + * @Author atuchina + * @Date 2024/6/18 10:21 + * @param userId + * @return + */ + LocalDateTime getPasswordUpdatetime(Long userId); + /** + * 插入密码修改时间 + * @Author atuchina + * @Date 2024/6/18 10:37 + * @param userId + */ + void insertPasswordUpdatetime(Long userId); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index ef06d3c29..833e80d7f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -25,6 +25,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.dept.PostService; import cn.iocoder.yudao.module.system.service.permission.PermissionService; @@ -37,6 +38,7 @@ import jakarta.annotation.Resource; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -83,6 +85,8 @@ public class AdminUserServiceImpl implements AdminUserService { private FileApi fileApi; @Resource private ConfigApi configApi; + @Resource + private StringRedisTemplate stringRedisTemplate; // WxMpService 需要使用到,所以在 Service 注入了它 @Override @Transactional(rollbackFor = Exception.class) @@ -129,7 +133,7 @@ public class AdminUserServiceImpl implements AdminUserService { // 2. 插入用户 AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class); - user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 + user.setStatus(CommonStatusEnum.DISABLE.getStatus()); // 默认关闭 user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码 userMapper.insert(user); return user.getId(); @@ -196,6 +200,13 @@ public class AdminUserServiceImpl implements AdminUserService { AdminUserDO updateObj = new AdminUserDO().setId(id); updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码 userMapper.updateById(updateObj); + + //更新密码修改时间表 + if(userMapper.getPasswordUpdateRecord(id)!=null){ + userMapper.update_password_updatetime(id); + }else{ + userMapper.insertPasswordUpdatetime(id); + } } @Override @@ -224,6 +235,7 @@ public class AdminUserServiceImpl implements AdminUserService { updateObj.setPassword(encodePassword(password)); // 加密密码 userMapper.updateById(updateObj); + userMapper.update_password_updatetime(id); // 3. 记录操作日志上下文 LogRecordContext.putVariable("user", user); LogRecordContext.putVariable("newPassword", updateObj.getPassword()); @@ -232,7 +244,11 @@ public class AdminUserServiceImpl implements AdminUserService { @Override public void updateUserStatus(Long id, Integer status) { // 校验用户存在 - validateUserExists(id); + AdminUserDO oldUser = validateUserExists(id); + if(oldUser.getStatus()==1) {//用户登录错误缓存次数重置 + String key = RedisKeyConstants.LOGIN_ERROR_TIMES + ":" + oldUser.getTenantId() + ":" + oldUser.getUsername(); + stringRedisTemplate.delete(key); + } // 更新状态 AdminUserDO updateObj = new AdminUserDO(); updateObj.setId(id); @@ -527,5 +543,17 @@ public class AdminUserServiceImpl implements AdminUserService { private String encodePassword(String password) { return passwordEncoder.encode(password); } - + @Override + public LocalDateTime getPasswordUpdatetime(Long userId){ + LocalDateTime localDateTime = userMapper.getPasswordUpdateRecord(userId); + if(localDateTime==null){ + userMapper.insertPasswordUpdatetime(userId); + return LocalDateTime.now(); + }else + return localDateTime; + } + @Override + public void insertPasswordUpdatetime(Long userId){ + userMapper.insertPasswordUpdatetime(userId); + } } From 8cfc9728fb770cc34e2a3a08ab160eb5b7e37f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=A9=E6=97=A0=E7=BA=BF=E7=94=B5=E9=A3=9EBG8GLR?= <233018@qq.com> Date: Fri, 10 Jan 2025 12:35:15 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E5=89=8D=E5=90=8E=E7=AB=AF=E5=8A=A0?= =?UTF-8?q?=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/filter/front/ResponseDto.java | 23 ++ .../yudao/gateway/util/secret/AESUtils.java | 216 ++++++++++++ .../yudao/gateway/util/secret/MD5Utils.java | 88 +++++ .../yudao/gateway/util/secret/RSAUtils.java | 327 ++++++++++++++++++ .../gateway/util/secret/SaltedHashUtils.java | 75 ++++ .../src/main/resources/application.yaml | 226 +----------- .../java}/application-dev.yaml | 0 .../java}/application-local.yaml | 0 .../src/main/resources/application.yaml | 18 +- .../resources => test}/application-dev.yaml | 0 .../resources => test}/application-local.yaml | 0 .../src/main/resources/application.yaml | 18 +- .../resources/application-dev.yaml | 0 .../resources/application-local.yaml | 22 +- 14 files changed, 790 insertions(+), 223 deletions(-) create mode 100644 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ResponseDto.java create mode 100644 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/AESUtils.java create mode 100644 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/MD5Utils.java create mode 100644 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/RSAUtils.java create mode 100644 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/SaltedHashUtils.java rename yudao-gateway/src/{main/resources => test/java}/application-dev.yaml (100%) rename yudao-gateway/src/{main/resources => test/java}/application-local.yaml (100%) rename yudao-module-infra/yudao-module-infra-biz/src/{main/resources => test}/application-dev.yaml (100%) rename yudao-module-infra/yudao-module-infra-biz/src/{main/resources => test}/application-local.yaml (100%) rename yudao-module-system/yudao-module-system-biz/src/{main => test}/resources/application-dev.yaml (100%) rename yudao-module-system/yudao-module-system-biz/src/{main => test}/resources/application-local.yaml (92%) diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ResponseDto.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ResponseDto.java new file mode 100644 index 000000000..9e149e873 --- /dev/null +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ResponseDto.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.gateway.filter.front; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 响应传输对象 + * + * @author feng + */ +@Data +public class ResponseDto implements Serializable { + @Serial + private static final long serialVersionUID = -6354714160436354659L; + + private Integer code; + + private String msg; + + private Object data; +} \ No newline at end of file diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/AESUtils.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/AESUtils.java new file mode 100644 index 000000000..00c83f97d --- /dev/null +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/AESUtils.java @@ -0,0 +1,216 @@ +package cn.iocoder.yudao.gateway.util.secret; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; + + +/** + * 功能:AES 工具类 + * 说明:对称分组密码算法 + * @author fir + * @date 2020-5-20 11:25 + */ +@Slf4j +@SuppressWarnings("all") +public class AESUtils { + private static final Logger logger = LoggerFactory.getLogger(AESUtils.class); + + public final static String KEY_ALGORITHMS = "AES"; + public final static int KEY_SIZE = 128; + + /** + * 生成AES密钥,base64编码格式 (128) + * @return + * @throws Exception + */ + public static String getKeyAES_128() throws Exception{ + KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHMS); + keyGen.init(KEY_SIZE); + SecretKey key = keyGen.generateKey(); + String base64str = Base64.encodeBase64String(key.getEncoded()); + return base64str; + } + + /** + * 生成AES密钥,base64编码格式 (256) + * @return + * @throws Exception + */ + public static String getKeyAES_256() throws Exception{ + // 256需要换jar包暂时用128 + String base64str = getKeyAES_128(); + return base64str; + } + + /** + * 根据base64Key获取SecretKey对象 + * @param base64Key + * @return + */ + public static SecretKey loadKeyAES(String base64Key) { + byte[] bytes = Base64.decodeBase64(base64Key); + SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, KEY_ALGORITHMS); + return secretKeySpec; + } + + + /** + * 生成SecretKey, base64对象 + * + * @return + */ + public static String generateKeyAES() { + String keyBase64Str = null; + String base64Key = ""; + try { + base64Key = AESUtils.getKeyAES_128(); + } catch (Exception e) { + e.printStackTrace(); + log.error("AES密钥生成失败"); + } + if(!base64Key.equals("")){ + byte[] bytes = Base64.decodeBase64(base64Key); + SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, KEY_ALGORITHMS); + keyBase64Str = Base64.encodeBase64String(secretKeySpec.getEncoded()); + } + return keyBase64Str; + } + + + /** + * AES 加密字符串,SecretKey对象 + * + * @param encryptData + * @param key + * @param encode + * @return + */ + public static String encrypt(String encryptData, SecretKey key, String encode) { + try { + final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] encryptBytes = encryptData.getBytes(encode); + byte[] result = cipher.doFinal(encryptBytes); + return Base64.encodeBase64String(result); + } catch (Exception e) { + logger.error("加密异常:" + e.getMessage()); + return null; + } + } + + /** + * AES 加密字符串,base64Key对象 + * + * @param encryptData + * @param base64Key + * @param encode + * @return + */ + public static String encrypt(String encryptData, String base64Key, String encode) { + SecretKey key = loadKeyAES(base64Key); + try { + final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] encryptBytes = encryptData.getBytes(encode); + byte[] result = cipher.doFinal(encryptBytes); + return Base64.encodeBase64String(result); + } catch (Exception e) { + logger.error("加密异常:" + e.getMessage()); + return null; + } + } + + /** + * AES 加密字符串,base64Key对象 + * + * @param encryptData + * @param base64Key + * @return + */ + public static String encrypt(String encryptData, String base64Key) { + SecretKey key = loadKeyAES(base64Key); + try { + final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] encryptBytes = encryptData.getBytes(String.valueOf(StandardCharsets.UTF_8)); + byte[] result = cipher.doFinal(encryptBytes); + return Base64.encodeBase64String(result); + } catch (Exception e) { + logger.error("加密异常:" + e.getMessage()); + return null; + } + } + + /** + * AES 解密字符串,SecretKey对象 + * + * @param decryptData + * @param key + * @param encode + * @return + */ + public static String decrypt(String decryptData, SecretKey key, String encode) { + try { + final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] decryptBytes = Base64.decodeBase64(decryptData); + byte[] result = cipher.doFinal(decryptBytes); + return new String(result, encode); + } catch (Exception e) { + logger.error("加密异常:" + e.getMessage()); + return null; + } + } + + /** + * AES 解密字符串,base64Key对象 + * + * @param decryptData + * @param base64Key + * @param encode + * @return + */ + public static String decrypt(String decryptData, String base64Key, String encode) { + SecretKey key = loadKeyAES(base64Key); + try { + final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] decryptBytes = Base64.decodeBase64(decryptData); + byte[] result = cipher.doFinal(decryptBytes); + return new String(result, encode); + } catch (Exception e) { + logger.error("加密异常:" + e.getMessage()); + return null; + } + } + + + /** + * AES 解密字符串,base64Key对象 + * + * @param decryptData + * @param base64Key + * @return + */ + public static String decrypt(String decryptData, String base64Key) { + SecretKey key = loadKeyAES(base64Key); + try { + final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] decryptBytes = Base64.decodeBase64(decryptData); + byte[] result = cipher.doFinal(decryptBytes); + return new String(result, String.valueOf(StandardCharsets.UTF_8)); + } catch (Exception e) { + logger.error("解密异常:" + e.getMessage()); + return null; + } + } +} \ No newline at end of file diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/MD5Utils.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/MD5Utils.java new file mode 100644 index 000000000..e1a808a6d --- /dev/null +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/MD5Utils.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.gateway.util.secret; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + + +/** + * @author fir + */ +public class MD5Utils { + private static MessageDigest md; + static { + try { + //初始化摘要对象 + md = MessageDigest.getInstance("md5"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + + /** + * 获得字符串的md5值 + * + * @param str 字符串 + * @return MD5值 + */ + public static String generateMd5ForString(String str){ + //更新摘要数据 + md.update(str.getBytes()); + //生成摘要数组 + byte[] digest = md.digest(); + //清空摘要数据,以便下次使用 + md.reset(); + return formatByteArrayToString(digest); + } + + + /** + * 获得文件的md5值 + * + * @param file 文件对象 + * @return 文件MD5值 + * @throws IOException 文件流读取异常 + */ + public static String generateMd5ForFile(File file) throws IOException { + //创建文件输入流 + FileInputStream fis = new FileInputStream(file); + //将文件中的数据写入md对象 + byte[] buffer = new byte[1024]; + int len; + while ((len = fis.read(buffer)) != -1) { + md.update(buffer, 0, len); + } + fis.close(); + //生成摘要数组 + byte[] digest = md.digest(); + //清空摘要数据,以便下次使用 + md.reset(); + return formatByteArrayToString(digest); + } + + + /** + * 将摘要字节数组转换为md5值 + * + * @param digest 字节数组 + * @return MD5数值 + */ + public static String formatByteArrayToString(byte[] digest) { + //创建sb用于保存md5值 + StringBuilder sb = new StringBuilder(); + int temp; + for (byte b : digest) { + //将数据转化为0到255之间的数据 + temp = b & 0xff; + if (temp < 16) { + sb.append(0); + } + //Integer.toHexString(temp)将10进制数字转换为16进制 + sb.append(Integer.toHexString(temp)); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/RSAUtils.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/RSAUtils.java new file mode 100644 index 000000000..fa7312f93 --- /dev/null +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/RSAUtils.java @@ -0,0 +1,327 @@ +package cn.iocoder.yudao.gateway.util.secret; + +import cn.hutool.core.io.file.FileReader; +import cn.hutool.core.io.file.FileWriter; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; + +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + + +/** + * 非对称加密工具方法 + * + * @author fir + */ +@Slf4j +public class RSAUtils { + + + private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; + /** + * 类型 + */ + public static final String ENCRYPT_TYPE = "RSA"; + + + /** + * 公钥名称 + */ + public static final String PUBLIC_KEY = "publicKey"; + + + /** + * 私钥名称 + */ + public static final String PRIVATE_KEY = "privateKey"; + + + /** + * 生成公钥私钥对 + * + * @return 公钥私钥对 + */ + public static Map generateKey() { + Map map = new HashMap<>(); + KeyPair pair = SecureUtil.generateKeyPair(ENCRYPT_TYPE); + PrivateKey privateKey = pair.getPrivate(); + PublicKey publicKey = pair.getPublic(); + + + String publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded()); + String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded()); + map.put(PUBLIC_KEY, publicKeyStr); + map.put(PRIVATE_KEY, privateKeyStr); + + return map; + } + + + /** + * 从文件中读取公钥 + * + * @param filename 公钥保存路径 + * @return 公钥字符串 + */ + public static String getPublicKey(String filename) { + //默认UTF-8编码,可以在构造中传入第二个参数做为编码 + FileReader fileReader = new FileReader(filename); + return fileReader.readString(); + } + + + /** + * 从文件中读取密钥 + * + * @param filename 私钥保存路径 + * @return 私钥字符串 + */ + public static String getPrivateKey(String filename) { + //默认UTF-8编码,可以在构造中传入第二个参数做为编码 + FileReader fileReader = new FileReader(filename); + return fileReader.readString(); + } + + + /** + * 公钥加密 + * + * @param content 要加密的内容 + * @param publicKey 公钥 + */ + public static String encrypt(String content, PublicKey publicKey) { + try { + RSA rsa = new RSA(null, publicKey); + return rsa.encryptBase64(content, KeyType.PublicKey); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 公钥加密 + * + * @param content 要加密的内容 + * @param publicKey 公钥(base64字符串) + */ + public static String encrypt(String content, String publicKey) { + try { + RSA rsa = new RSA(null, publicKey); + return rsa.encryptBase64(content, KeyType.PublicKey); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 公钥加密-分段加密(解密时需要分段解密) + * + * @param t 要加密的内容(泛型对象,转化为Json字符串加密) + * @param publicKey 公钥(base64字符串) + */ + public static String encryptSection(T t, String publicKey) { + + String content = JSONObject.toJSONString(t); + try { + return encryptSection(content, publicKey); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 公钥加密-分段加密(解密时需要分段解密) + * + * @param content 要加密的内容 + * @param publicKey 公钥(base64字符串) + */ + public static String encryptSection(String content, String publicKey) { + try { + RSA rsa = new RSA(null, publicKey); + + int blockSize = 117; + int encryptedLength = content.length(); + StringBuilder decryptedBlocks = new StringBuilder(); + // 拆分加密文本为块并逐个解密 + for (int i = 0; i < encryptedLength; i += blockSize) { + int b = i + blockSize; + if (b > encryptedLength) { + b = encryptedLength; + } + String block = content.substring(i, b); + String decryptedBlock = rsa.encryptBase64(block, KeyType.PublicKey); + decryptedBlocks.append(decryptedBlock); + } + + return decryptedBlocks.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 私钥解密 + * + * @param content 要解密的内容 + * @param privateKey 私钥 + */ + public static String decrypt(String content, PrivateKey privateKey) { + try { + RSA rsa = new RSA(privateKey, null); + return rsa.decryptStr(content, KeyType.PrivateKey); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 私钥解密 + * + * @param content 要解密的内容 + * @param privateKey 私钥(base64字符串) + */ + public static String decrypt(String content, String privateKey) { + + try { + RSA rsa = new RSA(privateKey, null); + return rsa.decryptStr(content, KeyType.PrivateKey); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 私钥解密-(只能解密分段解密的数据) + * + * @param content 要解密的内容 (只能解密分段解密的数据) + * @param privateKey 私钥(base64字符串) + */ + public static String decryptSection(String content, String privateKey) { + try { + RSA rsa = new RSA(privateKey, null); + + int blockSize = 172; + int encryptedLength = content.length(); + StringBuilder decryptedBlocks = new StringBuilder(); + // 拆分加密文本为块并逐个解密 + for (int i = 0; i < encryptedLength; i += blockSize) { + int b = i + blockSize; + if (b > encryptedLength) { + b = encryptedLength; + } + String block = content.substring(i, b); + String decryptedBlock = rsa.decryptStr(block, KeyType.PrivateKey); + decryptedBlocks.append(decryptedBlock); + + } + + return decryptedBlocks.toString(); + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 获取公私钥-请获取一次后保存公私钥使用 + * + * @param publicKeyFilename 公钥生成的路径 + * @param privateKeyFilename 私钥生成的路径 + */ + public static void generateKeyPair(String publicKeyFilename, String privateKeyFilename) { + try { + KeyPair pair = SecureUtil.generateKeyPair(ENCRYPT_TYPE); + PrivateKey privateKey = pair.getPrivate(); + PublicKey publicKey = pair.getPublic(); + // 获取 公钥和私钥 的 编码格式(通过该 编码格式 可以反过来 生成公钥和私钥对象) + byte[] pubEncBytes = publicKey.getEncoded(); + byte[] priEncBytes = privateKey.getEncoded(); + + // 把 公钥和私钥 的 编码格式 转换为 Base64文本 方便保存 + String pubEncBase64 = Base64.encodeBase64String(pubEncBytes);//new BASE64Encoder().encode(pubEncBytes); + String priEncBase64 = Base64.encodeBase64String(priEncBytes);//new BASE64Encoder().encode(priEncBytes); + + FileWriter pub = new FileWriter(publicKeyFilename); + FileWriter pri = new FileWriter(privateKeyFilename); + pub.write(pubEncBase64); + pri.write(priEncBase64); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + // - - - - - - - - - - - - - - - - - - - - SIGN 签名,验签 - - - - - - - - - - - - - - - - - - - - // + + /** + * 加签:生成报文签名 + * + * @param content 报文内容 + * @param privateKey 私钥 + * @param encode 编码 + * @return 签名 + */ + public static String doSign(String content, String privateKey, String encode) { + try { + String unSign = Base64.encodeBase64String(content.getBytes(StandardCharsets.UTF_8)); + byte[] privateKeys = Base64.decodeBase64(privateKey.getBytes()); + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeys); + KeyFactory mykeyFactory = KeyFactory.getInstance(ENCRYPT_TYPE); + PrivateKey psbcPrivateKey = mykeyFactory.generatePrivate(privateKeySpec); + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initSign(psbcPrivateKey); + signature.update(unSign.getBytes(encode)); + byte[] signed = signature.sign(); + return Base64.encodeBase64String(signed); + } catch (Exception e) { + log.error("生成报文签名出现异常"); + } + return null; + } + + + /** + * 验证:验证签名信息 + * + * @param content 签名报文 + * @param signed 签名信息 + * @param publicKey 公钥 + * @param encode 编码格式 + * @return 通过/失败 + */ + public static boolean doCheck(String content, String signed, PublicKey publicKey, String encode) { + try { + // 解密之前先把content明文,进行base64转码 + String unsigned = Base64.encodeBase64String(content.getBytes(encode)); + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initVerify(publicKey); + signature.update(unsigned.getBytes(encode)); + return signature.verify(Base64.decodeBase64(signed)); + } catch (Exception e) { + log.error("报文验证签名出现异常"); + } + return false; + } +} diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/SaltedHashUtils.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/SaltedHashUtils.java new file mode 100644 index 000000000..6960f0ded --- /dev/null +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/secret/SaltedHashUtils.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.gateway.util.secret; + +import org.springframework.security.crypto.bcrypt.BCrypt; + +import java.security.SecureRandom; + + +/** + * 盐加密函数函数 + * + * @author fir + * @date 2023/7/13 21:19 + */ +public class SaltedHashUtils { + + + /** + * 盐的长度 + */ + private static final int SALT_LENGTH = 16; + + + /** + * 盐值生成 + * + * @return 16位盐值 + */ + public static String generateSalt() { + SecureRandom secureRandom = new SecureRandom(); + byte[] salt = new byte[SALT_LENGTH]; + secureRandom.nextBytes(salt); + return bytesToHex(salt); + } + + + /** + * 密码加盐 + * + * @param password 密码 + * @param salt 盐值 + * @return 加盐数值 + */ + public static String generateHash(String password, String salt) { + String saltedPassword = salt + password; + return BCrypt.hashpw(saltedPassword, BCrypt.gensalt()); + } + + + /** + * 密码,盐 对比 盐后数值 是否相同 + * + * @param password 密码 + * @param salt 盐值 + * @return 相同:true/不相同:false + */ + public static boolean validatePassword(String password, String salt, String hashedPassword) { + String saltedPassword = salt + password; + return BCrypt.checkpw(saltedPassword, hashedPassword); + } + + + /** + * 字节转16进制 + * + * @param bytes 字节流 + * @return 字符串 + */ + private static String bytesToHex(byte[] bytes) { + StringBuilder result = new StringBuilder(); + for (byte b : bytes) { + result.append(String.format("%02x", b)); + } + return result.toString(); + } +} diff --git a/yudao-gateway/src/main/resources/application.yaml b/yudao-gateway/src/main/resources/application.yaml index f59435246..928fb72f1 100644 --- a/yudao-gateway/src/main/resources/application.yaml +++ b/yudao-gateway/src/main/resources/application.yaml @@ -1,4 +1,19 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + spring: + cloud: + nacos: + server-addr: ${NACOS_HOST_ADDR:nacos.default}:${NACOS_HOST_PORT:8848} + username: ${NACOS_USERNAME} + password: ${NACOS_PASSWORD} + discovery: # 【配置中心】配置项 + namespace: ${NACOS_NAMESPACE:ebe55be3-9630-4f18-9f8a-71b578896355} # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: ${NACOS_NAMESPACE:ebe55be3-9630-4f18-9f8a-71b578896355} # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP application: name: gateway-server @@ -13,165 +28,7 @@ spring: - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 - cloud: - # Spring Cloud Gateway 配置项,对应 GatewayProperties 类 - gateway: - # 路由配置项,对应 RouteDefinition 数组 - routes: - ## system-server 服务 - - id: system-admin-api # 路由的编号 - uri: grayLb://system-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/system/** - filters: - - RewritePath=/admin-api/system/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - - id: system-app-api # 路由的编号 - uri: grayLb://system-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/app-api/system/** - filters: - - RewritePath=/app-api/system/v3/api-docs, /v3/api-docs - ## infra-server 服务 - - id: infra-admin-api # 路由的编号 - uri: grayLb://infra-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/infra/** - filters: - - RewritePath=/admin-api/infra/v3/api-docs, /v3/api-docs - - id: infra-app-api # 路由的编号 - uri: grayLb://infra-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/app-api/infra/** - filters: - - RewritePath=/app-api/infra/v3/api-docs, /v3/api-docs - - id: infra-spring-boot-admin # 路由的编号(Spring Boot Admin) - uri: grayLb://infra-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin/** - - id: infra-websocket # 路由的编号(WebSocket) - uri: grayLb://infra-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/infra/ws/** - ## member-server 服务 - - id: member-admin-api # 路由的编号 - uri: grayLb://member-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/member/** - filters: - - RewritePath=/admin-api/member/v3/api-docs, /v3/api-docs - - id: member-app-api # 路由的编号 - uri: grayLb://member-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/app-api/member/** - filters: - - RewritePath=/app-api/member/v3/api-docs, /v3/api-docs - ## bpm-server 服务 - - id: bpm-admin-api # 路由的编号 - uri: grayLb://bpm-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/bpm/** - filters: - - RewritePath=/admin-api/bpm/v3/api-docs, /v3/api-docs - ## report-server 服务 - - id: report-admin-api # 路由的编号 - uri: grayLb://report-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/report/** - filters: - - RewritePath=/admin-api/report/v3/api-docs, /v3/api-docs - - id: report-jimu # 路由的编号(积木报表) - uri: grayLb://report-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/jmreport/** - ## pay-server 服务 - - id: pay-admin-api # 路由的编号 - uri: grayLb://pay-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/pay/** - filters: - - RewritePath=/admin-api/pay/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - - id: pay-app-api # 路由的编号 - uri: grayLb://pay-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/app-api/pay/** - filters: - - RewritePath=/app-api/pay/v3/api-docs, /v3/api-docs - ## mp-server 服务 - - id: mp-admin-api # 路由的编号 - uri: grayLb://mp-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/mp/** - filters: - - RewritePath=/admin-api/mp/v3/api-docs, /v3/api-docs - ## product-server 服务 - - id: product-admin-api # 路由的编号 - uri: grayLb://product-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/product/** - filters: - - RewritePath=/admin-api/product/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - - id: product-app-api # 路由的编号 - uri: grayLb://product-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/app-api/product/** - filters: - - RewritePath=/app-api/product/v3/api-docs, /v3/api-docs - ## promotion-server 服务 - - id: promotion-admin-api # 路由的编号 - uri: grayLb://promotion-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/promotion/** - filters: - - RewritePath=/admin-api/promotion/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - - id: promotion-app-api # 路由的编号 - uri: grayLb://promotion-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/app-api/promotion/** - filters: - - RewritePath=/app-api/promotion/v3/api-docs, /v3/api-docs - ## trade-server 服务 - - id: trade-admin-api # 路由的编号 - uri: grayLb://trade-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/trade/** - filters: - - RewritePath=/admin-api/trade/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - - id: trade-app-api # 路由的编号 - uri: grayLb://trade-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/app-api/trade/** - filters: - - RewritePath=/app-api/trade/v3/api-docs, /v3/api-docs - ## statistics-server 服务 - - id: statistics-admin-api # 路由的编号 - uri: grayLb://statistics-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/statistics/** - filters: - - RewritePath=/admin-api/statistics/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - ## erp-server 服务 - - id: erp-admin-api # 路由的编号 - uri: grayLb://erp-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/erp/** - filters: - - RewritePath=/admin-api/erp/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - ## crm-server 服务 - - id: crm-admin-api # 路由的编号 - uri: grayLb://crm-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/crm/** - filters: - - RewritePath=/admin-api/crm/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - ## ai-server 服务 - - id: ai-admin-api # 路由的编号 - uri: grayLb://ai-server - predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/admin-api/ai/** - filters: - - RewritePath=/admin-api/ai/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - x-forwarded: - prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 + server: port: 48080 @@ -179,54 +36,3 @@ server: logging: file: name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 - -knife4j: - # 聚合 Swagger 文档,参考 https://doc.xiaominfo.com/docs/action/springcloud-gateway 文档 - gateway: - enabled: true - routes: - - name: system-server - service-name: system-server - url: /admin-api/system/v3/api-docs - - name: infra-server - service-name: infra-server - url: /admin-api/infra/v3/api-docs - - name: member-server - service-name: member-server - url: /admin-api/member/v3/api-docs - - name: bpm-server - service-name: bpm-server - url: /admin-api/bpm/v3/api-docs - - name: pay-server - service-name: pay-server - url: /admin-api/pay/v3/api-docs - - name: mp-server - service-name: mp-server - url: /admin-api/mp/v3/api-docs - - name: product-server - service-name: product-server - url: /admin-api/product/v3/api-docs - - name: promotion-server - service-name: promotion-server - url: /admin-api/promotion/v3/api-docs - - name: trade-server - service-name: trade-server - url: /admin-api/trade/v3/api-docs - - name: statistics-server - service-name: statistics-server - url: /admin-api/statistics/v3/api-docs - - name: erp-server - service-name: erp-server - url: /admin-api/erp/v3/api-docs - - name: crm-server - service-name: crm-server - url: /admin-api/crm/v3/api-docs - - name: ai-server - service-name: ai-server - url: /admin-api/ai/v3/api-docs - ---- #################### 芋道相关配置 #################### - -yudao: - info: - version: 1.0.0 \ No newline at end of file diff --git a/yudao-gateway/src/main/resources/application-dev.yaml b/yudao-gateway/src/test/java/application-dev.yaml similarity index 100% rename from yudao-gateway/src/main/resources/application-dev.yaml rename to yudao-gateway/src/test/java/application-dev.yaml diff --git a/yudao-gateway/src/main/resources/application-local.yaml b/yudao-gateway/src/test/java/application-local.yaml similarity index 100% rename from yudao-gateway/src/main/resources/application-local.yaml rename to yudao-gateway/src/test/java/application-local.yaml diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application.yaml b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application.yaml index 51e84e4aa..6758a6855 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application.yaml +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application.yaml @@ -1,4 +1,20 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + spring: + cloud: + nacos: + server-addr: ${NACOS_HOST_ADDR:nacos.default}:${NACOS_HOST_PORT:8848} + username: ${NACOS_USERNAME} + password: ${NACOS_PASSWORD} + discovery: # 【配置中心】配置项 + namespace: ${NACOS_NAMESPACE:ebe55be3-9630-4f18-9f8a-71b578896355} # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: ${NACOS_NAMESPACE:ebe55be3-9630-4f18-9f8a-71b578896355} # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + application: name: infra-server @@ -64,7 +80,7 @@ mybatis-plus: map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 global-config: db-config: - id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + id-type: AUTO # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-dev.yaml b/yudao-module-infra/yudao-module-infra-biz/src/test/application-dev.yaml similarity index 100% rename from yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-dev.yaml rename to yudao-module-infra/yudao-module-infra-biz/src/test/application-dev.yaml diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-local.yaml b/yudao-module-infra/yudao-module-infra-biz/src/test/application-local.yaml similarity index 100% rename from yudao-module-infra/yudao-module-infra-biz/src/main/resources/application-local.yaml rename to yudao-module-infra/yudao-module-infra-biz/src/test/application-local.yaml diff --git a/yudao-module-system/yudao-module-system-biz/src/main/resources/application.yaml b/yudao-module-system/yudao-module-system-biz/src/main/resources/application.yaml index 45470daf4..4ba1208a1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/resources/application.yaml +++ b/yudao-module-system/yudao-module-system-biz/src/main/resources/application.yaml @@ -1,4 +1,20 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + spring: + cloud: + nacos: + server-addr: ${NACOS_HOST_ADDR:nacos.default}:${NACOS_HOST_PORT:8848} + username: ${NACOS_USERNAME} + password: ${NACOS_PASSWORD} + discovery: # 【配置中心】配置项 + namespace: ${NACOS_NAMESPACE:ebe55be3-9630-4f18-9f8a-71b578896355} # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: ${NACOS_NAMESPACE:ebe55be3-9630-4f18-9f8a-71b578896355} # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + application: name: system-server @@ -64,7 +80,7 @@ mybatis-plus: map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 global-config: db-config: - id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + id-type: AUTO # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/resources/application-dev.yaml b/yudao-module-system/yudao-module-system-biz/src/test/resources/application-dev.yaml similarity index 100% rename from yudao-module-system/yudao-module-system-biz/src/main/resources/application-dev.yaml rename to yudao-module-system/yudao-module-system-biz/src/test/resources/application-dev.yaml diff --git a/yudao-module-system/yudao-module-system-biz/src/main/resources/application-local.yaml b/yudao-module-system/yudao-module-system-biz/src/test/resources/application-local.yaml similarity index 92% rename from yudao-module-system/yudao-module-system-biz/src/main/resources/application-local.yaml rename to yudao-module-system/yudao-module-system-biz/src/test/resources/application-local.yaml index c58e49029..fd5ec6cb9 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/resources/application-local.yaml +++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/application-local.yaml @@ -3,16 +3,16 @@ spring: cloud: nacos: - server-addr: 127.0.0.1:8848 # Nacos 服务器地址 - username: # Nacos 账号 - password: # Nacos 密码 + server-addr: myserver:13721 #127.0.0.1:8848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: zyx123123 # Nacos 密码 discovery: # 【配置中心】配置项 - namespace: dev # 命名空间。这里使用 dev 开发环境 + namespace: d5c15366-e804-4c94-8288-5df56e8b7e8a # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP metadata: version: 1.0.0 # 服务实例的版本号,可用于灰度发布 config: # 【注册中心】配置项 - namespace: dev # 命名空间。这里使用 dev 开发环境 + namespace: d5c15366-e804-4c94-8288-5df56e8b7e8a # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP --- #################### 数据库相关配置 #################### @@ -56,14 +56,14 @@ spring: primary: master datasource: master: - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + url: jdbc:mysql://myserver:13722/dlifevpn?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro # SQLServer 连接的示例 # url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例 username: root - password: 123456 + password: Zyx234kk..# # username: sa # SQL Server 连接的示例 # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W # SQL Server 连接的示例 # username: SYSDBA # DM 连接的示例 @@ -72,15 +72,15 @@ spring: lazy: true # 开启懒加载,保证启动速度 url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true username: root - password: 123456 + password: Zyx234kk..# # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 data: redis: - host: 127.0.0.1 # 地址 - port: 6379 # 端口 + host: myserver # 地址 + port: 13723 # 端口 database: 0 # 数据库索引 -# password: 123456 # 密码,建议生产环境开启 + password: 123123123 # 密码,建议生产环境开启 --- #################### MQ 消息队列相关配置 #################### From fb355064d871c4a8f692742fa06bccbfcd73e1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=A9=E6=97=A0=E7=BA=BF=E7=94=B5=E9=A3=9EBG8GLR?= <233018@qq.com> Date: Fri, 10 Jan 2025 12:36:01 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=89=8D=E5=90=8E=E7=AB=AF=E5=8A=A0?= =?UTF-8?q?=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 13 +++++++++++++ yudao-gateway/pom.xml | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/pom.xml b/pom.xml index 990cdb148..8caadfb77 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,19 @@ + + + + + + + + + + + + + diff --git a/yudao-gateway/pom.xml b/yudao-gateway/pom.xml index fd75115d9..57595d54a 100644 --- a/yudao-gateway/pom.xml +++ b/yudao-gateway/pom.xml @@ -69,6 +69,10 @@ com.google.guava guava + + org.springframework.boot + spring-boot-starter-webflux + From 68d75723914e17be91d1f828b1f493721c995531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=A9=E6=97=A0=E7=BA=BF=E7=94=B5=E9=A3=9EBG8GLR?= <233018@qq.com> Date: Fri, 10 Jan 2025 22:11:11 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E5=89=8D=E5=BE=8C=E7=AB=AFrequest=20body?= =?UTF-8?q?=20response=20body=E5=8A=A0=E8=A7=A3=E5=AF=86=EF=BC=8C=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E6=95=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/GatewayServerApplication.java | 3 + .../front/{ResponseDto.java => BodyDto.java} | 6 +- .../filter/front/FrontSecretFilter.java | 239 ++++++++++++++++++ .../front/ParamterSecretProperties.java | 40 +++ .../filter/logging/AccessLogFilter.java | 2 +- .../admin/user/vo/user/UserSaveReqVO.java | 2 +- 6 files changed, 289 insertions(+), 3 deletions(-) rename yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/{ResponseDto.java => BodyDto.java} (74%) create mode 100644 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/FrontSecretFilter.java create mode 100755 yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ParamterSecretProperties.java diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/GatewayServerApplication.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/GatewayServerApplication.java index 99984699f..5da532a82 100644 --- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/GatewayServerApplication.java +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/GatewayServerApplication.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.gateway; +import cn.iocoder.yudao.gateway.filter.front.ParamterSecretProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication +@EnableConfigurationProperties(ParamterSecretProperties.class) public class GatewayServerApplication { public static void main(String[] args) { diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ResponseDto.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/BodyDto.java similarity index 74% rename from yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ResponseDto.java rename to yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/BodyDto.java index 9e149e873..927e85571 100644 --- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ResponseDto.java +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/BodyDto.java @@ -11,7 +11,7 @@ import java.io.Serializable; * @author feng */ @Data -public class ResponseDto implements Serializable { +public class BodyDto implements Serializable { @Serial private static final long serialVersionUID = -6354714160436354659L; @@ -20,4 +20,8 @@ public class ResponseDto implements Serializable { private String msg; private Object data; + + private String requestBody; + + private String responseBody; } \ No newline at end of file diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/FrontSecretFilter.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/FrontSecretFilter.java new file mode 100644 index 000000000..0b9a02c6b --- /dev/null +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/FrontSecretFilter.java @@ -0,0 +1,239 @@ +package cn.iocoder.yudao.gateway.filter.front; + +import cn.hutool.core.codec.Base64Encoder; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.gateway.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.gateway.util.secret.AESUtils; +import cn.iocoder.yudao.gateway.util.secret.RSAUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; +import org.springframework.cloud.gateway.support.BodyInserterContext; +import org.springframework.cloud.gateway.support.NotFoundException; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ReactiveHttpOutputMessage; +import org.springframework.http.codec.CodecConfigurer; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.BodyInserter; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +@Slf4j +@Component +public class FrontSecretFilter implements GlobalFilter, Ordered { + @Resource + private ParamterSecretProperties secretProperties; + + @Resource + private CodecConfigurer codecConfigurer; + + private String aesKey; + private BodyDto bodyDto=new BodyDto(); + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE+98; //在日志之前,以免日志无法打印 + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + // 将 Request 中可以直接获取到的参数,设置到网关日志 + ServerHttpRequest request = exchange.getRequest(); + + //筛选方法,只对get put post处理 + String method = request.getMethod().name(); + if (request.getMethod() != HttpMethod.POST && request.getMethod() != HttpMethod.PUT && request.getMethod() != HttpMethod.PATCH && request.getMethod() != HttpMethod.GET) { + // 如果不是post(新增)、put(全量修改)、patch(部分字段修改)GET(加密返回体) 操作,则直接放行 + return chain.filter(exchange); + } + + String secretHeader = request.getHeaders().getFirst(secretProperties.getHeader()); + if(StrUtil.isEmpty(secretHeader)){ //无头部指示 + return chain.filter(exchange); + } + if(!secretHeader.equals("true")) //头部指示不为true + return chain.filter(exchange); + if(!secretProperties.getEnable()) //系统配置为前后端不加密 + return chain.filter(exchange); + for(String url : secretProperties.getIgnoreUrls()){ //白名单剔除 + if(request.getURI().getPath().contains(url)) + return chain.filter(exchange); + } + + aesKey = exchange.getRequest().getHeaders().getFirst("frontSecKEY");//前端通过header 传递的aes key + try { + //使用rsa解密 key + aesKey = RSAUtils.decrypt(aesKey, secretProperties.getPrivateKey()); + }catch (Exception e){ + log.error("解密前端密钥失败:"+e.getMessage()); + throw NotFoundException.create(true, "Unable to find instance for " + request.getURI().getHost()); + } + // 继续 filter 过滤 + if(method.equals(HttpMethod.POST.name()) || method.equals(HttpMethod.PUT.name())) { + return filterWithRequestBody(exchange, chain); + } + return filterWithoutRequestBody(exchange, chain); //get method 不拦截request + } + + private Mono filterWithRequestBody(ServerWebExchange exchange, GatewayFilterChain chain) { + // 设置 Request Body 读取时,设置到网关日志 + // 此处 codecConfigurer.getReaders() 的目的,是解决 spring.codec.max-in-memory-size 不生效 + ServerRequest serverRequest = ServerRequest.create(exchange, codecConfigurer.getReaders()); + Mono modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> { + //**解码 + try { + String _body = body.substring(1, body.length() - 1); + String originalQuery = AESUtils.decrypt(_body, aesKey); + + bodyDto.setRequestBody(originalQuery); + return Mono.just(originalQuery); + }catch (Exception e){ + + } + return Mono.just(body); + }); + + + // 创建 BodyInserter 对象 + BodyInserter, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); + // 创建 CachedBodyOutputMessage 对象 + HttpHeaders headers = new HttpHeaders(); + headers.putAll(exchange.getRequest().getHeaders()); + // the new content type will be computed by bodyInserter + // and then set in the request decorator + headers.remove(HttpHeaders.CONTENT_LENGTH); // 移除 + CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); + // 通过 BodyInserter 将 Request Body 写入到 CachedBodyOutputMessage 中 + return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> { + // 包装 Request,用于缓存 Request Body + ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage); + // 包装 Response,用于记录 Response Body + ServerHttpResponseDecorator decoratedResponse = encodeResponse(exchange, bodyDto); + return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()); + })); + } + + /** + * 加密响应包 + * 通过 DataBufferFactory 解决响应体分段传输问题。 + * @param exchange + * @param bodyDto + * @return + */ + private ServerHttpResponseDecorator encodeResponse(ServerWebExchange exchange, BodyDto bodyDto) { + ServerHttpResponse response = exchange.getResponse(); + return new ServerHttpResponseDecorator(response) { + + @Override + public Mono writeWith(Publisher body) { + if (body instanceof Flux) { + DataBufferFactory bufferFactory = response.bufferFactory(); + // 获取响应类型,如果是 json 就打印 + String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); + if (StringUtils.isNotBlank(originalResponseContentType) + && originalResponseContentType.contains("application/json")) { + Flux fluxBody = Flux.from(body); + return super.writeWith(fluxBody.buffer().map(dataBuffers -> { + // 设置 response body 到网关日志 + byte[] content = readContent(dataBuffers); + String responseResult = new String(content, StandardCharsets.UTF_8); + //加密 + bodyDto.setResponseBody(responseResult); + log.info("aesKey:"+aesKey); + String _content = AESUtils.encrypt(responseResult, (aesKey)); + log.info("响应整体加密:"+_content); + // 响应 + return bufferFactory.wrap(_content.getBytes()); + })); + } + } + // if body is not a flux. never got there. + return super.writeWith(body); + } + + @Override + public HttpHeaders getHeaders() { + HttpHeaders httpHeaders = getDelegate().getHeaders(); +// if(!httpHeaders.containsKey(secretProperties.getHeader())){ //此处不写也是可以的,在前端可以通过response.requst得到 +// httpHeaders.add(secretProperties.getHeader(), "true"); +// } + return httpHeaders; + } + }; + } + + private Mono filterWithoutRequestBody(ServerWebExchange exchange, GatewayFilterChain chain) { + // 包装 Response,用于记录 Response Body + ServerHttpResponseDecorator decoratedResponse = encodeResponse(exchange, bodyDto); + return chain.filter(exchange.mutate().response(decoratedResponse).build()); + } + + // ========== 参考 ModifyResponseBodyGatewayFilterFactory 中的方法 ========== + + private byte[] readContent(List dataBuffers) { + // 合并多个流集合,解决返回体分段传输 + DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); + DataBuffer join = dataBufferFactory.join(dataBuffers); + byte[] content = new byte[join.readableByteCount()]; + join.read(content); + // 释放掉内存 + DataBufferUtils.release(join); + return content; + } + + /** + * 请求装饰器,支持重新计算 headers、body 缓存 + * + * @param exchange 请求 + * @param headers 请求头 + * @param outputMessage body 缓存 + * @return 请求装饰器 + */ + private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) { + return new ServerHttpRequestDecorator(exchange.getRequest()) { + + @Override + public HttpHeaders getHeaders() { + long contentLength = headers.getContentLength(); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.putAll(super.getHeaders()); + if (contentLength > 0) { + httpHeaders.setContentLength(contentLength); + } else { + // TODO: this causes a 'HTTP/1.1 411 Length Required' // on + // httpbin.org + httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); + } + return httpHeaders; + } + + @Override + public Flux getBody() { + return outputMessage.getBody(); + } + }; + } +} diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ParamterSecretProperties.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ParamterSecretProperties.java new file mode 100755 index 000000000..ff3f02322 --- /dev/null +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/front/ParamterSecretProperties.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.gateway.filter.front; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Set; + +/** + * 参数加密配置属性类 gareway globalfilter没起作用 + * 请求需要带上 ENCPARAMTER=true 的请求头,才会进行参数加密 + * @author atuchina + */ +@ConfigurationProperties(prefix = "yudao.front.secret") +@Data +public class ParamterSecretProperties { + /** + * 参数加是否开启 + */ + private static final Boolean ENABLE_DEFAULT = true; + + /** + * 是否开启 + */ + private Boolean enable = ENABLE_DEFAULT; + + /** + * 需要忽略参数加密的请求 + * + * 这里可配置部分请求是不需要加密的 + */ + private Set ignoreUrls = Collections.emptySet(); + + private String header = "ENCPARAMTER"; // 请求头名称 + + @NotNull(message = "前后端参数加密 密钥 不能为空") + private String privateKey; // 加密秘钥 +} diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/logging/AccessLogFilter.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/logging/AccessLogFilter.java index b01343f8d..e4eea5aa4 100644 --- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/logging/AccessLogFilter.java +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/logging/AccessLogFilter.java @@ -98,7 +98,7 @@ public class AccessLogFilter implements GlobalFilter, Ordered { @Override public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; + return Ordered.HIGHEST_PRECEDENCE+99; } @Override diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java index 7cadd243f..651370187 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -23,7 +23,7 @@ public class UserSaveReqVO { @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") @NotBlank(message = "用户账号不能为空") - @Pattern(regexp = "^[a-zA-Z0-9]$", message = "用户账号由 数字、字母 组成") + @Pattern(regexp = "^[A-Za-z0-9]+$", message = "用户账号由 数字、字母 组成") @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") @DiffLogField(name = "用户账号") private String username;