fix: project overseas authorization outside jwt

This commit is contained in:
2026-09-07 11:37:06 +08:00
parent 6b479d1a68
commit 4963889024
33 changed files with 382 additions and 92 deletions
@@ -26,19 +26,19 @@ class RequiredClaimsJwtValidatorTest {
Jwt value = Jwt.withTokenValue("token").header("alg", "RS256").subject("1001")
.issuedAt(Instant.now()).expiresAt(Instant.now().plusSeconds(300))
.claim("tenant_id", "0").claim("app_code", "PLATFORM")
.claim("permission_version", 3L).claim("authorities", List.of())
.claim("permission_version", 3L)
.build();
assertThat(validator.validate(value).hasErrors()).isTrue();
}
@Test
void rejectsMissingAuthorities() {
void acceptsTokenWithoutAuthoritiesBecauseProjectionOwnsAuthorization() {
Jwt value = Jwt.withTokenValue("token").header("alg", "RS256").subject("1001")
.issuedAt(Instant.now()).expiresAt(Instant.now().plusSeconds(300))
.claim("tenant_id", "0").claim("app_code", "PLATFORM").claim("token_version", 1)
.claim("permission_version", 3L)
.build();
assertThat(validator.validate(value).hasErrors()).isTrue();
assertThat(validator.validate(value).hasErrors()).isFalse();
}
@Test
@@ -1,6 +1,7 @@
package com.baiyue.gateway.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.baiyue.gateway.config.OverseasSecurityProjectionProperties;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
@@ -13,7 +14,9 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.Instant;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.assertj.core.api.Assertions.assertThat;
@@ -23,11 +26,15 @@ import static org.mockito.Mockito.when;
class OverseasSecurityProjectionFilterTest {
private final OverseasSecurityValidationClient client = mock(OverseasSecurityValidationClient.class);
private final OverseasSecurityProjectionFilter filter = new OverseasSecurityProjectionFilter(client, new ObjectMapper());
private final OverseasSecurityProjectionProperties properties =
new OverseasSecurityProjectionProperties("http://lastleg",
"gateway-internal-test-token-32-bytes", Duration.ofSeconds(2));
private final OverseasSecurityProjectionFilter filter =
new OverseasSecurityProjectionFilter(client, new ObjectMapper(), properties);
@Test void disabledUserFailsWithA0401() {
when(client.validate(42, 0, 8)).thenReturn(Mono.just(
new OverseasSecurityValidationClient.Result(false, "USER_DISABLED")));
new OverseasSecurityValidationClient.Result(false, "USER_DISABLED", Set.of(), Set.of())));
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);
@@ -37,7 +44,7 @@ class OverseasSecurityProjectionFilterTest {
@Test void disabledTenantAndStalePermissionFailWithA0403() {
for (String reason : new String[]{"TENANT_DISABLED", "PERMISSION_VERSION_STALE"}) {
when(client.validate(42, 0, 8)).thenReturn(Mono.just(
new OverseasSecurityValidationClient.Result(false, reason)));
new OverseasSecurityValidationClient.Result(false, reason, Set.of(), Set.of())));
var exchange = exchange("/api/merchant/v1/last-leg/orders");
StepVerifier.create(filter.filter(exchange, ignored -> Mono.empty())).verifyComplete();
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
@@ -54,15 +61,22 @@ class OverseasSecurityProjectionFilterTest {
@Test void acceptedSecurityStateContinuesAndNonApiPathsDoNotCallProjection() {
when(client.validate(42, 0, 8)).thenReturn(Mono.just(
new OverseasSecurityValidationClient.Result(true, null)));
new OverseasSecurityValidationClient.Result(true, null, Set.of("PLATFORM_ADMIN"),
Set.of("platform:overseas-warehouse:last-mile-order:list"))));
AtomicBoolean routed = new AtomicBoolean();
StepVerifier.create(filter.filter(exchange("/api/platform/v1/last-leg/orders"), ignored -> {
routed.set(true); return Mono.empty();
var accepted = exchange("/api/platform/v1/last-leg/orders");
StepVerifier.create(filter.filter(accepted, routedExchange -> {
routed.set(true);
assertThat(routedExchange.getRequest().getHeaders().getFirst("X-Gateway-Internal-Token"))
.isEqualTo("gateway-secret");
assertThat(routedExchange.getRequest().getHeaders().getFirst("X-Overseas-Authorities"))
.isEqualTo("platform:overseas-warehouse:last-mile-order:list");
return Mono.empty();
})).verifyComplete();
assertThat(routed).isTrue();
var isolatedClient = mock(OverseasSecurityValidationClient.class);
var isolatedFilter = new OverseasSecurityProjectionFilter(isolatedClient, new ObjectMapper());
var isolatedFilter = new OverseasSecurityProjectionFilter(isolatedClient, new ObjectMapper(), properties);
StepVerifier.create(isolatedFilter.filter(exchange("/actuator/health"), ignored -> Mono.empty())).verifyComplete();
verifyNoInteractions(isolatedClient);
}