Merge branch 'dev' into test
This commit is contained in:
@@ -11,6 +11,7 @@ import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiCreden
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewayJwtResolver;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewaySecurityFilter;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -171,6 +172,7 @@ public class ApiGatewayExecutionService {
|
||||
if (reqVO.getHeaders() != null) {
|
||||
requestHeaders.putAll(reqVO.getHeaders());
|
||||
}
|
||||
normalizeJwtHeaders(requestHeaders, reqVO.getQueryParams());
|
||||
requestHeaders.putIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
builder.setHeader(HEADER_REQUEST_HEADERS, requestHeaders);
|
||||
requestHeaders.forEach((key, value) -> {
|
||||
@@ -304,4 +306,37 @@ public class ApiGatewayExecutionService {
|
||||
builder.queryParam(key, value);
|
||||
}
|
||||
|
||||
private void normalizeJwtHeaders(Map<String, Object> headers, Map<String, Object> queryParams) {
|
||||
String token = GatewayJwtResolver.resolveJwtToken(headers, queryParams, objectMapper);
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return;
|
||||
}
|
||||
ensureHeaderValue(headers, GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, token);
|
||||
ensureHeaderValue(headers, HttpHeaders.AUTHORIZATION, "Bearer " + token);
|
||||
}
|
||||
|
||||
private void ensureHeaderValue(Map<String, Object> headers, String headerName, String value) {
|
||||
if (!StringUtils.hasText(headerName) || value == null) {
|
||||
return;
|
||||
}
|
||||
String existingKey = findHeaderKey(headers, headerName);
|
||||
if (existingKey != null) {
|
||||
headers.put(existingKey, value);
|
||||
} else {
|
||||
headers.put(headerName, value);
|
||||
}
|
||||
}
|
||||
|
||||
private String findHeaderKey(Map<String, Object> headers, String headerName) {
|
||||
if (headers == null || !StringUtils.hasText(headerName)) {
|
||||
return null;
|
||||
}
|
||||
for (String key : headers.keySet()) {
|
||||
if (headerName.equalsIgnoreCase(key)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_CREDENTIAL_UNAUTHORIZED;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EXCEEDED;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ApiGatewayErrorProcessorTest {
|
||||
|
||||
private final ApiGatewayErrorProcessor processor = new ApiGatewayErrorProcessor();
|
||||
|
||||
@Test
|
||||
void applyServiceException_should_fill_status_message_and_body() {
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
ServiceException exception = ServiceExceptionUtil.exception(API_RATE_LIMIT_EXCEEDED);
|
||||
|
||||
processor.applyServiceException(context, exception);
|
||||
|
||||
assertThat(context.getResponseStatus()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS.value());
|
||||
assertThat(context.getResponseMessage()).isNotBlank();
|
||||
assertThat(context.getResponseBody()).isInstanceOfSatisfying(java.util.Map.class, body -> {
|
||||
assertThat(body).containsEntry("errorCode", API_RATE_LIMIT_EXCEEDED.getCode());
|
||||
assertThat(body).containsKey("errorMessage");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyUnexpectedException_should_use_first_non_empty_message() {
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
RuntimeException exception = new RuntimeException("outer", new IllegalStateException("inner cause"));
|
||||
|
||||
processor.applyUnexpectedException(context, exception);
|
||||
|
||||
assertThat(context.getResponseStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
assertThat(context.getResponseMessage()).isEqualTo("outer");
|
||||
assertThat(context.getResponseBody()).isInstanceOf(java.util.Map.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveServiceException_should_traverse_causes() {
|
||||
ServiceException serviceException = ServiceExceptionUtil.exception(API_CREDENTIAL_UNAUTHORIZED);
|
||||
RuntimeException wrapped = new RuntimeException(new IllegalStateException(serviceException));
|
||||
|
||||
ServiceException resolved = processor.resolveServiceException(wrapped);
|
||||
|
||||
assertThat(resolved).isSameAs(serviceException);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -73,8 +73,8 @@ class ApiGatewayRequestMapperTest {
|
||||
|
||||
ApiInvocationContext context = mapper.map("", headers);
|
||||
|
||||
assertThat(context.getRequestHeaders().get("ZT-Auth-Token")).isEqualTo("token-123");
|
||||
assertThat(context.getRequestHeaders().get("zt-auth-token")).isEqualTo("token-123");
|
||||
assertThat(context.getRequestHeaders().get("ZT-Auth-Token")).isEqualTo(List.of("token-123"));
|
||||
assertThat(context.getRequestHeaders().get("zt-auth-token")).isEqualTo(List.of("token-123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.policy;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EVALUATION_FAILED;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EXCEEDED;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultRateLimitPolicyEvaluatorTest {
|
||||
|
||||
@Mock
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Mock
|
||||
private ValueOperations<String, String> valueOperations;
|
||||
|
||||
private DefaultRateLimitPolicyEvaluator evaluator;
|
||||
|
||||
private ApiDefinitionAggregate aggregate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
evaluator = new DefaultRateLimitPolicyEvaluator(objectMapper, stringRedisTemplate);
|
||||
|
||||
ApiDefinitionDO definitionDO = new ApiDefinitionDO();
|
||||
definitionDO.setApiCode("order.create");
|
||||
definitionDO.setVersion("v1");
|
||||
|
||||
ApiPolicyRateLimitDO rateLimitDO = new ApiPolicyRateLimitDO();
|
||||
rateLimitDO.setConfig("{\"limit\":2,\"windowSeconds\":60}");
|
||||
|
||||
aggregate = ApiDefinitionAggregate.builder()
|
||||
.definition(definitionDO)
|
||||
.rateLimitPolicy(rateLimitDO)
|
||||
.build();
|
||||
|
||||
when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void evaluate_should_set_expire_on_first_hit_and_pass_under_limit() {
|
||||
when(valueOperations.increment(any())).thenReturn(1L, 2L);
|
||||
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
context.getRequestHeaders().put("X-Client-Id", "c1");
|
||||
|
||||
evaluator.evaluate(aggregate, context);
|
||||
evaluator.evaluate(aggregate, context);
|
||||
|
||||
verify(stringRedisTemplate, times(2)).opsForValue();
|
||||
verify(valueOperations, times(2)).increment(any());
|
||||
verify(stringRedisTemplate).expire(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void evaluate_should_throw_when_exceed_limit() {
|
||||
when(valueOperations.increment(any())).thenReturn(3L);
|
||||
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> evaluator.evaluate(aggregate, context));
|
||||
|
||||
assertThat(ex.getCode()).isEqualTo(API_RATE_LIMIT_EXCEEDED.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void evaluate_should_wrap_redis_failure() {
|
||||
when(valueOperations.increment(any())).thenThrow(new DataAccessResourceFailureException("redis down"));
|
||||
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> evaluator.evaluate(aggregate, context));
|
||||
|
||||
assertThat(ex.getCode()).isEqualTo(API_RATE_LIMIT_EVALUATION_FAILED.getCode());
|
||||
}
|
||||
}
|
||||
@@ -29,15 +29,17 @@ public final class DatabusApiInvocationExample {
|
||||
|
||||
public static final String TIMESTAMP = Long.toString(System.currentTimeMillis());
|
||||
|
||||
private static final String APP_ID = "iwork";
|
||||
private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk=";
|
||||
// private static final String APP_ID = "ztmy";
|
||||
// private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ=";
|
||||
// private static final String APP_ID = "iwork";
|
||||
// private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk=";
|
||||
private static final String APP_ID = "ztmy";
|
||||
private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ=";
|
||||
private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
||||
// private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/callback/v1";
|
||||
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/callback/v1";
|
||||
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/callback/v1";
|
||||
private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456";
|
||||
private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456";
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class GatewayJwtResolverTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
void resolveJwtToken_should_pick_token_header_case_insensitive() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("zT-AuTh-ToKeN", "abc123");
|
||||
|
||||
String token = GatewayJwtResolver.resolveJwtToken(headers, null, objectMapper);
|
||||
|
||||
assertThat(token).isEqualTo("abc123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveJwtToken_should_strip_bearer_prefix_and_array_values() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("Authorization", new String[]{"Bearer XYZ.jwt"});
|
||||
|
||||
String token = GatewayJwtResolver.resolveJwtToken(headers, null, objectMapper);
|
||||
|
||||
assertThat(token).isEqualTo("XYZ.jwt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveJwtToken_should_parse_structured_json_string_and_fallback_to_query_params() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("Authorization", "{\"token\":\"json-token\"}");
|
||||
Map<String, Object> query = Map.of("token", List.of("q1", "q2"));
|
||||
|
||||
String token = GatewayJwtResolver.resolveJwtToken(headers, query, objectMapper);
|
||||
|
||||
assertThat(token).isEqualTo("json-token");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
DELETE FROM "databus_api_transform";
|
||||
DELETE FROM "databus_api_step";
|
||||
DELETE FROM "databus_api_definition";
|
||||
DELETE FROM "databus_api_definition_credential";
|
||||
DELETE FROM "databus_policy_rate_limit";
|
||||
DELETE FROM "databus_policy_audit";
|
||||
DELETE FROM "databus_api_flow_publish";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user