fix: close phase4 overseas acceptance gaps
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.baiyue.gateway.config;
|
||||
|
||||
import com.baiyue.gateway.filter.CorrelationIdWebFilter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -66,11 +67,16 @@ public class GatewaySecurityConfig {
|
||||
ObjectMapper mapper, HttpStatus status, String code, String message) {
|
||||
exchange.getResponse().setStatusCode(status);
|
||||
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
String requestId = CorrelationIdWebFilter.ensureRequestId(exchange);
|
||||
String traceId = CorrelationIdWebFilter.ensureTraceId(exchange);
|
||||
exchange.getResponse().getHeaders().set(CorrelationIdWebFilter.REQUEST_HEADER, requestId);
|
||||
exchange.getResponse().getHeaders().set(CorrelationIdWebFilter.TRACE_HEADER, traceId);
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("code", code);
|
||||
body.put("message", message);
|
||||
body.put("data", null);
|
||||
body.put("requestId", exchange.getRequest().getId());
|
||||
body.put("requestId", requestId);
|
||||
body.put("traceId", traceId);
|
||||
body.put("timestamp", Instant.now().toString());
|
||||
try {
|
||||
byte[] bytes = mapper.writeValueAsBytes(body);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.baiyue.gateway.filter;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/** Ensures overseas security failures carry the same correlation contract as normal API responses. */
|
||||
@Component
|
||||
public class CorrelationIdWebFilter implements WebFilter, Ordered {
|
||||
public static final String REQUEST_HEADER = "X-Request-Id";
|
||||
public static final String TRACE_HEADER = "X-Trace-Id";
|
||||
private static final String REQUEST_ATTRIBUTE = CorrelationIdWebFilter.class.getName() + ".requestId";
|
||||
private static final String TRACE_ATTRIBUTE = CorrelationIdWebFilter.class.getName() + ".traceId";
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
exchange.getResponse().getHeaders().set(REQUEST_HEADER, ensureRequestId(exchange));
|
||||
exchange.getResponse().getHeaders().set(TRACE_HEADER, ensureTraceId(exchange));
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
public static String ensureRequestId(ServerWebExchange exchange) {
|
||||
return (String) exchange.getAttributes().computeIfAbsent(REQUEST_ATTRIBUTE,
|
||||
ignored -> accepted(exchange.getRequest().getHeaders(), REQUEST_HEADER));
|
||||
}
|
||||
|
||||
public static String ensureTraceId(ServerWebExchange exchange) {
|
||||
return (String) exchange.getAttributes().computeIfAbsent(TRACE_ATTRIBUTE,
|
||||
ignored -> accepted(exchange.getRequest().getHeaders(), TRACE_HEADER));
|
||||
}
|
||||
|
||||
private static String accepted(HttpHeaders headers, String name) {
|
||||
String incoming = headers.getFirst(name);
|
||||
return valid(incoming) ? incoming : UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
private static boolean valid(String value) {
|
||||
return value != null && !value.isBlank() && value.length() >= 16 && value.length() <= 128
|
||||
&& value.chars().allMatch(character -> character >= 0x21 && character <= 0x7e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.baiyue.gateway.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
class GatewaySecurityErrorContractTest {
|
||||
@Test
|
||||
void audienceAndAuthenticationFailuresExposeRequestAndTraceCorrelation() throws Exception {
|
||||
var exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/api/platform/v1/test")
|
||||
.header("X-Request-Id", "request-1234567890")
|
||||
.header("X-Trace-Id", "trace-123456789012")
|
||||
.build());
|
||||
|
||||
GatewaySecurityConfig.writeError(exchange, new ObjectMapper(), HttpStatus.UNAUTHORIZED,
|
||||
"A0401", "未登录或令牌已失效").block();
|
||||
|
||||
var body = new ObjectMapper().readTree(exchange.getResponse().getBodyAsString().block());
|
||||
assertEquals("request-1234567890", body.path("requestId").asText());
|
||||
assertEquals("trace-123456789012", body.path("traceId").asText());
|
||||
assertEquals(body.path("requestId").asText(), exchange.getResponse().getHeaders().getFirst("X-Request-Id"));
|
||||
assertEquals(body.path("traceId").asText(), exchange.getResponse().getHeaders().getFirst("X-Trace-Id"));
|
||||
assertFalse(body.path("timestamp").asText().isBlank());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user