feat: PSK 版本化管理 + DB 表 + 管理后台增删启禁

- V4 迁移: psks 表 (version, secret, description, is_active)
- PskService: 启动导入 PAR_MEDIABOT_PSK → v1, verify 遍历所有活跃 PSK
- HmacAuthInterceptor: PSK 签名优先验签,精简为 Bearer/PSK 双路径
- AdminController: GET/POST /psks + toggle 启禁
- AuthController: register/recover 改用 PskService 验证
- docker-compose.yml 新增 PAR_MEDIABOT_PSK 环境变量

mediabot 端只需: X-Signature = HMAC(PSK, 'email:ts')
This commit is contained in:
mediabot-pt
2026-06-29 16:52:20 +08:00
parent 11d0840e42
commit 5304449de6
8 changed files with 224 additions and 72 deletions

View File

@@ -10,7 +10,9 @@ import com.par.core.mapper.AccountMapper;
import com.par.core.mapper.ConfigReviewMapper;
import com.par.core.mapper.SiteConfigMapper;
import com.par.core.mapper.SiteMapper;
import com.par.core.entity.Psk;
import com.par.core.service.AccountService;
import com.par.core.service.PskService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -19,6 +21,7 @@ import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.List;
/**
@@ -36,6 +39,7 @@ public class AdminController {
private final AccountMapper accountMapper;
private final SiteMapper siteMapper;
private final AccountService accountService;
private final PskService pskService;
/**
* 获取所有用户列表
@@ -221,6 +225,34 @@ public class AdminController {
return ApiResponse.success(accountService.listEmails(accountId));
}
// ===================== PSK 管理 =====================
@GetMapping("/psks")
public ApiResponse<List<Psk>> listPsks(HttpServletRequest request) {
checkAdmin(request);
return ApiResponse.success(pskService.listAll());
}
@PostMapping("/psks")
public ApiResponse<Psk> addPsk(@RequestBody Map<String, String> body, HttpServletRequest request) {
checkAdmin(request);
String version = body.get("version");
String secret = body.get("secret");
String description = body.get("description");
if (version == null || secret == null) {
return ApiResponse.error(400, "version and secret are required");
}
return ApiResponse.success(pskService.add(version, secret, description));
}
@PostMapping("/psks/{id}/toggle")
public ApiResponse<Void> togglePsk(@PathVariable Long id, @RequestParam("active") boolean active,
HttpServletRequest request) {
checkAdmin(request);
pskService.toggle(id, active);
return ApiResponse.success();
}
private void checkAdmin(HttpServletRequest request) {
TrustLevel level = (TrustLevel) request.getAttribute("trustLevel");
if (level == null || level != TrustLevel.ADMIN) {

View File

@@ -1,6 +1,5 @@
package com.par.api.controller;
import com.par.common.util.HmacUtil;
import com.par.core.dto.AccountDTO;
import com.par.core.dto.ApiResponse;
import com.par.core.dto.LoginRequest;
@@ -8,10 +7,10 @@ import com.par.core.dto.RegisterRequest;
import com.par.core.entity.Account;
import com.par.core.interceptor.HmacAuthInterceptor;
import com.par.core.service.AccountService;
import com.par.core.service.PskService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.LinkedHashMap;
@@ -27,13 +26,13 @@ public class AuthController {
private final AccountService accountService;
private final HmacAuthInterceptor hmacAuthInterceptor;
private final PskService pskService;
@Value("${par.mediabot.psk:}")
private String mediabotPsk;
public AuthController(AccountService accountService, HmacAuthInterceptor hmacAuthInterceptor) {
public AuthController(AccountService accountService, HmacAuthInterceptor hmacAuthInterceptor,
PskService pskService) {
this.accountService = accountService;
this.hmacAuthInterceptor = hmacAuthInterceptor;
this.pskService = pskService;
}
/**
@@ -116,15 +115,14 @@ public class AuthController {
}
/**
* 验证 X-Medibot-Timestamp + X-Medibot-Signature
* 验证 X-Medibot-Timestamp + X-Medibot-Signature(通过 PskService)
*/
private boolean verifyMediabotPsk(HttpServletRequest request, String email) {
String ts = request.getHeader("X-Medibot-Timestamp");
String sig = request.getHeader("X-Medibot-Signature");
if (ts == null || sig == null) return false;
try {
long timestamp = Long.parseLong(ts);
return HmacUtil.verifyMediabotPsk(email, timestamp, sig, mediabotPsk);
return pskService.verify(email, Long.parseLong(ts), sig) != null;
} catch (NumberFormatException e) {
return false;
}