优化
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@ import java.time.Duration;
|
||||
@ConfigurationProperties(prefix = "baiyue.security.overseas-projection")
|
||||
public record OverseasSecurityProjectionProperties(String serviceUrl, String internalServiceToken, Duration timeout) {
|
||||
public OverseasSecurityProjectionProperties {
|
||||
if (serviceUrl == null || serviceUrl.isBlank()) throw new IllegalArgumentException("必须配置 OVERSEAS_SECURITY_SERVICE_URL");
|
||||
if (serviceUrl == null || serviceUrl.isBlank()) throw new IllegalArgumentException("必须配置 LASTLEG_SECURITY_SERVICE_URL");
|
||||
if (internalServiceToken == null || internalServiceToken.getBytes(java.nio.charset.StandardCharsets.UTF_8).length < 32) {
|
||||
throw new IllegalArgumentException("INTERNAL_SERVICE_TOKEN 至少需要 32 字节");
|
||||
}
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class HttpOverseasSecurityValidationClient implements OverseasSecurityValidationClient {
|
||||
private static final String PATH = "/internal/overseas-security/v1/validate";
|
||||
private static final String PATH = "/internal/lastleg/security/v1/validate";
|
||||
private final WebClient client;
|
||||
private final OverseasSecurityProjectionProperties properties;
|
||||
|
||||
@@ -19,10 +19,10 @@ public class HttpOverseasSecurityValidationClient implements OverseasSecurityVal
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Result> validate(long userId, long tenantId, long tokenVersion, long permissionVersion) {
|
||||
public Mono<Result> validate(long userId, long tenantId, long permissionVersion) {
|
||||
return client.post().uri(properties.serviceUrl() + PATH).contentType(MediaType.APPLICATION_JSON)
|
||||
.header("X-Internal-Service-Token", properties.internalServiceToken())
|
||||
.bodyValue(new Request(userId, tenantId, tokenVersion, permissionVersion))
|
||||
.bodyValue(new Request(userId, tenantId, permissionVersion))
|
||||
.retrieve().bodyToMono(JsonNode.class).timeout(properties.timeout()).flatMap(body -> {
|
||||
JsonNode data = body == null ? null : body.path("data");
|
||||
if (data == null || data.isMissingNode() || !data.has("valid")) {
|
||||
@@ -33,5 +33,5 @@ public class HttpOverseasSecurityValidationClient implements OverseasSecurityVal
|
||||
});
|
||||
}
|
||||
|
||||
private record Request(long userId, long tenantId, long tokenVersion, long permissionVersion) { }
|
||||
private record Request(long userId, long tenantId, long permissionVersion) { }
|
||||
}
|
||||
|
||||
+2
-3
@@ -38,12 +38,11 @@ public class OverseasSecurityProjectionFilter implements GlobalFilter, Ordered {
|
||||
JwtAuthenticationToken authentication) {
|
||||
Long userId = number(authentication.getToken().getSubject());
|
||||
Long tenantId = number(authentication.getToken().getClaim("tenant_id"));
|
||||
Long tokenVersion = number(authentication.getToken().getClaim("token_version"));
|
||||
Long permissionVersion = number(authentication.getToken().getClaim("permission_version"));
|
||||
if (userId == null || tenantId == null || tokenVersion == null || permissionVersion == null) {
|
||||
if (userId == null || tenantId == null || permissionVersion == null) {
|
||||
return Mono.error(new SecurityProjectionFailure("A0401", "海外令牌安全版本声明缺失"));
|
||||
}
|
||||
return client.validate(userId, tenantId, tokenVersion, permissionVersion)
|
||||
return client.validate(userId, tenantId, permissionVersion)
|
||||
.switchIfEmpty(Mono.error(new SecurityProjectionFailure("A0401", "海外安全状态校验不可用")))
|
||||
.flatMap(result -> {
|
||||
if (result.valid()) return chain.filter(exchange);
|
||||
|
||||
+1
-1
@@ -3,6 +3,6 @@ package com.baiyue.gateway.filter;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface OverseasSecurityValidationClient {
|
||||
Mono<Result> validate(long userId, long tenantId, long tokenVersion, long permissionVersion);
|
||||
Mono<Result> validate(long userId, long tenantId, long permissionVersion);
|
||||
record Result(boolean valid, String failureReason) { }
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ baiyue:
|
||||
internal-service-token: ${INTERNAL_SERVICE_TOKEN}
|
||||
timeout: ${PDA_SESSION_VALIDATION_TIMEOUT:2s}
|
||||
overseas-projection:
|
||||
service-url: ${OVERSEAS_SECURITY_SERVICE_URL}
|
||||
service-url: ${LASTLEG_SECURITY_SERVICE_URL:http://lastleg-service:9303}
|
||||
internal-service-token: ${INTERNAL_SERVICE_TOKEN}
|
||||
timeout: ${OVERSEAS_SECURITY_VALIDATION_TIMEOUT:2s}
|
||||
timeout: ${LASTLEG_SECURITY_VALIDATION_TIMEOUT:2s}
|
||||
permit-paths:
|
||||
- /actuator/health
|
||||
- /swagger-ui/**
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.baiyue.gateway.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class OverseasSecurityValidationDeploymentContractTest {
|
||||
@Test
|
||||
void gatewayAndEventBridgeUseLastlegSecurityEndpointsWithoutTheRetiredService() throws Exception {
|
||||
String client = read("gateway-overseas/src/main/java/com/baiyue/gateway/filter/HttpOverseasSecurityValidationClient.java");
|
||||
String gatewayApplication = read("gateway-overseas/src/main/resources/application.yml");
|
||||
String gatewayNacos = read("config/nacos/overseas/gateway-overseas.yml");
|
||||
String bridgeApplication = read("event-bridge-service/src/main/resources/application.yml");
|
||||
String bridgeNacos = read("config/nacos/overseas/event-bridge-service.yml");
|
||||
String compose = read("deploy/docker-compose.overseas.yml");
|
||||
|
||||
assertThat(client).contains("/internal/lastleg/security/v1/validate")
|
||||
.contains("new Request(userId, tenantId, permissionVersion)")
|
||||
.doesNotContain("new Request(userId, tenantId, tokenVersion")
|
||||
.doesNotContain("/internal/overseas-security/");
|
||||
for (String source : new String[]{gatewayApplication, gatewayNacos, compose}) {
|
||||
assertThat(source).contains("LASTLEG_SECURITY_SERVICE_URL", "http://lastleg-service:9303");
|
||||
}
|
||||
for (String source : new String[]{bridgeApplication, bridgeNacos, compose}) {
|
||||
assertThat(source).contains("LASTLEG_SECURITY_EVENT_CONSUMER_URL",
|
||||
"/internal/lastleg/security/v1/events");
|
||||
}
|
||||
assertThat(gatewayApplication + gatewayNacos + bridgeApplication + bridgeNacos + compose)
|
||||
.doesNotContain("overseas-security-service", "9307", "OVERSEAS_SECURITY_DB_URL",
|
||||
"OVERSEAS_SECURITY_SERVICE_URL", "OVERSEAS_SECURITY_EVENT_CONSUMER_URL");
|
||||
}
|
||||
|
||||
private static String read(String relative) throws Exception {
|
||||
Path path = Path.of(relative);
|
||||
if (!Files.exists(path)) path = Path.of("..").resolve(relative);
|
||||
return Files.readString(path, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -26,7 +26,7 @@ class OverseasSecurityProjectionFilterTest {
|
||||
private final OverseasSecurityProjectionFilter filter = new OverseasSecurityProjectionFilter(client, new ObjectMapper());
|
||||
|
||||
@Test void disabledUserFailsWithA0401() {
|
||||
when(client.validate(42, 0, 3, 8)).thenReturn(Mono.just(
|
||||
when(client.validate(42, 0, 8)).thenReturn(Mono.just(
|
||||
new OverseasSecurityValidationClient.Result(false, "USER_DISABLED")));
|
||||
var exchange = exchange("/api/platform/v1/last-leg/orders");
|
||||
StepVerifier.create(filter.filter(exchange, ignored -> Mono.empty())).verifyComplete();
|
||||
@@ -36,7 +36,7 @@ class OverseasSecurityProjectionFilterTest {
|
||||
|
||||
@Test void disabledTenantAndStalePermissionFailWithA0403() {
|
||||
for (String reason : new String[]{"TENANT_DISABLED", "PERMISSION_VERSION_STALE"}) {
|
||||
when(client.validate(42, 0, 3, 8)).thenReturn(Mono.just(
|
||||
when(client.validate(42, 0, 8)).thenReturn(Mono.just(
|
||||
new OverseasSecurityValidationClient.Result(false, reason)));
|
||||
var exchange = exchange("/api/merchant/v1/last-leg/orders");
|
||||
StepVerifier.create(filter.filter(exchange, ignored -> Mono.empty())).verifyComplete();
|
||||
@@ -46,14 +46,14 @@ class OverseasSecurityProjectionFilterTest {
|
||||
}
|
||||
|
||||
@Test void projectionUnavailableFailsClosed() {
|
||||
when(client.validate(42, 0, 3, 8)).thenReturn(Mono.error(new IllegalStateException("down")));
|
||||
when(client.validate(42, 0, 8)).thenReturn(Mono.error(new IllegalStateException("down")));
|
||||
var exchange = exchange("/api/platform/v1/last-leg/orders");
|
||||
StepVerifier.create(filter.filter(exchange, ignored -> Mono.empty())).verifyComplete();
|
||||
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@Test void acceptedSecurityStateContinuesAndNonApiPathsDoNotCallProjection() {
|
||||
when(client.validate(42, 0, 3, 8)).thenReturn(Mono.just(
|
||||
when(client.validate(42, 0, 8)).thenReturn(Mono.just(
|
||||
new OverseasSecurityValidationClient.Result(true, null)));
|
||||
AtomicBoolean routed = new AtomicBoolean();
|
||||
StepVerifier.create(filter.filter(exchange("/api/platform/v1/last-leg/orders"), ignored -> {
|
||||
|
||||
Reference in New Issue
Block a user