mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-02-24 06:05:23 +00:00
fix: stabilize auth/decrypt flow and refresh donate account counts
This commit is contained in:
@@ -318,6 +318,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
// 只处理POST/PUT/PATCH等有body的请求方法,避免GET请求读取body导致"Request has already been read"错误
|
// 只处理POST/PUT/PATCH等有body的请求方法,避免GET请求读取body导致"Request has already been read"错误
|
||||||
String httpMethod = ctx.request().method().name();
|
String httpMethod = ctx.request().method().name();
|
||||||
if (("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod))
|
if (("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod))
|
||||||
|
&& ctx.parsedHeaders() != null && ctx.parsedHeaders().contentType() != null
|
||||||
&& HttpHeaderValues.APPLICATION_JSON.toString().equals(ctx.parsedHeaders().contentType().value())
|
&& HttpHeaderValues.APPLICATION_JSON.toString().equals(ctx.parsedHeaders().contentType().value())
|
||||||
&& ctx.body() != null && ctx.body().asJsonObject() != null) {
|
&& ctx.body() != null && ctx.body().asJsonObject() != null) {
|
||||||
JsonObject body = ctx.body().asJsonObject();
|
JsonObject body = ctx.body().asJsonObject();
|
||||||
@@ -340,8 +341,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod))
|
} else if (("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod))
|
||||||
&& ctx.body() != null) {
|
&& ctx.body() != null && ctx.body().length() > 0) {
|
||||||
queryParams.addAll(ParamUtil.paramsToMap(ctx.body().asString()));
|
try {
|
||||||
|
queryParams.addAll(ParamUtil.paramsToMap(ctx.body().asString()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.debug("Failed to parse body as params: {}", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析其他参数
|
// 解析其他参数
|
||||||
@@ -360,6 +365,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
parameterValueList.put(k, ctx.request());
|
parameterValueList.put(k, ctx.request());
|
||||||
} else if (HttpServerResponse.class.getName().equals(v.getRight().getName())) {
|
} else if (HttpServerResponse.class.getName().equals(v.getRight().getName())) {
|
||||||
parameterValueList.put(k, ctx.response());
|
parameterValueList.put(k, ctx.response());
|
||||||
|
} else if (JsonObject.class.getName().equals(v.getRight().getName())) {
|
||||||
|
if (ctx.body() != null && ctx.body().asJsonObject() != null) {
|
||||||
|
parameterValueList.put(k, ctx.body().asJsonObject());
|
||||||
|
} else {
|
||||||
|
parameterValueList.put(k, new JsonObject());
|
||||||
|
}
|
||||||
} else if (parameterValueList.get(k) == null
|
} else if (parameterValueList.get(k) == null
|
||||||
&& CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
|
&& CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
|
||||||
// 绑定实体类
|
// 绑定实体类
|
||||||
@@ -374,6 +385,17 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
});
|
});
|
||||||
// 调用handle 获取响应对象
|
// 调用handle 获取响应对象
|
||||||
Object[] parameterValueArray = parameterValueList.values().toArray(new Object[0]);
|
Object[] parameterValueArray = parameterValueList.values().toArray(new Object[0]);
|
||||||
|
|
||||||
|
// 打印调试信息,确认参数注入的情况
|
||||||
|
if (LOGGER.isDebugEnabled() && method.getName().equals("donateAccount")) {
|
||||||
|
LOGGER.debug("donateAccount parameter list:");
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, Object> entry : parameterValueList.entrySet()) {
|
||||||
|
LOGGER.debug("Param [{}]: {} = {}", i++, entry.getKey(),
|
||||||
|
entry.getValue() != null ? entry.getValue().toString() : "null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 反射调用
|
// 反射调用
|
||||||
Object data = ReflectionUtil.invokeWithArguments(method, instance, parameterValueArray);
|
Object data = ReflectionUtil.invokeWithArguments(method, instance, parameterValueArray);
|
||||||
|
|||||||
@@ -361,7 +361,8 @@
|
|||||||
v-model="showDonateDialog"
|
v-model="showDonateDialog"
|
||||||
title="🎁 捐赠网盘账号"
|
title="🎁 捐赠网盘账号"
|
||||||
width="550px"
|
width="550px"
|
||||||
:close-on-click-modal="false">
|
:close-on-click-modal="false"
|
||||||
|
@open="loadDonateAccountCounts">
|
||||||
<el-alert type="info" :closable="false" show-icon style="margin-bottom: 15px;">
|
<el-alert type="info" :closable="false" show-icon style="margin-bottom: 15px;">
|
||||||
<template #title>
|
<template #title>
|
||||||
捐赠您的网盘 Cookie/Token,解析时将从所有捐赠账号中随机选择使用,分摊请求压力。
|
捐赠您的网盘 Cookie/Token,解析时将从所有捐赠账号中随机选择使用,分摊请求压力。
|
||||||
|
|||||||
@@ -78,12 +78,17 @@ public class AuthParamCodec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: URL解码
|
// Step 1: URL解码(兼容:有些框架已自动解码,此处避免再次把 '+' 变成空格)
|
||||||
String urlDecoded = URLDecoder.decode(encryptedAuth, StandardCharsets.UTF_8);
|
String normalized = encryptedAuth;
|
||||||
log.debug("URL解码结果: {}", urlDecoded);
|
if (normalized.contains("%")) {
|
||||||
|
normalized = URLDecoder.decode(normalized, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
// 兼容 query 参数中 '+' 被还原为空格的情况
|
||||||
|
normalized = normalized.replace(' ', '+');
|
||||||
|
log.debug("认证参数规范化结果: {}", normalized);
|
||||||
|
|
||||||
// Step 2: Base64解码
|
// Step 2: Base64解码
|
||||||
byte[] base64Decoded = Base64.getDecoder().decode(urlDecoded);
|
byte[] base64Decoded = Base64.getDecoder().decode(normalized);
|
||||||
log.debug("Base64解码成功,长度: {}", base64Decoded.length);
|
log.debug("Base64解码成功,长度: {}", base64Decoded.length);
|
||||||
|
|
||||||
// Step 3: AES解密
|
// Step 3: AES解密
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import io.vertx.core.Promise;
|
|||||||
import io.vertx.core.http.HttpServerRequest;
|
import io.vertx.core.http.HttpServerRequest;
|
||||||
import io.vertx.core.http.HttpServerResponse;
|
import io.vertx.core.http.HttpServerResponse;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -484,12 +485,13 @@ public class ParserApi {
|
|||||||
* 捐赠网盘账号
|
* 捐赠网盘账号
|
||||||
*/
|
*/
|
||||||
@RouteMapping(value = "/donateAccount", method = RouteMethod.POST)
|
@RouteMapping(value = "/donateAccount", method = RouteMethod.POST)
|
||||||
public Future<JsonObject> donateAccount(HttpServerRequest request, JsonObject body) {
|
public Future<JsonObject> donateAccount(RoutingContext ctx) {
|
||||||
|
JsonObject body = ctx.body().asJsonObject();
|
||||||
if (body == null || StringUtils.isBlank(body.getString("panType"))
|
if (body == null || StringUtils.isBlank(body.getString("panType"))
|
||||||
|| StringUtils.isBlank(body.getString("authType"))) {
|
|| StringUtils.isBlank(body.getString("authType"))) {
|
||||||
return Future.succeededFuture(JsonResult.error("panType and authType are required").toJsonObject());
|
return Future.succeededFuture(JsonResult.error("panType and authType are required").toJsonObject());
|
||||||
}
|
}
|
||||||
String ip = request.remoteAddress().host();
|
String ip = ctx.request().remoteAddress().host();
|
||||||
body.put("ip", ip);
|
body.put("ip", ip);
|
||||||
return dbService.saveDonatedAccount(body);
|
return dbService.saveDonatedAccount(body);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,11 +366,21 @@ public class DbServiceImpl implements DbService {
|
|||||||
|
|
||||||
return Future.all(usernameFuture, passwordFuture, tokenFuture, failureTokenFuture)
|
return Future.all(usernameFuture, passwordFuture, tokenFuture, failureTokenFuture)
|
||||||
.map(compositeFuture -> {
|
.map(compositeFuture -> {
|
||||||
|
String username = usernameFuture.result();
|
||||||
|
String password = passwordFuture.result();
|
||||||
|
String token = tokenFuture.result();
|
||||||
|
|
||||||
|
// 如果解密后没有任何可用凭证,返回空对象,避免把密文当作明文认证参数下发给前端
|
||||||
|
if (StringUtils.isBlank(username) && StringUtils.isBlank(password) && StringUtils.isBlank(token)) {
|
||||||
|
log.warn("random donated account has no usable credential after decrypt, accountId={}", row.getLong("id"));
|
||||||
|
return JsonResult.data(new JsonObject()).toJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
JsonObject account = new JsonObject();
|
JsonObject account = new JsonObject();
|
||||||
account.put("authType", row.getString("auth_type"));
|
account.put("authType", row.getString("auth_type"));
|
||||||
account.put("username", usernameFuture.result());
|
account.put("username", username);
|
||||||
account.put("password", passwordFuture.result());
|
account.put("password", password);
|
||||||
account.put("token", tokenFuture.result());
|
account.put("token", token);
|
||||||
account.put("donatedAccountToken", failureTokenFuture.result());
|
account.put("donatedAccountToken", failureTokenFuture.result());
|
||||||
return JsonResult.data(account).toJsonObject();
|
return JsonResult.data(account).toJsonObject();
|
||||||
});
|
});
|
||||||
@@ -450,8 +460,10 @@ public class DbServiceImpl implements DbService {
|
|||||||
return Future.succeededFuture(value);
|
return Future.succeededFuture(value);
|
||||||
}
|
}
|
||||||
return CryptoUtil.decrypt(value).recover(e -> {
|
return CryptoUtil.decrypt(value).recover(e -> {
|
||||||
log.warn("decrypt donated account field failed, fallback to plaintext", e);
|
// value 看起来像密文但无法解密,通常是密钥轮换/不一致导致;
|
||||||
return Future.succeededFuture(value);
|
// 不应回退为明文,否则会把密文误当 token/cookie 返回给调用方
|
||||||
|
log.warn("decrypt donated account field failed, fallback to null to avoid ciphertext leakage", e);
|
||||||
|
return Future.succeededFuture((String) null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,15 @@ public class CryptoUtil {
|
|||||||
secretKeyFuture = ConfigUtil.readYamlConfig("secret", vertx)
|
secretKeyFuture = ConfigUtil.readYamlConfig("secret", vertx)
|
||||||
.map(config -> {
|
.map(config -> {
|
||||||
String key = config.getJsonObject("encrypt").getString("key");
|
String key = config.getJsonObject("encrypt").getString("key");
|
||||||
if (key == null || key.length() != 32) {
|
if (key != null) {
|
||||||
throw new IllegalArgumentException("Invalid AES key length in secret.yml. Key must be 32 bytes.");
|
key = key.trim();
|
||||||
}
|
}
|
||||||
return new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
|
byte[] keyBytes = key == null ? null : key.getBytes(StandardCharsets.UTF_8);
|
||||||
|
if (keyBytes == null || keyBytes.length != 32) {
|
||||||
|
int currentLen = keyBytes == null ? 0 : keyBytes.length;
|
||||||
|
throw new IllegalArgumentException("Invalid AES key length in secret.yml. Key must be 32 bytes. current=" + currentLen);
|
||||||
|
}
|
||||||
|
return new SecretKeySpec(keyBytes, "AES");
|
||||||
})
|
})
|
||||||
.onFailure(err -> logger.error("Failed to load encryption key from secret.yml", err));
|
.onFailure(err -> logger.error("Failed to load encryption key from secret.yml", err));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user