mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-04-23 09:06:55 +00:00
fix: 修复演练场输入密码后提示未授权访问的问题
根本原因:框架 RouterHandlerFactory 未注册 SessionHandler, 导致 ctx.session() 始终返回 null。登录时密码校验通过但认证 状态被静默丢弃,后续所有请求均返回"未授权访问"。 修复方案:将 Session 鉴权改为 Token(Bearer)鉴权: - PlaygroundConfig: 新增 generateToken()/validateToken(), 使用 SecureRandom 生成密码学安全 Token,并在生成时 清理过期 Token 防止内存泄漏 - PlaygroundApi: login() 返回 Token;checkAuth() 从 Authorization 请求头中读取并校验 Token - playgroundApi.js: 添加请求拦截器自动携带 Token; login() 从响应中提取并保存 Token 到 localStorage - Playground.vue: 后端报告未认证时同步清除 playground_token Agent-Logs-Url: https://github.com/qaiu/netdisk-fast-download/sessions/52144d13-cd49-4a3d-b279-9b8d6cbad757 Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2161190d9a
commit
2f55294b58
@@ -4,6 +4,10 @@ import io.vertx.core.json.JsonObject;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* JS演练场配置
|
||||
*
|
||||
@@ -12,11 +16,21 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Data
|
||||
@Slf4j
|
||||
public class PlaygroundConfig {
|
||||
|
||||
|
||||
/** Token有效期:24小时 */
|
||||
private static final long TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000L;
|
||||
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
/**
|
||||
* 单例实例
|
||||
*/
|
||||
private static PlaygroundConfig instance;
|
||||
|
||||
/**
|
||||
* 已颁发的认证Token及其创建时间
|
||||
*/
|
||||
private final Map<String, Long> validTokens = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 是否启用演练场
|
||||
@@ -41,6 +55,39 @@ public class PlaygroundConfig {
|
||||
*/
|
||||
private PlaygroundConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并存储一个新的认证Token,同时清理过期Token
|
||||
*/
|
||||
public String generateToken() {
|
||||
// 清理过期Token,防止Map无限增长
|
||||
long now = System.currentTimeMillis();
|
||||
validTokens.entrySet().removeIf(e -> now - e.getValue() > TOKEN_EXPIRY_MS);
|
||||
// 使用SecureRandom生成32字节的密码学安全Token
|
||||
byte[] bytes = new byte[32];
|
||||
SECURE_RANDOM.nextBytes(bytes);
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
String token = sb.toString();
|
||||
validTokens.put(token, now);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验Token是否合法且未过期
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
if (token == null || token.isEmpty()) return false;
|
||||
Long createdAt = validTokens.get(token);
|
||||
if (createdAt == null) return false;
|
||||
if (System.currentTimeMillis() - createdAt > TOKEN_EXPIRY_MS) {
|
||||
validTokens.remove(token);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
|
||||
@@ -21,7 +21,6 @@ import io.vertx.core.http.HttpServerRequest;
|
||||
import io.vertx.core.http.HttpServerResponse;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.Session;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -47,7 +46,6 @@ public class PlaygroundApi {
|
||||
|
||||
private static final int MAX_PARSER_COUNT = 100;
|
||||
private static final int MAX_CODE_LENGTH = 128 * 1024; // 128KB 代码长度限制
|
||||
private static final String SESSION_AUTH_KEY = "playgroundAuthed";
|
||||
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
||||
|
||||
/**
|
||||
@@ -74,14 +72,14 @@ public class PlaygroundApi {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 否则检查Session中的认证状态
|
||||
Session session = ctx.session();
|
||||
if (session == null) {
|
||||
return false;
|
||||
// 检查 Authorization: Bearer <token> 请求头
|
||||
String authHeader = ctx.request().getHeader("Authorization");
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ") && authHeader.length() > 7) {
|
||||
String token = authHeader.substring(7).trim();
|
||||
return config.validateToken(token);
|
||||
}
|
||||
|
||||
Boolean authed = session.get(SESSION_AUTH_KEY);
|
||||
return authed != null && authed;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,12 +114,8 @@ public class PlaygroundApi {
|
||||
try {
|
||||
PlaygroundConfig config = PlaygroundConfig.getInstance();
|
||||
|
||||
// 如果是公开模式,直接成功
|
||||
// 如果是公开模式,直接成功(不需要token)
|
||||
if (config.isPublic()) {
|
||||
Session session = ctx.session();
|
||||
if (session != null) {
|
||||
session.put(SESSION_AUTH_KEY, true);
|
||||
}
|
||||
promise.complete(JsonResult.success("公开模式,无需密码").toJsonObject());
|
||||
return promise.future();
|
||||
}
|
||||
@@ -137,11 +131,9 @@ public class PlaygroundApi {
|
||||
|
||||
// 验证密码
|
||||
if (config.getPassword().equals(password)) {
|
||||
Session session = ctx.session();
|
||||
if (session != null) {
|
||||
session.put(SESSION_AUTH_KEY, true);
|
||||
}
|
||||
promise.complete(JsonResult.success("登录成功").toJsonObject());
|
||||
String token = config.generateToken();
|
||||
JsonObject tokenData = new JsonObject().put("token", token);
|
||||
promise.complete(JsonResult.data(tokenData).toJsonObject());
|
||||
} else {
|
||||
promise.complete(JsonResult.error("密码错误").toJsonObject());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user