1. 调整 databus 模块写入日志的时机,解决获取不到租户的问题
This commit is contained in:
@@ -59,7 +59,8 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final ApiGatewayAccessLogger accessLogger;
|
private final ApiGatewayAccessLogger accessLogger;
|
||||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {};
|
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
@@ -68,32 +69,38 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
// 仅处理配置的 API 门户路径,不符合的请求直接放行
|
// 仅处理配置的 API 门户路径,不符合的请求直接放行
|
||||||
boolean matchesPortalPath = properties.getAllBasePaths()
|
boolean matchesPortalPath = properties.getAllBasePaths()
|
||||||
.stream()
|
.stream()
|
||||||
.map(this::normalizeBasePath)
|
|
||||||
.anyMatch(basePath -> pathMatcher.match(basePath + "/**", pathWithinApplication));
|
.anyMatch(basePath -> pathMatcher.match(basePath + "/**", pathWithinApplication));
|
||||||
if (!matchesPortalPath) {
|
if (!matchesPortalPath) {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Long accessLogId = accessLogger.logEntrance(request);
|
Long accessLogId = null;
|
||||||
// 校验访问 IP 是否落在允许范围内
|
|
||||||
if (!isIpAllowed(request)) {
|
|
||||||
log.warn("[API-PORTAL] 拦截来自 IP {} 访问 {} 的请求", request.getRemoteAddr(), pathWithinApplication);
|
|
||||||
response.sendError(HttpStatus.FORBIDDEN.value(), "IP 禁止访问");
|
|
||||||
accessLogger.finalizeEarly(request, HttpStatus.FORBIDDEN.value(), "IP 禁止访问");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ApiGatewayProperties.Security security = properties.getSecurity();
|
ApiGatewayProperties.Security security = properties.getSecurity();
|
||||||
ApiClientCredentialDO credential = null;
|
ApiClientCredentialDO credential = null;
|
||||||
if (!security.isEnabled()) {
|
Long tenantId = null;
|
||||||
byte[] originalBody = StreamUtils.copyToByteArray(request.getInputStream());
|
|
||||||
CachedBodyHttpServletRequest passthroughRequest = new CachedBodyHttpServletRequest(request, originalBody);
|
|
||||||
ApiGatewayAccessLogger.propagateLogIdHeader(passthroughRequest, accessLogId);
|
|
||||||
filterChain.doFilter(passthroughRequest, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean dispatchedToGateway = false;
|
boolean dispatchedToGateway = false;
|
||||||
try {
|
try {
|
||||||
Long tenantId = resolveTenantId(request);
|
tenantId = resolveTenantId(request);
|
||||||
|
if (tenantId != null) {
|
||||||
|
// 绑定租户上下文,保证访问日志与后续数据库操作可写入 tenantId
|
||||||
|
TenantContextHolder.setTenantId(tenantId);
|
||||||
|
}
|
||||||
|
if (!isIpAllowed(request)) {
|
||||||
|
log.warn("[API-PORTAL] 拦截来自 IP {} 访问 {} 的请求", request.getRemoteAddr(), pathWithinApplication);
|
||||||
|
accessLogId = accessLogger.logEntrance(request);
|
||||||
|
response.sendError(HttpStatus.FORBIDDEN.value(), "IP 禁止访问");
|
||||||
|
accessLogger.finalizeEarly(request, HttpStatus.FORBIDDEN.value(), "IP 禁止访问");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// IP 校验通过后再补录入口日志,避免无租户信息写库
|
||||||
|
accessLogId = accessLogger.logEntrance(request);
|
||||||
|
if (!security.isEnabled()) {
|
||||||
|
byte[] originalBody = StreamUtils.copyToByteArray(request.getInputStream());
|
||||||
|
CachedBodyHttpServletRequest passthroughRequest = new CachedBodyHttpServletRequest(request, originalBody);
|
||||||
|
ApiGatewayAccessLogger.propagateLogIdHeader(passthroughRequest, accessLogId);
|
||||||
|
filterChain.doFilter(passthroughRequest, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 从请求头解析 appId 并加载客户端凭证,包含匿名访问配置
|
// 从请求头解析 appId 并加载客户端凭证,包含匿名访问配置
|
||||||
String appId = requireHeader(request, APP_ID_HEADER, "缺少应用标识");
|
String appId = requireHeader(request, APP_ID_HEADER, "缺少应用标识");
|
||||||
credential = credentialService.findActiveCredential(appId)
|
credential = credentialService.findActiveCredential(appId)
|
||||||
@@ -144,17 +151,27 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
responseWrapper.copyBodyToResponse();
|
responseWrapper.copyBodyToResponse();
|
||||||
}
|
}
|
||||||
} catch (SecurityValidationException ex) {
|
} catch (SecurityValidationException ex) {
|
||||||
|
if (accessLogId == null) {
|
||||||
|
accessLogId = accessLogger.logEntrance(request);
|
||||||
|
}
|
||||||
log.warn("[API-PORTAL] 安全校验失败: {}", ex.getMessage());
|
log.warn("[API-PORTAL] 安全校验失败: {}", ex.getMessage());
|
||||||
writeErrorResponse(response, security, credential, ex.status(), ex.getMessage());
|
writeErrorResponse(response, security, credential, ex.status(), ex.getMessage());
|
||||||
if (!dispatchedToGateway) {
|
if (!dispatchedToGateway) {
|
||||||
accessLogger.finalizeEarly(request, ex.status().value(), ex.getMessage());
|
accessLogger.finalizeEarly(request, ex.status().value(), ex.getMessage());
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
if (accessLogId == null) {
|
||||||
|
accessLogId = accessLogger.logEntrance(request);
|
||||||
|
}
|
||||||
log.error("[API-PORTAL] 处理安全校验时出现异常", ex);
|
log.error("[API-PORTAL] 处理安全校验时出现异常", ex);
|
||||||
writeErrorResponse(response, security, credential, HttpStatus.INTERNAL_SERVER_ERROR, "网关安全校验失败");
|
writeErrorResponse(response, security, credential, HttpStatus.INTERNAL_SERVER_ERROR, "网关安全校验失败");
|
||||||
if (!dispatchedToGateway) {
|
if (!dispatchedToGateway) {
|
||||||
accessLogger.finalizeEarly(request, HttpStatus.INTERNAL_SERVER_ERROR.value(), "网关安全校验失败");
|
accessLogger.finalizeEarly(request, HttpStatus.INTERNAL_SERVER_ERROR.value(), "网关安全校验失败");
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (tenantId != null) {
|
||||||
|
TenantContextHolder.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,15 +194,6 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
return requestUri;
|
return requestUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizeBasePath(String basePath) {
|
|
||||||
String candidate = StringUtils.hasText(basePath) ? basePath : ApiGatewayProperties.DEFAULT_BASE_PATH;
|
|
||||||
candidate = candidate.startsWith("/") ? candidate : "/" + candidate;
|
|
||||||
if (candidate.endsWith("/")) {
|
|
||||||
candidate = candidate.substring(0, candidate.length() - 1);
|
|
||||||
}
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long resolveTenantId(HttpServletRequest request) {
|
private Long resolveTenantId(HttpServletRequest request) {
|
||||||
if (!properties.isEnableTenantHeader()) {
|
if (!properties.isEnableTenantHeader()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -306,7 +314,6 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
});
|
});
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
log.debug("[API-PORTAL] 解析查询串 {} 失败", queryString, ex);
|
log.debug("[API-PORTAL] 解析查询串 {} 失败", queryString, ex);
|
||||||
target.put("query", queryString);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +328,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
if (bodyText.startsWith("{")) {
|
if (bodyText.startsWith("{")) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> bodyMap = objectMapper.readValue(bodyText, MAP_TYPE);
|
Map<String, Object> bodyMap = objectMapper.readValue(bodyText, MAP_TYPE);
|
||||||
bodyMap.forEach((key, value) -> target.put(key, normalizeValue(value)));
|
target.putAll(bodyMap);
|
||||||
return;
|
return;
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
log.debug("[API-PORTAL] 解析请求体 JSON 失败", ex);
|
log.debug("[API-PORTAL] 解析请求体 JSON 失败", ex);
|
||||||
@@ -330,20 +337,6 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
target.put("body", bodyText);
|
target.put("body", bodyText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object normalizeValue(Object value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (value instanceof Map || value instanceof List) {
|
|
||||||
try {
|
|
||||||
return objectMapper.writeValueAsString(value);
|
|
||||||
} catch (JsonProcessingException ex) {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveEncryptionType(ApiClientCredentialDO credential, ApiGatewayProperties.Security security) {
|
private String resolveEncryptionType(ApiClientCredentialDO credential, ApiGatewayProperties.Security security) {
|
||||||
if (credential != null && StringUtils.hasText(credential.getEncryptionType())) {
|
if (credential != null && StringUtils.hasText(credential.getEncryptionType())) {
|
||||||
return credential.getEncryptionType();
|
return credential.getEncryptionType();
|
||||||
@@ -481,12 +474,12 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
response.resetBuffer();
|
response.resetBuffer();
|
||||||
response.setStatus(status.value());
|
response.setStatus(status.value());
|
||||||
String resolvedMessage = StringUtils.hasText(message) ? message : status.getReasonPhrase();
|
String resolvedMessage = StringUtils.hasText(message) ? message : status.getReasonPhrase();
|
||||||
String traceId = TracerUtils.getTraceId();
|
String traceId = TracerUtils.getTraceId();
|
||||||
ApiGatewayResponse envelope = ApiGatewayResponse.builder()
|
ApiGatewayResponse envelope = ApiGatewayResponse.builder()
|
||||||
.code(status.value())
|
.code(status.value())
|
||||||
.message(resolvedMessage)
|
.message(resolvedMessage)
|
||||||
.response(null)
|
.response(null)
|
||||||
.traceId(traceId)
|
.traceId(traceId)
|
||||||
.build();
|
.build();
|
||||||
if (shouldEncryptErrorResponse(security, credential)) {
|
if (shouldEncryptErrorResponse(security, credential)) {
|
||||||
String encryptionKey = credential.getEncryptionKey();
|
String encryptionKey = credential.getEncryptionKey();
|
||||||
|
|||||||
Reference in New Issue
Block a user