From 13a90c25733dbf36ea54ea7b4c213852902ac374 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Sat, 18 Jul 2026 18:24:36 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20RBAC=E6=A8=A1=E5=9E=8BP3=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P3-13: 密码改用BCrypt+MD5兼容模式 P3-14: 角色继承-增加parentId递归合并父角色权限 P3-15: 权限变更审计日志-新增base_sys_perms_audit_log表 --- .../cool/core/security/JwtSecurityConfig.java | 11 +++- .../sys/BaseSysPermsAuditLogEntity.java | 41 ++++++++++++ .../base/entity/sys/BaseSysRoleEntity.java | 3 + .../sys/BaseSysPermsAuditLogMapper.java | 7 +++ .../sys/impl/BaseSysLoginServiceImpl.java | 8 +-- .../sys/impl/BaseSysPermsServiceImpl.java | 63 ++++++++++++++++++- .../sys/impl/BaseSysUserServiceImpl.java | 13 ++-- 7 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/cool/modules/base/entity/sys/BaseSysPermsAuditLogEntity.java create mode 100644 src/main/java/com/cool/modules/base/mapper/sys/BaseSysPermsAuditLogMapper.java diff --git a/src/main/java/com/cool/core/security/JwtSecurityConfig.java b/src/main/java/com/cool/core/security/JwtSecurityConfig.java index b0f2c23..c49be0b 100644 --- a/src/main/java/com/cool/core/security/JwtSecurityConfig.java +++ b/src/main/java/com/cool/core/security/JwtSecurityConfig.java @@ -22,6 +22,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -125,15 +126,19 @@ public class JwtSecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new PasswordEncoder() { + private final BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder(); + @Override public String encode(CharSequence rawPassword) { - return DigestUtils.md5DigestAsHex(((String) rawPassword).getBytes()); + return bcrypt.encode(rawPassword); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { - return encodedPassword.equals( - DigestUtils.md5DigestAsHex(((String) rawPassword).getBytes())); + if (encodedPassword.startsWith("$2a$") || encodedPassword.startsWith("$2b$") || encodedPassword.startsWith("$2y$")) { + return bcrypt.matches(rawPassword, encodedPassword); + } + return encodedPassword.equals(DigestUtils.md5DigestAsHex(((String) rawPassword).getBytes())); } }; } diff --git a/src/main/java/com/cool/modules/base/entity/sys/BaseSysPermsAuditLogEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysPermsAuditLogEntity.java new file mode 100644 index 0000000..98635e1 --- /dev/null +++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysPermsAuditLogEntity.java @@ -0,0 +1,41 @@ +package com.cool.modules.base.entity.sys; + +import com.cool.core.base.BaseEntity; +import com.cool.core.annotation.ColumnDefine; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; +import com.cool.core.mybatis.handler.Fastjson2TypeHandler; +import lombok.Getter; +import lombok.Setter; +import org.dromara.autotable.annotation.Index; + +@Getter +@Setter +@Table(value = "base_sys_perms_audit_log", comment = "权限变更审计日志表") +public class BaseSysPermsAuditLogEntity extends BaseEntity { + + @Index + @ColumnDefine(comment = "操作用户ID", type = "bigint") + private Long operatorUserId; + + @ColumnDefine(comment = "操作用户名") + private String operatorUsername; + + @ColumnDefine(comment = "变更类型: ROLE_ASSIGN/PERMS_UPDATE/ROLE_DISABLE/ROLE_ENABLE/USER_ROLE_UPDATE") + private String changeType; + + @Index + @ColumnDefine(comment = "目标ID(角色ID或用户ID)", type = "bigint") + private Long targetId; + + @ColumnDefine(comment = "变更前值", type = "json") + @Column(typeHandler = Fastjson2TypeHandler.class) + private Object beforeValue; + + @ColumnDefine(comment = "变更后值", type = "json") + @Column(typeHandler = Fastjson2TypeHandler.class) + private Object afterValue; + + @ColumnDefine(comment = "IP地址", length = 50) + private String ip; +} diff --git a/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java index e2a690e..f61d26e 100644 --- a/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java +++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java @@ -36,6 +36,9 @@ public class BaseSysRoleEntity extends TenantEntity { @ColumnDefine(comment = "数据权限是否关联上下级", defaultValue = "1") private Integer relevance; + @ColumnDefine(comment = "父角色ID", type = "bigint") + private Long parentId; + @ColumnDefine(comment = "菜单权限", type = "json") @Column(typeHandler = Fastjson2TypeHandler.class) private List menuIdList; diff --git a/src/main/java/com/cool/modules/base/mapper/sys/BaseSysPermsAuditLogMapper.java b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysPermsAuditLogMapper.java new file mode 100644 index 0000000..b24b9e1 --- /dev/null +++ b/src/main/java/com/cool/modules/base/mapper/sys/BaseSysPermsAuditLogMapper.java @@ -0,0 +1,7 @@ +package com.cool.modules.base.mapper.sys; + +import com.mybatisflex.core.BaseMapper; +import com.cool.modules.base.entity.sys.BaseSysPermsAuditLogEntity; + +public interface BaseSysPermsAuditLogMapper extends BaseMapper { +} diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java index afb5afa..066dbbe 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysLoginServiceImpl.java @@ -8,7 +8,6 @@ import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.digest.MD5; import cn.hutool.jwt.JWT; import com.cool.core.cache.CoolCache; import com.cool.core.enums.UserTypeEnum; @@ -16,8 +15,7 @@ import com.cool.core.exception.CoolPreconditions; import com.cool.core.security.jwt.JwtTokenUtil; import com.cool.core.util.CoolSecurityUtil; import com.cool.modules.base.dto.sys.BaseSysLoginDto; -import com.cool.modules.base.dto.sys.BaseSysRegisterDto; -import com.cool.modules.base.entity.sys.BaseSysDepartmentEntity; +import com.cool.modules.base.dto.sys.BaseSysRegisterDto;import com.cool.modules.base.entity.sys.BaseSysDepartmentEntity; import com.cool.modules.base.entity.sys.BaseSysRoleEntity; import com.cool.modules.base.entity.sys.BaseSysUserEntity; import com.cool.modules.base.mapper.sys.BaseSysDepartmentMapper; @@ -54,6 +52,8 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService { private final BaseSysPermsService baseSysPermsService; + private final org.springframework.security.crypto.password.PasswordEncoder passwordEncoder; + @org.springframework.beans.factory.annotation.Value("${cool.captcha.enable:true}") private boolean captchaEnable; @@ -149,7 +149,7 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService { CoolPreconditions.check(defaultDepartment == null, "系统未配置部门,请联系管理员"); BaseSysUserEntity userEntity = new BaseSysUserEntity(); userEntity.setUsername(baseSysRegisterDto.getUsername()); - userEntity.setPassword(MD5.create().digestHex(baseSysRegisterDto.getPassword())); + userEntity.setPassword(passwordEncoder.encode(baseSysRegisterDto.getPassword())); userEntity.setPasswordV(1); userEntity.setNickName(StrUtil.isNotEmpty(baseSysRegisterDto.getNickName()) ? baseSysRegisterDto.getNickName() : baseSysRegisterDto.getUsername()); diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java index 7c169f3..cb782f1 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysPermsServiceImpl.java @@ -23,11 +23,13 @@ import java.util.*; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service @RequiredArgsConstructor public class BaseSysPermsServiceImpl implements BaseSysPermsService { @@ -49,6 +51,8 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { final private ExecutorService cachedThreadPool; + final private BaseSysPermsAuditLogMapper baseSysPermsAuditLogMapper; + @Override public Long[] loginDepartmentIds() { String username = CoolSecurityUtil.getAdminUsername(); @@ -162,7 +166,7 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { @Override public String[] getPerms(Long[] roleIds) { - List menus = getMenus(roleIds); + List menus = getMenus(expandWithParentRoles(roleIds)); Set perms = new HashSet<>(); String[] permsData = menus.stream().map(BaseSysMenuEntity::getPerms) .filter(itemPerms -> !StrUtil.isEmpty(itemPerms)).toArray(String[]::new); @@ -174,6 +178,10 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { @Override public List getMenus(Long[] roleIds) { + return getMenusInner(expandWithParentRoles(roleIds)); + } + + private List getMenusInner(Long[] roleIds) { if (CollUtil.toList(roleIds).contains(1L)) { roleIds = null; } @@ -213,6 +221,12 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { @Override @Transactional(rollbackFor = Exception.class) public void updatePerms(Long roleId, Long[] menuIdList, Long[] departmentIds) { + Long[] beforeMenuIds = baseSysRoleMenuMapper.selectListByQuery( + QueryWrapper.create().eq(BaseSysRoleMenuEntity::getRoleId, roleId)) + .stream().map(BaseSysRoleMenuEntity::getMenuId).toArray(Long[]::new); + Long[] beforeDeptIds = baseSysRoleDepartmentMapper.selectListByQuery( + QueryWrapper.create().eq(BaseSysRoleDepartmentEntity::getRoleId, roleId)) + .stream().map(BaseSysRoleDepartmentEntity::getDepartmentId).toArray(Long[]::new); // 更新菜单权限 baseSysRoleMenuMapper.deleteByQuery(QueryWrapper.create().eq(BaseSysRoleMenuEntity::getRoleId, roleId)); List batchRoleMenuList = new ArrayList<>(); @@ -239,6 +253,9 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { baseSysRoleDepartmentMapper.insertBatch(batchRoleDepartmentList); } SpringContextUtils.getBean(MySecurityMetadataSource.class).clearCache(); + logPermsAudit("PERMS_UPDATE", roleId, + Dict.create().set("menuIds", beforeMenuIds).set("deptIds", beforeDeptIds), + Dict.create().set("menuIds", menuIdList).set("deptIds", departmentIds)); cachedThreadPool.submit(() -> { // 刷新对应角色用户的权限 List userRoles = baseSysUserRoleMapper @@ -252,6 +269,9 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { @Override @Transactional(rollbackFor = Exception.class) public void updateUserRole(Long userId, Long[] roleIdList) { + Long[] beforeRoleIds = baseSysUserRoleMapper.selectListByQuery( + QueryWrapper.create().eq(BaseSysUserRoleEntity::getUserId, userId)) + .stream().map(BaseSysUserRoleEntity::getRoleId).toArray(Long[]::new); baseSysUserRoleMapper.deleteByQuery(QueryWrapper.create().eq(BaseSysUserRoleEntity::getUserId, userId)); if (roleIdList == null) { roleIdList = new Long[0]; @@ -262,6 +282,9 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { sysUserRoleEntity.setUserId(userId); baseSysUserRoleMapper.insert(sysUserRoleEntity); } + logPermsAudit("USER_ROLE_UPDATE", userId, + Dict.create().set("roleIds", beforeRoleIds), + Dict.create().set("roleIds", roleIdList)); refreshPerms(userId); } @@ -322,4 +345,42 @@ public class BaseSysPermsServiceImpl implements BaseSysPermsService { } return getDepartmentIdsByUserId(CoolSecurityUtil.getCurrentUserId()); } + + private Long[] expandWithParentRoles(Long[] roleIds) { + if (ObjectUtil.isEmpty(roleIds)) { + return roleIds; + } + Set expanded = new LinkedHashSet<>(Arrays.asList(roleIds)); + for (Long roleId : roleIds) { + collectParentRoleIds(roleId, expanded); + } + return expanded.toArray(Long[]::new); + } + + private void collectParentRoleIds(Long roleId, Set result) { + BaseSysRoleEntity role = baseSysRoleMapper.selectOneById(roleId); + if (role == null || role.getParentId() == null || result.contains(role.getParentId())) { + return; + } + BaseSysRoleEntity parentRole = baseSysRoleMapper.selectOneById(role.getParentId()); + if (parentRole != null && parentRole.getStatus() != 0) { + result.add(parentRole.getId()); + collectParentRoleIds(parentRole.getId(), result); + } + } + + private void logPermsAudit(String changeType, Long targetId, Object beforeValue, Object afterValue) { + try { + BaseSysPermsAuditLogEntity auditLog = new BaseSysPermsAuditLogEntity(); + auditLog.setOperatorUserId(CoolSecurityUtil.getCurrentUserId()); + auditLog.setOperatorUsername(CoolSecurityUtil.getAdminUsername()); + auditLog.setChangeType(changeType); + auditLog.setTargetId(targetId); + auditLog.setBeforeValue(beforeValue); + auditLog.setAfterValue(afterValue); + baseSysPermsAuditLogMapper.insertSelective(auditLog); + } catch (Exception e) { + log.warn("记录权限审计日志失败: {}", e.getMessage()); + } + } } diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java index a03f123..ab33532 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java @@ -12,7 +12,6 @@ import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.digest.MD5; import cn.hutool.json.JSONObject; import com.cool.core.base.BaseServiceImpl; import com.cool.core.base.ModifyEnum; @@ -34,6 +33,7 @@ import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.update.UpdateChain; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.Collection; @@ -57,6 +57,8 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl page, QueryWrapper qw) { String keyWord = requestParams.getStr("keyWord"); @@ -131,9 +133,8 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl