|
|
@@ -17,6 +17,10 @@ import jakarta.servlet.http.HttpServletResponse;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.http.MediaType;
|
|
|
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
|
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
|
+import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
import org.springframework.web.filter.OncePerRequestFilter;
|
|
|
import org.springframework.web.util.ContentCachingRequestWrapper;
|
|
|
@@ -24,7 +28,9 @@ import org.springframework.web.util.ContentCachingRequestWrapper;
|
|
|
import java.io.IOException;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.time.OffsetDateTime;
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.UUID;
|
|
|
|
|
|
/**
|
|
|
* 租户 API Key 认证过滤器 — 完整镜像 Python app/core/apikey.py L16-136 的 TenantApiKeyAuth
|
|
|
@@ -96,7 +102,10 @@ public class TenantApiKeyAuthFilter extends OncePerRequestFilter {
|
|
|
@Override
|
|
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
|
|
// 仅拦截 OpenAPI 账户端点 (Python: OpenapiRouter 中使用了 TenantApiKeyAuth 的路由)
|
|
|
- String path = request.getRequestURI();
|
|
|
+ // 注意: 必须用 getServletPath()(不含 context-path)而非 getRequestURI()。
|
|
|
+ // 后端 context-path=/api/v1,getRequestURI() 返回 /api/v1/payment/openapi/...
|
|
|
+ // 与 OPENAPI_ACCOUNT_PATHS 精确匹配永远不命中,导致 API Key 认证被整体跳过。
|
|
|
+ String path = request.getServletPath();
|
|
|
for (String p : OPENAPI_ACCOUNT_PATHS) {
|
|
|
if (path.equals(p)) {
|
|
|
return false; // 需要过滤
|
|
|
@@ -179,6 +188,24 @@ public class TenantApiKeyAuthFilter extends OncePerRequestFilter {
|
|
|
wrappedRequest.setAttribute("openapi.apiKey", apiKey);
|
|
|
wrappedRequest.setAttribute("openapi.apiKeyId", keyEntity.getId());
|
|
|
|
|
|
+ // ---- 6b. 写入 SecurityContext ----
|
|
|
+ // SecurityConfig 的 anyRequest().authenticated() 需要非匿名认证才能通过授权。
|
|
|
+ // 若只设置 request attribute 而不设置 SecurityContext,授权过滤器会把请求视为
|
|
|
+ // 匿名 → 默认 403 空响应(对齐 JwtAuthFilter 认证成功后的 setAuthentication)。
|
|
|
+ // 注意: principal 必须是 LoginUser(带 tenantId)而非裸 Long —
|
|
|
+ // TenantInnerInterceptor.getTenantId() 只识别 LoginUser,裸 Long 会让后续
|
|
|
+ // 业务查询(pay_account 等)被追加 tenant_id=0 而查不到数据。
|
|
|
+ LoginUser openapiUser = new LoginUser();
|
|
|
+ openapiUser.setTenantId(keyEntity.getTenantId());
|
|
|
+ openapiUser.setUsername("openapi:" + apiKey);
|
|
|
+ openapiUser.setStatus("0");
|
|
|
+ openapiUser.setIsSuperuser(false);
|
|
|
+ openapiUser.setAuthorities(List.of(new SimpleGrantedAuthority("ROLE_OPENAPI")));
|
|
|
+ UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
|
|
+ openapiUser, null, openapiUser.getAuthorities());
|
|
|
+ authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
|
+ SecurityContextHolder.getContext().setAuthentication(authToken);
|
|
|
+
|
|
|
// ---- 7. 记录成功日志 (Python L109-119) ----
|
|
|
logApiCall(keyEntity.getId(), keyEntity.getTenantId(), request, response, 200, startTime);
|
|
|
|
|
|
@@ -251,6 +278,8 @@ public class TenantApiKeyAuthFilter extends OncePerRequestFilter {
|
|
|
int responseCode, long startTime) {
|
|
|
try {
|
|
|
TenantApiLogEntity logEntry = new TenantApiLogEntity();
|
|
|
+ // sys_tenant_api_log.uuid NOT NULL 且无默认值,必须显式赋值(对齐 ApikeyService.create 模式)
|
|
|
+ logEntry.setUuid(UUID.randomUUID().toString());
|
|
|
logEntry.setApiKeyId(apiKeyId);
|
|
|
if (tenantId != null) logEntry.setTenantId(tenantId);
|
|
|
logEntry.setEndpoint(request.getRequestURI()); // Python L160: endpoint
|
|
|
@@ -260,6 +289,7 @@ public class TenantApiKeyAuthFilter extends OncePerRequestFilter {
|
|
|
// Python L167: response_time = (time.time() - start_time) * 1000 (毫秒)
|
|
|
float responseTimeMs = (System.currentTimeMillis() - startTime);
|
|
|
logEntry.setResponseTime(responseTimeMs);
|
|
|
+ logEntry.setTenantId(tenantId);
|
|
|
// Python L173: request_data = None (避免记录敏感数据)
|
|
|
logEntry.setRequestData(null);
|
|
|
|