!58 修复:登录失败锁定不生效 、账号删除后依然能登录、用户名枚举漏洞

Merge pull request !58 from 憨涛子/master
This commit is contained in:
1024创新实验室
2025-03-11 07:26:43 +00:00
committed by Gitee
19 changed files with 509 additions and 269 deletions

View File

@@ -49,6 +49,11 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<!-- sa-token start -->
<dependency>
<groupId>cn.dev33</groupId>
@@ -204,7 +209,7 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
@@ -268,7 +273,12 @@
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@@ -161,6 +161,10 @@ public class Level3ProtectConfigService {
this.maxUploadFileSizeMb = configForm.getMaxUploadFileSizeMb();
}
if (configForm.getLoginFailMaxTimes() != null) {
this.loginFailMaxTimes = configForm.getLoginFailMaxTimes();
}
if (configForm.getLoginFailLockMinutes() != null) {
this.loginFailLockSeconds = configForm.getLoginFailLockMinutes() * 60;
}

View File

@@ -2,10 +2,19 @@ package net.lab1024.sa.base.module.support.securityprotect.service;
import jakarta.annotation.Resource;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.exception.TikaException;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.mime.MediaType;
import org.apache.tika.mime.MimeTypes;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* 三级等保 文件上传 相关
@@ -23,6 +32,28 @@ public class SecurityFileService {
@Resource
private Level3ProtectConfigService level3ProtectConfigService;
// 定义白名单MIME类型
private static final List<String> ALLOWED_MIME_TYPES = Arrays.asList(
"application/json",
"application/zip",
"application/x-7z-compressed",
"application/pdf",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-works",
"text/csv",
"audio/*",
"video/*",
// 图片类型 svg有安全隐患所以不使用"image/*"
"image/jpeg",
"image/png",
"image/gif",
"image/bmp"
);
/**
* 检测文件安全类型
@@ -38,15 +69,50 @@ public class SecurityFileService {
}
// 文件类型安全检测
if (!level3ProtectConfigService.isFileDetectFlag()) {
return ResponseDTO.ok();
if (level3ProtectConfigService.isFileDetectFlag()) {
String fileType = getFileMimeType(file);
if(ALLOWED_MIME_TYPES.stream()
.noneMatch(allowedType -> matchesMimeType(fileType, allowedType))){
return ResponseDTO.userErrorParam("禁止上传此文件类型");
}
}
// 检测文件类型
// .....
return ResponseDTO.ok();
}
/**
* 获取文件的 MIME 类型
*
* @param file 要检查的文件
* @return 文件的 MIME 类型
*
*/
public static String getFileMimeType(MultipartFile file) {
try {
TikaConfig tika = new TikaConfig();
Metadata metadata = new Metadata();
metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, file.getOriginalFilename());
TikaInputStream stream = TikaInputStream.get(file.getInputStream());
MediaType mimetype = tika.getDetector().detect(stream, metadata);
return mimetype.toString();
} catch (IOException | TikaException e) {
return MimeTypes.OCTET_STREAM;
}
}
/**
* 检查文件的 MIME 类型是否与指定的MIME 类型匹配(支持通配符)
*
* @param fileType 文件的 MIME 类型
* @param mimetype MIME 类型(支持通配符)
* @return 是否匹配
*/
private static boolean matchesMimeType(String fileType, String mimetype) {
if (mimetype.endsWith("/*")) {
String prefix = mimetype.substring(0, mimetype.length() - 1);
return fileType.startsWith(prefix);
} else {
return fileType.equalsIgnoreCase(mimetype);
}
}
}
;

View File

@@ -8,6 +8,7 @@ import net.lab1024.sa.base.module.support.securityprotect.dao.PasswordLogDao;
import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@@ -46,6 +47,8 @@ public class SecurityPasswordService {
@Resource
private Level3ProtectConfigService level3ProtectConfigService;
static Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
/**
* 校验密码复杂度
*/
@@ -84,8 +87,9 @@ public class SecurityPasswordService {
// 检查最近几次是否有重复密码
List<String> oldPasswords = passwordLogDao.selectOldPassword(requestUser.getUserType().getValue(), requestUser.getUserId(), level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes());
if (oldPasswords != null && oldPasswords.contains(getEncryptPwd(newPassword))) {
return ResponseDTO.userErrorParam(String.format("与前%s个历史密码重复请换个密码!", level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes()));
boolean isDuplicate = oldPasswords.stream().anyMatch(oldPassword -> encoder.matches(newPassword, oldPassword));
if (isDuplicate) {
return ResponseDTO.userErrorParam(String.format("与前%d个历史密码重复请换个密码!", level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes()));
}
return ResponseDTO.ok();
@@ -143,7 +147,14 @@ public class SecurityPasswordService {
* 获取 加密后 的密码
*/
public static String getEncryptPwd(String password) {
return DigestUtils.md5Hex(String.format(PASSWORD_SALT_FORMAT, password));
return encoder.encode(password);
}
/**
* 校验密码是否匹配
*/
public static Boolean matchesPwd( String password, String encodedPassword){
return encoder.matches( password, encodedPassword);
}
}