This commit is contained in:
@jyuhsin
2025-10-29 10:47:50 +08:00
parent a3fd6a1c26
commit 6e47110458
9 changed files with 258 additions and 15 deletions
@@ -5,6 +5,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
@@ -17,6 +18,12 @@ import com.google.common.net.HttpHeaders;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.io.RandomAccessRead;
import org.apache.pdfbox.io.RandomAccessReadBuffer;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.nutz.dao.Cnd;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -26,8 +33,10 @@ import org.nutz.mvc.upload.UploadAdaptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.List;
@IocBean
@@ -62,6 +71,20 @@ public class SysFileController {
if (file == null) {
return Result.error("文件内容为空");
}
String submittedFileName = file.getSubmittedFileName();
if (submittedFileName.contains("pdf")) {
try {
byte[] pdfBytes = Files.readAllBytes(file.getFile().toPath());
if (isPdfContainsJavaScript(pdfBytes)) {
//throw new BaseException("禁止上传包含 JavaScript 的 PDF 文件");
throw new BaseException("系统检测到此 PDF 文件具有一定危险性");
}
} catch (IOException e) {
log.warn("PDF 安全扫描失败,文件可能损坏", e);
// 可选择拒绝或放行(建议拒绝)
throw new BaseException("PDF 文件解析失败,请上传合法文件");
}
}
String id = sysFileService.uploadReturnUrl(SysFileEngineTypeEnum.MINIO.getValue(), file);
return Result.success().addData(id);
} catch (Exception e) {
@@ -207,4 +230,33 @@ public class SysFileController {
}
}
public boolean isPdfContainsJavaScript(byte[] pdfBytes) throws IOException {
try (PDDocument document = Loader.loadPDF(new RandomAccessReadBuffer(pdfBytes))) {
COSDictionary catalog = document.getDocumentCatalog().getCOSObject();
// 1. 检查 /Names -> /JavaScript
if (catalog.containsKey(COSName.NAMES)) {
COSDictionary names = (COSDictionary) catalog.getDictionaryObject(COSName.NAMES);
if (names != null && names.containsKey(COSName.JAVA_SCRIPT)) {
return true;
}
}
// 2. 检查 /OpenAction(可能指向 JS action
if (catalog.containsKey(COSName.OPEN_ACTION)) {
var openAction = catalog.getDictionaryObject(COSName.OPEN_ACTION);
if (openAction instanceof COSDictionary) {
COSDictionary actionDict = (COSDictionary) openAction;
if (COSName.JAVA_SCRIPT.equals(actionDict.getDictionaryObject(COSName.S))) {
return true;
}
}
}
// 3. (可选)检查 AcroForm 中的 JS(更复杂,通常用于表单)
// 可根据安全需求决定是否实现
return false;
}
}
}
@@ -11,6 +11,7 @@ import com.budwk.app.base.enums.LoginType;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.interceptor.sLog.SLogService;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.RsaUtils;
import com.budwk.app.sys.models.Sys_log;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysUserService;
@@ -39,6 +40,15 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
import java.util.UUID;
@IocBean
@@ -84,15 +94,25 @@ public class SysLoginController {
@At("/doLogin")
@Ok("json")
@ApiOperation("用户本地账号密码登录")
public Object doLogin(@Param("username") String username, @Param("password") String password, @Param("platformKey") String captchaKey, @Param("platformCaptcha") String captchaCode, HttpServletRequest req, HttpServletResponse response, HttpSession session) {
public Object doLogin(@Param("username") String username,
@Param("password") String password,
@Param("platformKey") String captchaKey,
@Param("platformCaptcha") String captchaCode,
@Param("keyId") String keyId,
HttpServletRequest req,
HttpServletResponse response,
HttpSession session) {
if (StrUtil.isBlank(username)) {
return Result.error("用户名不能为空");
}
if (StrUtil.isBlank(password)) {
return Result.error("密码不能为空");
}
if (StrUtil.isBlank(keyId)) {
return Result.error("参数缺失");
}
String lockKey = RedisConstant.USER_LOGIN_LOCK_PREFIX + username;
String lockKey = RedisConstant.USER_LOGIN_LOCK_PREFIX + username;
int errCount = Convert.toInt(StrUtil.blankToDefault(redisService.get(lockKey), "0"));
log.info("用户名:" + username + "登录失败次数:" + errCount);
@@ -108,8 +128,14 @@ public class SysLoginController {
return Result.error(e.getMessage());
}
// 用户名密码校验
Sys_user user = sysUserService.loginByPassword(username, password);
// 解密密码
String decryptPwd = validateService.decryptPwd(keyId, password);
if (decryptPwd == null) {
throw new BaseException("用户登录失败");
}
// 用户名密码校验
Sys_user user = sysUserService.loginByPassword(username, decryptPwd);
if (user == null) {
throw new BaseException("用户登录失败");
}
@@ -234,4 +260,30 @@ public class SysLoginController {
return Result.success(validateService.getCaptcha());
}
@At("/publicKey")
@Ok("json")
@ApiOperation("获取公钥")
public Object publicKey() {
try {
// 生成密钥对
KeyPair keyPair = RsaUtils.generateKeyPair();
String publicKeyStr = RsaUtils.getPublicKeyBase64(keyPair.getPublic());
String privateKeyStr = RsaUtils.getPrivateKeyBase64(keyPair.getPrivate());
// 生成 UUID 作为 keyId
String keyId = UUID.randomUUID().toString().replace("-", "");
// 私钥存入 Redis,5 分钟过期,也防止恶意刷密钥
redisService.setex(RedisConstant.RSA_KEY_PREFIX + keyId, 5 * 60, privateKeyStr);
// 返回给前端
return Result.success(Map.of(
"publicKey", publicKeyStr,
"keyId", keyId
));
} catch (Exception e) {
log.error("生成 RSA 密钥失败", e);
return Result.error("系统异常");
}
}
}