添加密码字典校验

This commit is contained in:
2023-12-01 17:49:47 +08:00
parent 355e38933b
commit 4950578310
13 changed files with 302 additions and 3 deletions

View File

@@ -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";

View File

@@ -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<PasswordDictDto> list(@RequestBody CommonQuery request) {
return this.service.list(request);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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> {}

View File

@@ -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<PasswordDictEntity> {
int countByPass(String password);
}

View File

@@ -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<PasswordDictEntity, PasswordDictRepository> {
@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<PasswordDictDto> list(CommonQuery query) {
Page<PasswordDictEntity> 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();
}
}
}
}
}

View File

@@ -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();

View File

@@ -173,6 +173,7 @@ public class SysConfigService extends BaseService<SysConfigEntity, SysConfigRepo
Integer.MAX_VALUE);
addDictConfig(all, Constants.SYSCONFIG_PASSWORD_DICT_DETECT, "密码字典检测", "1", "status.type");
}

View File

@@ -9,6 +9,7 @@ import cn.lihongjie.coal.common.Ctx;
import cn.lihongjie.coal.exception.BizException;
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.dto.PermissionSimpleDto;
import cn.lihongjie.coal.permission.entity.PermissionEntity;
import cn.lihongjie.coal.permission.mapper.PermissionMapper;
@@ -18,6 +19,7 @@ import cn.lihongjie.coal.resource.mapper.ResourceMapper;
import cn.lihongjie.coal.resource.service.ResourceService;
import cn.lihongjie.coal.role.entity.RoleEntity;
import cn.lihongjie.coal.role.service.RoleService;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
import cn.lihongjie.coal.user.dto.*;
import cn.lihongjie.coal.user.entity.UserEntity;
import cn.lihongjie.coal.user.mapper.UserMapper;
@@ -65,6 +67,9 @@ public class UserService extends BaseService<UserEntity, UserRepository> {
@Autowired RoleService roleService;
@Autowired SysConfigService sysConfigService;
@Autowired PasswordDictService passwordDictService;
private Pbkdf2PasswordEncoder passwordEncoder;
@PostConstruct
@@ -92,7 +97,11 @@ public class UserService extends BaseService<UserEntity, UserRepository> {
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<UserEntity, UserRepository> {
}
}
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<UserEntity, UserRepository> {
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<UserEntity, UserRepository> {
throw new BizException("两次输入的密码不一致");
}
checkPassDict(request.getNewPassword());
user.setPassword(passwordEncoder.encode(request.getNewPassword()));
repository.save(user);
@@ -191,7 +211,7 @@ public class UserService extends BaseService<UserEntity, UserRepository> {
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<UserEntity, UserRepository> {
adminuser.setUsername("adminuser");
adminuser.setPassword(passwordEncoder.encode("abc@123"));
adminuser.setPassword(passwordEncoder.encode("adminuser@123"));
adminuser.setOrgAdmin(true);
adminuser.setName("超级管理员");

Binary file not shown.