forked from github/cool-admin-java
fix: RBAC模型P3安全修复
P3-13: 密码改用BCrypt+MD5兼容模式 P3-14: 角色继承-增加parentId递归合并父角色权限 P3-15: 权限变更审计日志-新增base_sys_perms_audit_log表
This commit is contained in:
@@ -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()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<BaseSysPermsAuditLogEntity> {
|
||||
|
||||
@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;
|
||||
}
|
||||
@@ -36,6 +36,9 @@ public class BaseSysRoleEntity extends TenantEntity<BaseSysRoleEntity> {
|
||||
@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<Long> menuIdList;
|
||||
|
||||
@@ -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<BaseSysPermsAuditLogEntity> {
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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<BaseSysMenuEntity> menus = getMenus(roleIds);
|
||||
List<BaseSysMenuEntity> menus = getMenus(expandWithParentRoles(roleIds));
|
||||
Set<String> 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<BaseSysMenuEntity> getMenus(Long[] roleIds) {
|
||||
return getMenusInner(expandWithParentRoles(roleIds));
|
||||
}
|
||||
|
||||
private List<BaseSysMenuEntity> 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<BaseSysRoleMenuEntity> 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<BaseSysUserRoleEntity> 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<Long> expanded = new LinkedHashSet<>(Arrays.asList(roleIds));
|
||||
for (Long roleId : roleIds) {
|
||||
collectParentRoleIds(roleId, expanded);
|
||||
}
|
||||
return expanded.toArray(Long[]::new);
|
||||
}
|
||||
|
||||
private void collectParentRoleIds(Long roleId, Set<Long> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BaseSysUserMapper, B
|
||||
|
||||
final private BaseSysRoleMapper baseSysRoleMapper;
|
||||
|
||||
final private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public Object page(JSONObject requestParams, Page<BaseSysUserEntity> page, QueryWrapper qw) {
|
||||
String keyWord = requestParams.getStr("keyWord");
|
||||
@@ -131,9 +133,8 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl<BaseSysUserMapper, B
|
||||
userEntity.setNickName(body.getStr("nickName"));
|
||||
userEntity.setHeadImg(body.getStr("headImg"));
|
||||
if (StrUtil.isNotEmpty(body.getStr("password")) && StrUtil.isNotEmpty(body.getStr("newPassword"))) {
|
||||
String oldPasswordMd5 = MD5.create().digestHex(body.getStr("password"));
|
||||
CoolPreconditions.check(!oldPasswordMd5.equals(userEntity.getPassword()), "原密码错误");
|
||||
userEntity.setPassword(MD5.create().digestHex(body.getStr("newPassword")));
|
||||
CoolPreconditions.check(!passwordEncoder.matches(body.getStr("password"), userEntity.getPassword()), "原密码错误");
|
||||
userEntity.setPassword(passwordEncoder.encode(body.getStr("newPassword")));
|
||||
userEntity.setPasswordV(userEntity.getPasswordV() + 1);
|
||||
coolCache.set("admin:passwordVersion:" + userId, userEntity.getPasswordV());
|
||||
}
|
||||
@@ -153,7 +154,7 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl<BaseSysUserMapper, B
|
||||
BaseSysUserEntity check = getOne(
|
||||
QueryWrapper.create().eq(BaseSysUserEntity::getUsername, entity.getUsername()));
|
||||
CoolPreconditions.check(check != null, "用户名已存在");
|
||||
entity.setPassword(MD5.create().digestHex(entity.getPassword()));
|
||||
entity.setPassword(passwordEncoder.encode(entity.getPassword()));
|
||||
super.add(requestParams, entity);
|
||||
return entity.getId();
|
||||
}
|
||||
@@ -167,7 +168,7 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl<BaseSysUserMapper, B
|
||||
String requestPassword = requestParams.getStr("password");
|
||||
if (StrUtil.isNotEmpty(requestPassword)) {
|
||||
entity.setPasswordV(userEntity.getPasswordV() + 1);
|
||||
entity.setPassword(MD5.create().digestHex(requestPassword));
|
||||
entity.setPassword(passwordEncoder.encode(requestPassword));
|
||||
coolCache.set("admin:passwordVersion:" + entity.getId(), entity.getPasswordV());
|
||||
} else {
|
||||
entity.setPassword(userEntity.getPassword());
|
||||
|
||||
Reference in New Issue
Block a user