commit
This commit is contained in:
@@ -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("系统异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user