diff --git a/src/main/java/cn/lihongjie/coal/common/Constants.java b/src/main/java/cn/lihongjie/coal/common/Constants.java index 7c08bc10..10c61721 100644 --- a/src/main/java/cn/lihongjie/coal/common/Constants.java +++ b/src/main/java/cn/lihongjie/coal/common/Constants.java @@ -39,6 +39,7 @@ public class Constants { public static String SYSCONFIG_SESSION_TIMEOUT = "session_timeout"; public static String SYSCONFIG_ACCOUNT_MAX_ONLINE = "account_max_online"; public static String SYSCONFIG_RESETPWD_ENABLE = "resetpwd_enable"; + public static String SYSCONFIG_PASSWORD_DICT_DETECT = "password_dict_detect"; public static String SYSCONFIG_RESETPWD_TIMEOUT = "resetpwd_timeout"; public static String SYSCONFIG_RESETPWD_MAX_FAIL_COUNT = "resetpwd_max_fail_count"; public static String SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN = "session_global_rate_limit_per_min"; diff --git a/src/main/java/cn/lihongjie/coal/passwordDict/controller/PasswordDictController.java b/src/main/java/cn/lihongjie/coal/passwordDict/controller/PasswordDictController.java new file mode 100644 index 00000000..b11f0d20 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/passwordDict/controller/PasswordDictController.java @@ -0,0 +1,52 @@ +package cn.lihongjie.coal.passwordDict.controller; + +import cn.lihongjie.coal.annotation.SysLog; +import cn.lihongjie.coal.base.dto.CommonQuery; +import cn.lihongjie.coal.base.dto.IdRequest; +import cn.lihongjie.coal.passwordDict.dto.CreatePasswordDictDto; +import cn.lihongjie.coal.passwordDict.dto.PasswordDictDto; +import cn.lihongjie.coal.passwordDict.dto.UpdatePasswordDictDto; +import cn.lihongjie.coal.passwordDict.service.PasswordDictService; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/passwordDict") +@SysLog(module = "常见密码字典") +@Slf4j +public class PasswordDictController { + @Autowired private PasswordDictService service; + + @PostMapping("/create") + public PasswordDictDto create(@RequestBody CreatePasswordDictDto request) { + return this.service.create(request); + } + + @PostMapping("/update") + public PasswordDictDto update(@RequestBody UpdatePasswordDictDto request) { + return this.service.update(request); + } + + @PostMapping("/delete") + public Object delete(@RequestBody IdRequest request) { + this.service.delete(request); + return true; + } + + @PostMapping("/getById") + public PasswordDictDto getById(@RequestBody IdRequest request) { + return this.service.getById(request.getId()); + } + + @PostMapping("/list") + public Page list(@RequestBody CommonQuery request) { + return this.service.list(request); + } +} diff --git a/src/main/java/cn/lihongjie/coal/passwordDict/dto/CreatePasswordDictDto.java b/src/main/java/cn/lihongjie/coal/passwordDict/dto/CreatePasswordDictDto.java new file mode 100644 index 00000000..ece81787 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/passwordDict/dto/CreatePasswordDictDto.java @@ -0,0 +1,11 @@ +package cn.lihongjie.coal.passwordDict.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; + +import lombok.Data; + +@Data +public class CreatePasswordDictDto extends CommonDto { + + private String pass; +} diff --git a/src/main/java/cn/lihongjie/coal/passwordDict/dto/PasswordDictDto.java b/src/main/java/cn/lihongjie/coal/passwordDict/dto/PasswordDictDto.java new file mode 100644 index 00000000..58169239 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/passwordDict/dto/PasswordDictDto.java @@ -0,0 +1,11 @@ +package cn.lihongjie.coal.passwordDict.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; + +import lombok.Data; + +@Data +public class PasswordDictDto extends CommonDto { + + private String pass; +} diff --git a/src/main/java/cn/lihongjie/coal/passwordDict/dto/UpdatePasswordDictDto.java b/src/main/java/cn/lihongjie/coal/passwordDict/dto/UpdatePasswordDictDto.java new file mode 100644 index 00000000..e9092c8a --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/passwordDict/dto/UpdatePasswordDictDto.java @@ -0,0 +1,11 @@ +package cn.lihongjie.coal.passwordDict.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; + +import lombok.Data; + +@Data +public class UpdatePasswordDictDto extends CommonDto { + + private String pass; +} diff --git a/src/main/java/cn/lihongjie/coal/passwordDict/entity/PasswordDictEntity.java b/src/main/java/cn/lihongjie/coal/passwordDict/entity/PasswordDictEntity.java new file mode 100644 index 00000000..a4f4467e --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/passwordDict/entity/PasswordDictEntity.java @@ -0,0 +1,17 @@ +package cn.lihongjie.coal.passwordDict.entity; + +import cn.lihongjie.coal.base.entity.CommonEntity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Index; +import jakarta.persistence.Table; + +import lombok.Data; + +@Data +@Entity +@Table(indexes = {@Index(columnList = "pass", name = "idx_password_dict_pass")}) +public class PasswordDictEntity extends CommonEntity { + + private String pass; +} diff --git a/src/main/java/cn/lihongjie/coal/passwordDict/mapper/PasswordDictMapper.java b/src/main/java/cn/lihongjie/coal/passwordDict/mapper/PasswordDictMapper.java new file mode 100644 index 00000000..6fd325b4 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/passwordDict/mapper/PasswordDictMapper.java @@ -0,0 +1,22 @@ +package cn.lihongjie.coal.passwordDict.mapper; + +import cn.lihongjie.coal.base.mapper.BaseMapper; +import cn.lihongjie.coal.base.mapper.CommonMapper; +import cn.lihongjie.coal.passwordDict.dto.CreatePasswordDictDto; +import cn.lihongjie.coal.passwordDict.dto.PasswordDictDto; +import cn.lihongjie.coal.passwordDict.dto.UpdatePasswordDictDto; +import cn.lihongjie.coal.passwordDict.entity.PasswordDictEntity; + +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; + +@Mapper( + componentModel = org.mapstruct.MappingConstants.ComponentModel.SPRING, + uses = {CommonMapper.class}, + mappingControl = DeepClone.class) +public interface PasswordDictMapper + extends BaseMapper< + PasswordDictEntity, + PasswordDictDto, + CreatePasswordDictDto, + UpdatePasswordDictDto> {} diff --git a/src/main/java/cn/lihongjie/coal/passwordDict/repository/PasswordDictRepository.java b/src/main/java/cn/lihongjie/coal/passwordDict/repository/PasswordDictRepository.java new file mode 100644 index 00000000..93f59faa --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/passwordDict/repository/PasswordDictRepository.java @@ -0,0 +1,11 @@ +package cn.lihongjie.coal.passwordDict.repository; + +import cn.lihongjie.coal.base.dao.BaseRepository; +import cn.lihongjie.coal.passwordDict.entity.PasswordDictEntity; + +import org.springframework.stereotype.Repository; + +@Repository +public interface PasswordDictRepository extends BaseRepository { + int countByPass(String password); +} diff --git a/src/main/java/cn/lihongjie/coal/passwordDict/service/PasswordDictService.java b/src/main/java/cn/lihongjie/coal/passwordDict/service/PasswordDictService.java new file mode 100644 index 00000000..ea44dbb0 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/passwordDict/service/PasswordDictService.java @@ -0,0 +1,137 @@ +package cn.lihongjie.coal.passwordDict.service; + +import cn.lihongjie.coal.base.dto.CommonQuery; +import cn.lihongjie.coal.base.dto.IdRequest; +import cn.lihongjie.coal.base.service.BaseService; +import cn.lihongjie.coal.common.Constants; +import cn.lihongjie.coal.passwordDict.dto.CreatePasswordDictDto; +import cn.lihongjie.coal.passwordDict.dto.PasswordDictDto; +import cn.lihongjie.coal.passwordDict.dto.UpdatePasswordDictDto; +import cn.lihongjie.coal.passwordDict.entity.PasswordDictEntity; +import cn.lihongjie.coal.passwordDict.mapper.PasswordDictMapper; +import cn.lihongjie.coal.passwordDict.repository.PasswordDictRepository; +import cn.lihongjie.coal.sysconfig.service.SysConfigService; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.io.Resource; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +@Service +@Slf4j +@Transactional +public class PasswordDictService extends BaseService { + @Value("classpath:passwordDict/dict.zip") + Resource dictFile; + @Autowired SysConfigService sysConfigService; + @Autowired private PasswordDictRepository repository; + @Autowired private PasswordDictMapper mapper; + @Autowired private ConversionService conversionService; + @PersistenceContext + private EntityManager em; + + public PasswordDictDto create(CreatePasswordDictDto request) { + PasswordDictEntity entity = mapper.toEntity(request); + + this.repository.save(entity); + return getById(entity.getId()); + } + + public PasswordDictDto update(UpdatePasswordDictDto request) { + PasswordDictEntity entity = this.repository.get(request.getId()); + this.mapper.updateEntity(entity, request); + + this.repository.save(entity); + + return getById(entity.getId()); + } + + public void delete(IdRequest request) { + this.repository.deleteAllById(request.getIds()); + } + + public PasswordDictDto getById(String id) { + PasswordDictEntity entity = repository.get(id); + + return mapper.toDto(entity); + } + + public Page list(CommonQuery query) { + Page page = + repository.findAll( + query.specification(conversionService), + PageRequest.of( + query.getPageNo(), + query.getPageSize(), + Sort.by(query.getOrders()))); + + return page.map(this.mapper::toDto); + } + + public boolean isInDict(String password) { + + return this.repository.countByPass(password) > 0; + } + + @SneakyThrows + public void initDict(){ + + if (sysConfigService.isEnable(Constants.SYSCONFIG_PASSWORD_DICT_DETECT)) { + if (this.repository.count() == 0){ + + log.info("开始初始化密码字典"); + AtomicInteger count = new AtomicInteger(); + + try (var i = new ZipInputStream(dictFile.getInputStream())){ + + ZipEntry entry = i.getNextEntry(); + + while (entry != null){ + + BufferedReader reader = new BufferedReader(new InputStreamReader(i)); + + reader.lines().forEach(x -> { + PasswordDictEntity dict = new PasswordDictEntity(); + dict.setPass(x); + + + this.repository.save(dict); + + int increment = count.getAndIncrement(); + + if (increment % 10000 == 0){ + log.info("已经初始化 {} 条密码", increment); + em.flush(); + } + + + }); + + entry = i.getNextEntry(); + } + + em.flush(); + + } + + } + } + } +} diff --git a/src/main/java/cn/lihongjie/coal/runner/InitDataRunner.java b/src/main/java/cn/lihongjie/coal/runner/InitDataRunner.java index bbafa28b..47c27f8a 100644 --- a/src/main/java/cn/lihongjie/coal/runner/InitDataRunner.java +++ b/src/main/java/cn/lihongjie/coal/runner/InitDataRunner.java @@ -4,6 +4,7 @@ import cn.lihongjie.coal.coalParameterDef.service.CoalParameterDefService; import cn.lihongjie.coal.dictionary.service.DictionaryService; import cn.lihongjie.coal.organization.entity.OrganizationEntity; import cn.lihongjie.coal.organization.service.OrganizationService; +import cn.lihongjie.coal.passwordDict.service.PasswordDictService; import cn.lihongjie.coal.permission.service.PermissionService; import cn.lihongjie.coal.resource.service.ResourceService; import cn.lihongjie.coal.script.service.ScriptService; @@ -47,6 +48,8 @@ public class InitDataRunner implements CommandLineRunner { @Autowired UserMapper userMapper; + @Autowired PasswordDictService passwordDictService; + @Override @Transactional public void run(String... args) throws Exception { @@ -74,6 +77,8 @@ public class InitDataRunner implements CommandLineRunner { scriptService.initFromResource(); + passwordDictService.initDict(); + } finally { SecurityContextHolder.clearContext(); diff --git a/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java b/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java index 7fa96d65..e0e523d6 100644 --- a/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java +++ b/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java @@ -173,6 +173,7 @@ public class SysConfigService extends BaseService { @Autowired RoleService roleService; + @Autowired SysConfigService sysConfigService; + + @Autowired PasswordDictService passwordDictService; private Pbkdf2PasswordEncoder passwordEncoder; @PostConstruct @@ -92,7 +97,11 @@ public class UserService extends BaseService { checkDuplicateUserName(request.getUsername()); try { stopWatch.start("encode"); - request.setPassword(passwordEncoder.encode(request.getPassword())); + String password = request.getPassword(); + + checkPassDict(password); + + request.setPassword(passwordEncoder.encode(password)); stopWatch.stop(); UserEntity entity = mapper.toEntity(request); @@ -108,6 +117,14 @@ public class UserService extends BaseService { } } + private void checkPassDict(String password) { + if (sysConfigService.isEnable(Constants.SYSCONFIG_PASSWORD_DICT_DETECT)) { + if (passwordDictService.isInDict(password)) { + throw new BizException("当前密码为常见密码,请重新设置"); + } + } + } + private void checkDuplicateUserName(String username) { if (this.repository.countByUsername(username) > 0) { @@ -119,6 +136,7 @@ public class UserService extends BaseService { StopWatch stopWatch = new StopWatch(); checkDuplicateUserName(request.getUsername()); + checkPassDict(request.getPassword()); try { stopWatch.start("encode"); request.setPassword(passwordEncoder.encode(request.getPassword())); @@ -181,6 +199,8 @@ public class UserService extends BaseService { throw new BizException("两次输入的密码不一致"); } + checkPassDict(request.getNewPassword()); + user.setPassword(passwordEncoder.encode(request.getNewPassword())); repository.save(user); @@ -191,7 +211,7 @@ public class UserService extends BaseService { public void resetPwd(String userId, String password) { UserEntity user = repository.findById(userId).orElseThrow(() -> new BizException("用户不存在")); - + checkPassDict(password); user.setPassword(passwordEncoder.encode(password)); repository.save(user); @@ -240,7 +260,7 @@ public class UserService extends BaseService { adminuser.setUsername("adminuser"); - adminuser.setPassword(passwordEncoder.encode("abc@123")); + adminuser.setPassword(passwordEncoder.encode("adminuser@123")); adminuser.setOrgAdmin(true); adminuser.setName("超级管理员"); diff --git a/src/main/resources/passwordDict/dict.zip b/src/main/resources/passwordDict/dict.zip new file mode 100644 index 00000000..392c8708 Binary files /dev/null and b/src/main/resources/passwordDict/dict.zip differ