From 3efb0750a92e0beaa572fcedcfdc3bc0801d7d3f Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Thu, 12 Mar 2026 15:46:48 +0800 Subject: [PATCH 01/18] feat(api-gateway): implement OMS authentication filter with comprehensive tests --- backend/api-gateway/pom.xml | 25 ++- .../gateway/common/filter/OmsAuthFilter.java | 117 +++++++++++ .../src/main/resources/application.yml | 5 + .../common/filter/OmsAuthFilterTest.java | 184 ++++++++++++++++++ 4 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java create mode 100644 backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java diff --git a/backend/api-gateway/pom.xml b/backend/api-gateway/pom.xml index 98a8066e6..96e0854dd 100644 --- a/backend/api-gateway/pom.xml +++ b/backend/api-gateway/pom.xml @@ -68,6 +68,12 @@ postgresql + + + org.apache.httpcomponents.client5 + httpclient5 + + io.jsonwebtoken @@ -82,10 +88,27 @@ io.jsonwebtoken - jjwt-jackson + jjwt-jackson ${jjwt.version} runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-junit-jupiter + test + diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java new file mode 100644 index 000000000..3f43a5797 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -0,0 +1,117 @@ +package com.datamate.gateway.common.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class OmsAuthFilter extends AbstractGatewayFilterFactory { + private static final int OMS_AUTH_FILTER_ORDER = -1; + private static final String USER_NAME_HEADER = "X-User-Name"; + + @Value("${oms.auth.enabled:false}") + private Boolean omsAuthEnable; + + @Value("${oms.gateway.url}") + private String omsGatewayUrl; + + private final ObjectMapper objectMapper; + + private CloseableHttpClient httpClient; + + public OmsAuthFilter() { + super(Config.class); + this.objectMapper = new ObjectMapper(); + this.httpClient = HttpClients.createDefault(); + } + + public void setHttpClient(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public GatewayFilter apply(Config config) { + log.info("OmsAuthFilter is apply, omsAuthEnable: {}", omsAuthEnable); + return new OrderedGatewayFilter(this::filter, OMS_AUTH_FILTER_ORDER); + } + + private Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + if (!omsAuthEnable) { + return chain.filter(exchange); + } + ServerHttpRequest request = exchange.getRequest(); + String uri = request.getURI().getPath(); + log.info("Oms auth filter uri: {}", uri); + + String fullPath = omsGatewayUrl + "/framework/v1/iam/roles/query-by-token"; + log.info("oms auth full path: {}", fullPath); + + try { + HttpPost httpPost = new HttpPost(fullPath); + + String authToken = request.getHeaders().getFirst("Authorization"); + if (authToken != null && authToken.startsWith("Bearer ")) { + authToken = authToken.substring(7); + } + httpPost.setHeader("X-Auth-Token", authToken); + + CloseableHttpResponse response = httpClient.execute(httpPost); + String responseBody = EntityUtils.toString(response.getEntity()); + log.info("response code: {}, response body: {}", response.getCode(), responseBody); + + ResultVo> resultVo = objectMapper.readValue(responseBody, + objectMapper.getTypeFactory().constructParametricType(ResultVo.class, List.class)); + + if (resultVo.getData() == null || resultVo.getData().isEmpty()) { + exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); + log.error("Authentication failed: Token is null or invalid."); + return exchange.getResponse().setComplete(); + } + + String userName = resultVo.getData().get(0); + ServerHttpRequest newRequest = request.mutate() + .header(USER_NAME_HEADER, userName) + .build(); + + return chain.filter(exchange.mutate().request(newRequest).build()); + } catch (IOException | ParseException e) { + log.error("Exception occurred during POST request: {}", e.getMessage(), e); + exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); + return exchange.getResponse().setComplete(); + } + } + + @Data + @NoArgsConstructor + public static class Config {} + + @Data + public static class ResultVo { + private Integer code; + private String message; + private T data; + } +} diff --git a/backend/api-gateway/src/main/resources/application.yml b/backend/api-gateway/src/main/resources/application.yml index a6603b6c9..705f3158f 100644 --- a/backend/api-gateway/src/main/resources/application.yml +++ b/backend/api-gateway/src/main/resources/application.yml @@ -33,6 +33,11 @@ datamate: jwt: secret: ${JWT_SECRET} expiration-seconds: 3600 +oms: + auth: + enabled: false + gateway: + url: ${OMS_GATEWAY_URL:http://localhost:8080} # 服务器端口配置 server: port: 8080 # 必须有这个配置 \ No newline at end of file diff --git a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java new file mode 100644 index 000000000..cb224b6fc --- /dev/null +++ b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java @@ -0,0 +1,184 @@ +package com.datamate.gateway.common.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; +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.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class OmsAuthFilterTest { + + @Mock + private GatewayFilterChain chain; + + @Mock + private CloseableHttpClient httpClient; + + @Mock + private CloseableHttpResponse httpResponse; + + private OmsAuthFilter omsAuthFilter; + private GatewayFilter gatewayFilter; + + @BeforeEach + void setUp() throws Exception { + omsAuthFilter = new OmsAuthFilter(); + setField(omsAuthFilter, "omsAuthEnable", false); + setField(omsAuthFilter, "omsGatewayUrl", "http://localhost:8080"); + gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); + } + + private void setField(Object target, String fieldName, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + + @Test + void testFilter_WhenOmsAuthDisabled_ShouldPassThrough() { + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); + + gatewayFilter.filter(exchange, chain); + + verify(chain, times(1)).filter(any(ServerWebExchange.class)); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws Exception { + setField(omsAuthFilter, "omsAuthEnable", true); + omsAuthFilter.setHttpClient(httpClient); + + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") + .header("Authorization", "Bearer valid-token") + .build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + String successResponse = "{\"code\":200,\"message\":\"success\",\"data\":[\"testuser\"]}"; + StringEntity entity = new StringEntity(successResponse, StandardCharsets.UTF_8); + when(httpResponse.getEntity()).thenReturn(entity); + when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + + when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); + + gatewayFilter.filter(exchange, chain); + + verify(chain, times(1)).filter(argThat(ex -> { + HttpHeaders headers = ex.getRequest().getHeaders(); + return headers.containsKey("X-User-Name") && + "testuser".equals(headers.getFirst("X-User-Name")); + })); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndTokenInvalid_ShouldReturn403() throws Exception { + setField(omsAuthFilter, "omsAuthEnable", true); + omsAuthFilter.setHttpClient(httpClient); + + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") + .header("Authorization", "Bearer invalid-token") + .build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + String failureResponse = "{\"code\":403,\"message\":\"unauthorized\",\"data\":[]}"; + StringEntity entity = new StringEntity(failureResponse, StandardCharsets.UTF_8); + when(httpResponse.getEntity()).thenReturn(entity); + when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + + gatewayFilter.filter(exchange, chain); + + ServerHttpResponse response = exchange.getResponse(); + assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); + verify(chain, never()).filter(any(ServerWebExchange.class)); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndNoToken_ShouldReturn403() throws Exception { + setField(omsAuthFilter, "omsAuthEnable", true); + omsAuthFilter.setHttpClient(httpClient); + + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + String failureResponse = "{\"code\":403,\"message\":\"unauthorized\",\"data\":[]}"; + StringEntity entity = new StringEntity(failureResponse, StandardCharsets.UTF_8); + when(httpResponse.getEntity()).thenReturn(entity); + when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + + gatewayFilter.filter(exchange, chain); + + ServerHttpResponse response = exchange.getResponse(); + assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); + verify(chain, never()).filter(any(ServerWebExchange.class)); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndBearerTokenFormat_ShouldExtractToken() throws Exception { + setField(omsAuthFilter, "omsAuthEnable", true); + omsAuthFilter.setHttpClient(httpClient); + + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") + .header("Authorization", "Bearer test-token") + .build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + String successResponse = "{\"code\":200,\"message\":\"success\",\"data\":[\"testuser\"]}"; + StringEntity entity = new StringEntity(successResponse, StandardCharsets.UTF_8); + when(httpResponse.getEntity()).thenReturn(entity); + when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + + when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); + + gatewayFilter.filter(exchange, chain); + + verify(chain, times(1)).filter(any(ServerWebExchange.class)); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndNonBearerToken_ShouldProcessAsIs() throws Exception { + setField(omsAuthFilter, "omsAuthEnable", true); + omsAuthFilter.setHttpClient(httpClient); + + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") + .header("Authorization", "test-token") + .build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + String successResponse = "{\"code\":200,\"message\":\"success\",\"data\":[\"testuser\"]}"; + StringEntity entity = new StringEntity(successResponse, StandardCharsets.UTF_8); + when(httpResponse.getEntity()).thenReturn(entity); + when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + + when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); + + gatewayFilter.filter(exchange, chain); + + verify(chain, times(1)).filter(any(ServerWebExchange.class)); + } +} From 09d72367b2b6e6b8bfdb5789bd87dfe9f60e3c36 Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Thu, 12 Mar 2026 16:02:45 +0800 Subject: [PATCH 02/18] refactor(api-gateway): optimize OMS auth filter to apply configuration switch in apply method --- .../com/datamate/gateway/common/filter/OmsAuthFilter.java | 8 ++++---- .../datamate/gateway/common/filter/OmsAuthFilterTest.java | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index 3f43a5797..00eb5e1db 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -54,14 +54,14 @@ public void setHttpClient(CloseableHttpClient httpClient) { @Override public GatewayFilter apply(Config config) { - log.info("OmsAuthFilter is apply, omsAuthEnable: {}", omsAuthEnable); + log.info("OmsAuthFilter is apply, omsAuthEnable: {}", this.omsAuthEnable); + if (!this.omsAuthEnable) { + return (exchange, chain) -> chain.filter(exchange); + } return new OrderedGatewayFilter(this::filter, OMS_AUTH_FILTER_ORDER); } private Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (!omsAuthEnable) { - return chain.filter(exchange); - } ServerHttpRequest request = exchange.getRequest(); String uri = request.getURI().getPath(); log.info("Oms auth filter uri: {}", uri); diff --git a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java index cb224b6fc..7d2e3f450 100644 --- a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java +++ b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java @@ -74,6 +74,7 @@ void testFilter_WhenOmsAuthDisabled_ShouldPassThrough() { void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws Exception { setField(omsAuthFilter, "omsAuthEnable", true); omsAuthFilter.setHttpClient(httpClient); + gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") .header("Authorization", "Bearer valid-token") @@ -100,6 +101,7 @@ void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws void testFilter_WhenOmsAuthEnabledAndTokenInvalid_ShouldReturn403() throws Exception { setField(omsAuthFilter, "omsAuthEnable", true); omsAuthFilter.setHttpClient(httpClient); + gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") .header("Authorization", "Bearer invalid-token") @@ -122,6 +124,7 @@ void testFilter_WhenOmsAuthEnabledAndTokenInvalid_ShouldReturn403() throws Excep void testFilter_WhenOmsAuthEnabledAndNoToken_ShouldReturn403() throws Exception { setField(omsAuthFilter, "omsAuthEnable", true); omsAuthFilter.setHttpClient(httpClient); + gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); @@ -142,6 +145,7 @@ void testFilter_WhenOmsAuthEnabledAndNoToken_ShouldReturn403() throws Exception void testFilter_WhenOmsAuthEnabledAndBearerTokenFormat_ShouldExtractToken() throws Exception { setField(omsAuthFilter, "omsAuthEnable", true); omsAuthFilter.setHttpClient(httpClient); + gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") .header("Authorization", "Bearer test-token") @@ -164,6 +168,7 @@ void testFilter_WhenOmsAuthEnabledAndBearerTokenFormat_ShouldExtractToken() thro void testFilter_WhenOmsAuthEnabledAndNonBearerToken_ShouldProcessAsIs() throws Exception { setField(omsAuthFilter, "omsAuthEnable", true); omsAuthFilter.setHttpClient(httpClient); + gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") .header("Authorization", "test-token") From b350c99b2fefb71a4a7b74c545210c9c23ba0699 Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Thu, 12 Mar 2026 16:51:31 +0800 Subject: [PATCH 03/18] refactor(api-gateway): change OMS auth token retrieval from header to cookies --- .../gateway/common/filter/OmsAuthFilter.java | 27 +++++++++++--- .../common/filter/OmsAuthFilterTest.java | 37 +++---------------- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index 00eb5e1db..2bb86c228 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -16,14 +16,17 @@ import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpCookie; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.io.IOException; import java.util.List; +import java.util.Objects; @Slf4j @Component @@ -31,6 +34,10 @@ public class OmsAuthFilter extends AbstractGatewayFilterFactory { private static final int OMS_AUTH_FILTER_ORDER = -1; private static final String USER_NAME_HEADER = "X-User-Name"; + private static final String AUTH_TOKEN_NEW_HEADER_KEY = "X-Auth-Token"; + private static final String CSRF_TOKEN_NEW_HEADER_KEY = "X-Csrf-Token"; + private static final String AUTH_TOKEN_KEY = "__Host-X-Auth-Token"; + private static final String CSRF_TOKEN_KEY = "__Host-X-Csrf-Token"; @Value("${oms.auth.enabled:false}") private Boolean omsAuthEnable; @@ -66,17 +73,18 @@ private Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) String uri = request.getURI().getPath(); log.info("Oms auth filter uri: {}", uri); - String fullPath = omsGatewayUrl + "/framework/v1/iam/roles/query-by-token"; + String fullPath = this.omsGatewayUrl + "/framework/v1/iam/roles/query-by-token"; log.info("oms auth full path: {}", fullPath); try { HttpPost httpPost = new HttpPost(fullPath); - String authToken = request.getHeaders().getFirst("Authorization"); - if (authToken != null && authToken.startsWith("Bearer ")) { - authToken = authToken.substring(7); - } - httpPost.setHeader("X-Auth-Token", authToken); + MultiValueMap cookies = request.getCookies(); + String authToken = getToken(cookies, AUTH_TOKEN_KEY); + String csrfToken = getToken(cookies, CSRF_TOKEN_KEY); + + httpPost.setHeader(AUTH_TOKEN_NEW_HEADER_KEY, authToken); + httpPost.setHeader(CSRF_TOKEN_NEW_HEADER_KEY, csrfToken); CloseableHttpResponse response = httpClient.execute(httpPost); String responseBody = EntityUtils.toString(response.getEntity()); @@ -104,6 +112,13 @@ private Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) } } + private String getToken(MultiValueMap cookies, String tokenKey) { + if (cookies.containsKey(tokenKey)) { + return Objects.requireNonNull(cookies.getFirst(tokenKey)).getValue(); + } + return ""; + } + @Data @NoArgsConstructor public static class Config {} diff --git a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java index 7d2e3f450..df670ba99 100644 --- a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java +++ b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java @@ -13,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -76,9 +77,7 @@ void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws omsAuthFilter.setHttpClient(httpClient); gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); - MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") - .header("Authorization", "Bearer valid-token") - .build(); + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); String successResponse = "{\"code\":200,\"message\":\"success\",\"data\":[\"testuser\"]}"; @@ -103,9 +102,7 @@ void testFilter_WhenOmsAuthEnabledAndTokenInvalid_ShouldReturn403() throws Excep omsAuthFilter.setHttpClient(httpClient); gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); - MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") - .header("Authorization", "Bearer invalid-token") - .build(); + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); String failureResponse = "{\"code\":403,\"message\":\"unauthorized\",\"data\":[]}"; @@ -142,36 +139,14 @@ void testFilter_WhenOmsAuthEnabledAndNoToken_ShouldReturn403() throws Exception } @Test - void testFilter_WhenOmsAuthEnabledAndBearerTokenFormat_ShouldExtractToken() throws Exception { - setField(omsAuthFilter, "omsAuthEnable", true); - omsAuthFilter.setHttpClient(httpClient); - gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); - - MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") - .header("Authorization", "Bearer test-token") - .build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - - String successResponse = "{\"code\":200,\"message\":\"success\",\"data\":[\"testuser\"]}"; - StringEntity entity = new StringEntity(successResponse, StandardCharsets.UTF_8); - when(httpResponse.getEntity()).thenReturn(entity); - when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); - - when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); - - gatewayFilter.filter(exchange, chain); - - verify(chain, times(1)).filter(any(ServerWebExchange.class)); - } - - @Test - void testFilter_WhenOmsAuthEnabledAndNonBearerToken_ShouldProcessAsIs() throws Exception { + void testFilter_WhenOmsAuthEnabledAndTokenInCookie_ShouldUseToken() throws Exception { setField(omsAuthFilter, "omsAuthEnable", true); omsAuthFilter.setHttpClient(httpClient); gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); + HttpCookie authCookie = new HttpCookie("__Host-X-Auth-Token", "test-token"); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") - .header("Authorization", "test-token") + .cookie(authCookie) .build(); ServerWebExchange exchange = MockServerWebExchange.from(request); From abc0b546a6302a050ed4e06d70e69fe8eb7e5862 Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Thu, 12 Mar 2026 16:53:33 +0800 Subject: [PATCH 04/18] refactor(api-gateway): support config oms.gateway.url with env --- backend/api-gateway/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/api-gateway/src/main/resources/application.yml b/backend/api-gateway/src/main/resources/application.yml index 705f3158f..0db832c43 100644 --- a/backend/api-gateway/src/main/resources/application.yml +++ b/backend/api-gateway/src/main/resources/application.yml @@ -35,7 +35,7 @@ datamate: expiration-seconds: 3600 oms: auth: - enabled: false + enabled: ${OMS_AUTH_ENABLED:false} gateway: url: ${OMS_GATEWAY_URL:http://localhost:8080} # 服务器端口配置 From 6786db5462cce33eb64268464cdfe304fecae45f Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Thu, 12 Mar 2026 16:59:40 +0800 Subject: [PATCH 05/18] fix(api-gateway): config omsservice url --- .../com/datamate/gateway/common/filter/OmsAuthFilter.java | 6 +++--- backend/api-gateway/src/main/resources/application.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index 2bb86c228..cb91877b4 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -42,8 +42,8 @@ public class OmsAuthFilter extends AbstractGatewayFilterFactory filter(ServerWebExchange exchange, GatewayFilterChain chain) String uri = request.getURI().getPath(); log.info("Oms auth filter uri: {}", uri); - String fullPath = this.omsGatewayUrl + "/framework/v1/iam/roles/query-by-token"; + String fullPath = this.omsServiceUrl + "/framework/v1/iam/roles/query-by-token"; log.info("oms auth full path: {}", fullPath); try { diff --git a/backend/api-gateway/src/main/resources/application.yml b/backend/api-gateway/src/main/resources/application.yml index 0db832c43..99167734b 100644 --- a/backend/api-gateway/src/main/resources/application.yml +++ b/backend/api-gateway/src/main/resources/application.yml @@ -36,8 +36,8 @@ datamate: oms: auth: enabled: ${OMS_AUTH_ENABLED:false} - gateway: - url: ${OMS_GATEWAY_URL:http://localhost:8080} + service: + url: ${OMS_SERVICE_URL:https://omsservice:18082} # 服务器端口配置 server: port: 8080 # 必须有这个配置 \ No newline at end of file From 785e1fec1f8603a6e8bc31185f4ad800fff8d827 Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Thu, 12 Mar 2026 17:01:48 +0800 Subject: [PATCH 06/18] fix(api-gateway): return UNAUTHORIZED when get user fail --- .../com/datamate/gateway/common/filter/OmsAuthFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index cb91877b4..de31a9453 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -94,7 +94,7 @@ private Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) objectMapper.getTypeFactory().constructParametricType(ResultVo.class, List.class)); if (resultVo.getData() == null || resultVo.getData().isEmpty()) { - exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); + exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); log.error("Authentication failed: Token is null or invalid."); return exchange.getResponse().setComplete(); } @@ -107,7 +107,7 @@ private Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) return chain.filter(exchange.mutate().request(newRequest).build()); } catch (IOException | ParseException e) { log.error("Exception occurred during POST request: {}", e.getMessage(), e); - exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); + exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } } From f8795e62600d52d0566280bbdde0cbe80e0e2f4e Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Tue, 17 Mar 2026 10:50:15 +0800 Subject: [PATCH 07/18] feat(api-gateway): ignore ssl verify --- .../config/SslIgnoreHttpClientFactory.java | 41 +++++++++++ .../gateway/common/filter/OmsAuthFilter.java | 59 ++++++++------- .../common/filter/OmsAuthFilterTest.java | 72 +++++++++---------- 3 files changed, 102 insertions(+), 70 deletions(-) create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/common/config/SslIgnoreHttpClientFactory.java diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/SslIgnoreHttpClientFactory.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/SslIgnoreHttpClientFactory.java new file mode 100644 index 000000000..17a1a227c --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/SslIgnoreHttpClientFactory.java @@ -0,0 +1,41 @@ +package com.datamate.gateway.common.config; + +import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; +import org.apache.hc.client5.http.ssl.TrustAllStrategy; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.springframework.stereotype.Component; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; + +/** + * SslIgnoreHttpClientFactory is a factory that creates a CloseableHttpClient that ignores SSL errors. + * + * @author songyongtan + * @date 2026-03-16 + */ +@Slf4j +@Component +public class SslIgnoreHttpClientFactory { + public CloseableHttpClient getHttpClient() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() + .setSslContext(SSLContextBuilder.create() + .setProtocol("TLSv1.2") + .loadTrustMaterial(TrustAllStrategy.INSTANCE) + .build()) + .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) + .build(); + PoolingHttpClientConnectionManager connManager = + PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(sslSocketFactory).build(); + return HttpClients.custom().setConnectionManager(connManager).build(); + } +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index de31a9453..7e3c0aeef 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -1,21 +1,17 @@ package com.datamate.gateway.common.filter; +import com.datamate.gateway.common.config.SslIgnoreHttpClientFactory; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.http.HttpCookie; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -28,31 +24,43 @@ import java.util.List; import java.util.Objects; +/** + * OmsAuthFilter is a global filter that authenticates requests to the OMS service. + * + * @author songyongtan + * @date 2026-03-16 + */ @Slf4j @Component -@RequiredArgsConstructor -public class OmsAuthFilter extends AbstractGatewayFilterFactory { - private static final int OMS_AUTH_FILTER_ORDER = -1; +public class OmsAuthFilter implements GlobalFilter { private static final String USER_NAME_HEADER = "X-User-Name"; private static final String AUTH_TOKEN_NEW_HEADER_KEY = "X-Auth-Token"; private static final String CSRF_TOKEN_NEW_HEADER_KEY = "X-Csrf-Token"; private static final String AUTH_TOKEN_KEY = "__Host-X-Auth-Token"; private static final String CSRF_TOKEN_KEY = "__Host-X-Csrf-Token"; - @Value("${oms.auth.enabled:false}") - private Boolean omsAuthEnable; + private final Boolean omsAuthEnable; - @Value("${oms.service.url}") - private String omsServiceUrl; + private final String omsServiceUrl; - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper = new ObjectMapper(); + + private final SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory; private CloseableHttpClient httpClient; - public OmsAuthFilter() { - super(Config.class); - this.objectMapper = new ObjectMapper(); - this.httpClient = HttpClients.createDefault(); + public OmsAuthFilter( + @Value("${oms.auth.enabled:false}") Boolean omsAuthEnable, + @Value("${oms.service.url}") String omsServiceUrl, + SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory) { + this.omsAuthEnable = omsAuthEnable; + this.omsServiceUrl = omsServiceUrl; + this.sslIgnoreHttpClientFactory = sslIgnoreHttpClientFactory; + try { + this.httpClient = this.sslIgnoreHttpClientFactory.getHttpClient(); + } catch (Exception e) { + log.error("Failed to create SSL ignore HTTP client", e); + } } public void setHttpClient(CloseableHttpClient httpClient) { @@ -60,21 +68,16 @@ public void setHttpClient(CloseableHttpClient httpClient) { } @Override - public GatewayFilter apply(Config config) { + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("OmsAuthFilter is apply, omsAuthEnable: {}", this.omsAuthEnable); if (!this.omsAuthEnable) { - return (exchange, chain) -> chain.filter(exchange); + return chain.filter(exchange); } - return new OrderedGatewayFilter(this::filter, OMS_AUTH_FILTER_ORDER); - } - - private Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String uri = request.getURI().getPath(); log.info("Oms auth filter uri: {}", uri); String fullPath = this.omsServiceUrl + "/framework/v1/iam/roles/query-by-token"; - log.info("oms auth full path: {}", fullPath); try { HttpPost httpPost = new HttpPost(fullPath); @@ -119,14 +122,10 @@ private String getToken(MultiValueMap cookies, String tokenK return ""; } - @Data - @NoArgsConstructor - public static class Config {} - @Data public static class ResultVo { private Integer code; - private String message; + private String msg; private T data; } } diff --git a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java index df670ba99..a99cfd0a0 100644 --- a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java +++ b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java @@ -1,17 +1,15 @@ package com.datamate.gateway.common.filter; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.datamate.gateway.common.config.SslIgnoreHttpClientFactory; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.StringEntity; 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.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; @@ -22,14 +20,17 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; -import java.util.List; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +/** + * OmsAuthFilterTest is a test class for OmsAuthFilter. + * + * @author songyongtan + * @date 2026-03-16 + */ @ExtendWith(MockitoExtension.class) class OmsAuthFilterTest { @@ -42,52 +43,49 @@ class OmsAuthFilterTest { @Mock private CloseableHttpResponse httpResponse; + @Mock + private SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory; + private OmsAuthFilter omsAuthFilter; - private GatewayFilter gatewayFilter; @BeforeEach void setUp() throws Exception { - omsAuthFilter = new OmsAuthFilter(); - setField(omsAuthFilter, "omsAuthEnable", false); - setField(omsAuthFilter, "omsGatewayUrl", "http://localhost:8080"); - gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); + when(sslIgnoreHttpClientFactory.getHttpClient()).thenReturn(httpClient); } - private void setField(Object target, String fieldName, Object value) throws Exception { - Field field = target.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - field.set(target, value); + private OmsAuthFilter createOmsAuthFilter(Boolean omsAuthEnable) { + return new OmsAuthFilter(omsAuthEnable, "http://localhost:8080", sslIgnoreHttpClientFactory); } @Test void testFilter_WhenOmsAuthDisabled_ShouldPassThrough() { + omsAuthFilter = createOmsAuthFilter(false); + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); - gatewayFilter.filter(exchange, chain); + omsAuthFilter.filter(exchange, chain); verify(chain, times(1)).filter(any(ServerWebExchange.class)); } @Test void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws Exception { - setField(omsAuthFilter, "omsAuthEnable", true); - omsAuthFilter.setHttpClient(httpClient); - gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); + omsAuthFilter = createOmsAuthFilter(true); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); - String successResponse = "{\"code\":200,\"message\":\"success\",\"data\":[\"testuser\"]}"; + String successResponse = "{\"code\":200,\"msg\":\"success\",\"data\":[\"testuser\"]}"; StringEntity entity = new StringEntity(successResponse, StandardCharsets.UTF_8); when(httpResponse.getEntity()).thenReturn(entity); when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); - gatewayFilter.filter(exchange, chain); + omsAuthFilter.filter(exchange, chain); verify(chain, times(1)).filter(argThat(ex -> { HttpHeaders headers = ex.getRequest().getHeaders(); @@ -97,52 +95,46 @@ void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws } @Test - void testFilter_WhenOmsAuthEnabledAndTokenInvalid_ShouldReturn403() throws Exception { - setField(omsAuthFilter, "omsAuthEnable", true); - omsAuthFilter.setHttpClient(httpClient); - gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); + void testFilter_WhenOmsAuthEnabledAndTokenInvalid_ShouldReturn401() throws Exception { + omsAuthFilter = createOmsAuthFilter(true); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); - String failureResponse = "{\"code\":403,\"message\":\"unauthorized\",\"data\":[]}"; + String failureResponse = "{\"code\":403,\"msg\":\"unauthorized\",\"data\":[]}"; StringEntity entity = new StringEntity(failureResponse, StandardCharsets.UTF_8); when(httpResponse.getEntity()).thenReturn(entity); when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); - gatewayFilter.filter(exchange, chain); + omsAuthFilter.filter(exchange, chain); ServerHttpResponse response = exchange.getResponse(); - assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); verify(chain, never()).filter(any(ServerWebExchange.class)); } @Test - void testFilter_WhenOmsAuthEnabledAndNoToken_ShouldReturn403() throws Exception { - setField(omsAuthFilter, "omsAuthEnable", true); - omsAuthFilter.setHttpClient(httpClient); - gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); + void testFilter_WhenOmsAuthEnabledAndNoToken_ShouldReturn401() throws Exception { + omsAuthFilter = createOmsAuthFilter(true); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); - String failureResponse = "{\"code\":403,\"message\":\"unauthorized\",\"data\":[]}"; + String failureResponse = "{\"code\":401,\"msg\":\"unauthorized\",\"data\":[]}"; StringEntity entity = new StringEntity(failureResponse, StandardCharsets.UTF_8); when(httpResponse.getEntity()).thenReturn(entity); when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); - gatewayFilter.filter(exchange, chain); + omsAuthFilter.filter(exchange, chain); ServerHttpResponse response = exchange.getResponse(); - assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); verify(chain, never()).filter(any(ServerWebExchange.class)); } @Test void testFilter_WhenOmsAuthEnabledAndTokenInCookie_ShouldUseToken() throws Exception { - setField(omsAuthFilter, "omsAuthEnable", true); - omsAuthFilter.setHttpClient(httpClient); - gatewayFilter = omsAuthFilter.apply(new OmsAuthFilter.Config()); + omsAuthFilter = createOmsAuthFilter(true); HttpCookie authCookie = new HttpCookie("__Host-X-Auth-Token", "test-token"); MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") @@ -150,14 +142,14 @@ void testFilter_WhenOmsAuthEnabledAndTokenInCookie_ShouldUseToken() throws Excep .build(); ServerWebExchange exchange = MockServerWebExchange.from(request); - String successResponse = "{\"code\":200,\"message\":\"success\",\"data\":[\"testuser\"]}"; + String successResponse = "{\"code\":200,\"msg\":\"success\",\"data\":[\"testuser\"]}"; StringEntity entity = new StringEntity(successResponse, StandardCharsets.UTF_8); when(httpResponse.getEntity()).thenReturn(entity); when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); - gatewayFilter.filter(exchange, chain); + omsAuthFilter.filter(exchange, chain); verify(chain, times(1)).filter(any(ServerWebExchange.class)); } From 1c6ffc690bccb9a2fb7ffe3d65fc5ca369e9ae5e Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Tue, 17 Mar 2026 10:56:12 +0800 Subject: [PATCH 08/18] refactor: extract getUserNameFromOms method to improve code readability - Extracted user name retrieval logic from filter method into dedicated getUserNameFromOms method - Simplified filter method by delegating user name retrieval - Added initialization log in constructor - Improved code maintainability and testability --- .../gateway/common/filter/OmsAuthFilter.java | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index 7e3c0aeef..9c2872662 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -53,6 +53,7 @@ public OmsAuthFilter( @Value("${oms.auth.enabled:false}") Boolean omsAuthEnable, @Value("${oms.service.url}") String omsServiceUrl, SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory) { + log.info("OmsAuthFilter is apply, omsAuthEnable: {}", omsAuthEnable); this.omsAuthEnable = omsAuthEnable; this.omsServiceUrl = omsServiceUrl; this.sslIgnoreHttpClientFactory = sslIgnoreHttpClientFactory; @@ -69,7 +70,6 @@ public void setHttpClient(CloseableHttpClient httpClient) { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - log.info("OmsAuthFilter is apply, omsAuthEnable: {}", this.omsAuthEnable); if (!this.omsAuthEnable) { return chain.filter(exchange); } @@ -77,32 +77,13 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { String uri = request.getURI().getPath(); log.info("Oms auth filter uri: {}", uri); - String fullPath = this.omsServiceUrl + "/framework/v1/iam/roles/query-by-token"; - try { - HttpPost httpPost = new HttpPost(fullPath); - - MultiValueMap cookies = request.getCookies(); - String authToken = getToken(cookies, AUTH_TOKEN_KEY); - String csrfToken = getToken(cookies, CSRF_TOKEN_KEY); - - httpPost.setHeader(AUTH_TOKEN_NEW_HEADER_KEY, authToken); - httpPost.setHeader(CSRF_TOKEN_NEW_HEADER_KEY, csrfToken); - - CloseableHttpResponse response = httpClient.execute(httpPost); - String responseBody = EntityUtils.toString(response.getEntity()); - log.info("response code: {}, response body: {}", response.getCode(), responseBody); - - ResultVo> resultVo = objectMapper.readValue(responseBody, - objectMapper.getTypeFactory().constructParametricType(ResultVo.class, List.class)); - - if (resultVo.getData() == null || resultVo.getData().isEmpty()) { + String userName = this.getUserNameFromOms(request); + if (userName == null) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); log.error("Authentication failed: Token is null or invalid."); return exchange.getResponse().setComplete(); } - - String userName = resultVo.getData().get(0); ServerHttpRequest newRequest = request.mutate() .header(USER_NAME_HEADER, userName) .build(); @@ -115,6 +96,32 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { } } + private String getUserNameFromOms(ServerHttpRequest request) throws IOException, ParseException { + String fullPath = this.omsServiceUrl + "/framework/v1/iam/roles/query-by-token"; + + HttpPost httpPost = new HttpPost(fullPath); + + MultiValueMap cookies = request.getCookies(); + String authToken = getToken(cookies, AUTH_TOKEN_KEY); + String csrfToken = getToken(cookies, CSRF_TOKEN_KEY); + + httpPost.setHeader(AUTH_TOKEN_NEW_HEADER_KEY, authToken); + httpPost.setHeader(CSRF_TOKEN_NEW_HEADER_KEY, csrfToken); + + CloseableHttpResponse response = httpClient.execute(httpPost); + String responseBody = EntityUtils.toString(response.getEntity()); + log.info("response code: {}, response body: {}", response.getCode(), responseBody); + + ResultVo> resultVo = objectMapper.readValue(responseBody, + objectMapper.getTypeFactory().constructParametricType(ResultVo.class, List.class)); + + if (resultVo.getData() == null || resultVo.getData().isEmpty()) { + return null; + } + + return resultVo.getData().get(0); + } + private String getToken(MultiValueMap cookies, String tokenKey) { if (cookies.containsKey(tokenKey)) { return Objects.requireNonNull(cookies.getFirst(tokenKey)).getValue(); From 0439494d6d3f4ba8021776a48a1a26b32c19bf4c Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Tue, 17 Mar 2026 11:08:41 +0800 Subject: [PATCH 09/18] feat: add real IP retrieval and set to request header - Add REAL_IP_HEADER_KEY constant for X-Real-Ip header - Add getRealIp method to extract real client IP from various proxy headers - Support X-Forwarded-For, X-Real-IP, Proxy-Client-IP, WL-Proxy-Client-IP headers - Handle multiple IPs in X-Forwarded-For header by taking the first one - Set real IP to request header when calling OMS authentication API --- .../gateway/common/filter/OmsAuthFilter.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index 9c2872662..08ffa5920 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -36,6 +36,7 @@ public class OmsAuthFilter implements GlobalFilter { private static final String USER_NAME_HEADER = "X-User-Name"; private static final String AUTH_TOKEN_NEW_HEADER_KEY = "X-Auth-Token"; private static final String CSRF_TOKEN_NEW_HEADER_KEY = "X-Csrf-Token"; + private static final String REAL_IP_HEADER_KEY = "X-Real-Ip"; private static final String AUTH_TOKEN_KEY = "__Host-X-Auth-Token"; private static final String CSRF_TOKEN_KEY = "__Host-X-Csrf-Token"; @@ -104,9 +105,11 @@ private String getUserNameFromOms(ServerHttpRequest request) throws IOException, MultiValueMap cookies = request.getCookies(); String authToken = getToken(cookies, AUTH_TOKEN_KEY); String csrfToken = getToken(cookies, CSRF_TOKEN_KEY); + String realIp = getRealIp(request); httpPost.setHeader(AUTH_TOKEN_NEW_HEADER_KEY, authToken); httpPost.setHeader(CSRF_TOKEN_NEW_HEADER_KEY, csrfToken); + httpPost.setHeader(REAL_IP_HEADER_KEY, realIp); CloseableHttpResponse response = httpClient.execute(httpPost); String responseBody = EntityUtils.toString(response.getEntity()); @@ -122,6 +125,26 @@ private String getUserNameFromOms(ServerHttpRequest request) throws IOException, return resultVo.getData().get(0); } + private String getRealIp(ServerHttpRequest request) { + String ip = request.getHeaders().getFirst("X-Forwarded-For"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("X-Real-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("WL-Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddress() != null ? request.getRemoteAddress().getAddress().getHostAddress() : ""; + } + if (ip != null && ip.contains(",")) { + ip = ip.split(",")[0].trim(); + } + return ip != null ? ip : ""; + } + private String getToken(MultiValueMap cookies, String tokenKey) { if (cookies.containsKey(tokenKey)) { return Objects.requireNonNull(cookies.getFirst(tokenKey)).getValue(); From ba2c0b8ba322aa634d8d668b2c91152274d497c2 Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Tue, 17 Mar 2026 11:41:22 +0800 Subject: [PATCH 10/18] refactor(api-gateway): extract OMS authentication logic to service layer with DDD architecture --- .../gateway/common/filter/OmsAuthFilter.java | 76 +++------------- .../infrastructure/client/OmsService.java | 22 +++++ .../client/impl/OmsServiceImpl.java | 91 +++++++++++++++++++ .../common/filter/OmsAuthFilterTest.java | 40 ++------ 4 files changed, 135 insertions(+), 94 deletions(-) create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/OmsService.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index 08ffa5920..9bc3d766f 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -1,14 +1,7 @@ package com.datamate.gateway.common.filter; -import com.datamate.gateway.common.config.SslIgnoreHttpClientFactory; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.core5.http.ParseException; -import org.apache.hc.core5.http.io.entity.EntityUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; @@ -18,10 +11,11 @@ import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; + +import com.datamate.gateway.infrastructure.client.OmsService; + import reactor.core.publisher.Mono; -import java.io.IOException; -import java.util.List; import java.util.Objects; /** @@ -34,39 +28,18 @@ @Component public class OmsAuthFilter implements GlobalFilter { private static final String USER_NAME_HEADER = "X-User-Name"; - private static final String AUTH_TOKEN_NEW_HEADER_KEY = "X-Auth-Token"; - private static final String CSRF_TOKEN_NEW_HEADER_KEY = "X-Csrf-Token"; - private static final String REAL_IP_HEADER_KEY = "X-Real-Ip"; private static final String AUTH_TOKEN_KEY = "__Host-X-Auth-Token"; private static final String CSRF_TOKEN_KEY = "__Host-X-Csrf-Token"; private final Boolean omsAuthEnable; - - private final String omsServiceUrl; - - private final ObjectMapper objectMapper = new ObjectMapper(); - - private final SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory; - - private CloseableHttpClient httpClient; + private final OmsService omsService; public OmsAuthFilter( @Value("${oms.auth.enabled:false}") Boolean omsAuthEnable, - @Value("${oms.service.url}") String omsServiceUrl, - SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory) { + OmsService omsService) { log.info("OmsAuthFilter is apply, omsAuthEnable: {}", omsAuthEnable); this.omsAuthEnable = omsAuthEnable; - this.omsServiceUrl = omsServiceUrl; - this.sslIgnoreHttpClientFactory = sslIgnoreHttpClientFactory; - try { - this.httpClient = this.sslIgnoreHttpClientFactory.getHttpClient(); - } catch (Exception e) { - log.error("Failed to create SSL ignore HTTP client", e); - } - } - - public void setHttpClient(CloseableHttpClient httpClient) { - this.httpClient = httpClient; + this.omsService = omsService; } @Override @@ -79,7 +52,12 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("Oms auth filter uri: {}", uri); try { - String userName = this.getUserNameFromOms(request); + MultiValueMap cookies = request.getCookies(); + String authToken = getToken(cookies, AUTH_TOKEN_KEY); + String csrfToken = getToken(cookies, CSRF_TOKEN_KEY); + String realIp = getRealIp(request); + + String userName = this.omsService.getUserNameFromOms(authToken, csrfToken, realIp); if (userName == null) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); log.error("Authentication failed: Token is null or invalid."); @@ -90,41 +68,13 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { .build(); return chain.filter(exchange.mutate().request(newRequest).build()); - } catch (IOException | ParseException e) { + } catch (Exception e) { log.error("Exception occurred during POST request: {}", e.getMessage(), e); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } } - private String getUserNameFromOms(ServerHttpRequest request) throws IOException, ParseException { - String fullPath = this.omsServiceUrl + "/framework/v1/iam/roles/query-by-token"; - - HttpPost httpPost = new HttpPost(fullPath); - - MultiValueMap cookies = request.getCookies(); - String authToken = getToken(cookies, AUTH_TOKEN_KEY); - String csrfToken = getToken(cookies, CSRF_TOKEN_KEY); - String realIp = getRealIp(request); - - httpPost.setHeader(AUTH_TOKEN_NEW_HEADER_KEY, authToken); - httpPost.setHeader(CSRF_TOKEN_NEW_HEADER_KEY, csrfToken); - httpPost.setHeader(REAL_IP_HEADER_KEY, realIp); - - CloseableHttpResponse response = httpClient.execute(httpPost); - String responseBody = EntityUtils.toString(response.getEntity()); - log.info("response code: {}, response body: {}", response.getCode(), responseBody); - - ResultVo> resultVo = objectMapper.readValue(responseBody, - objectMapper.getTypeFactory().constructParametricType(ResultVo.class, List.class)); - - if (resultVo.getData() == null || resultVo.getData().isEmpty()) { - return null; - } - - return resultVo.getData().get(0); - } - private String getRealIp(ServerHttpRequest request) { String ip = request.getHeaders().getFirst("X-Forwarded-For"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/OmsService.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/OmsService.java new file mode 100644 index 000000000..8d3eb8a54 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/OmsService.java @@ -0,0 +1,22 @@ +package com.datamate.gateway.infrastructure.client; + +import java.io.IOException; + +/** + * OmsService is a service that interacts with the OMS service. + * + * @author songyongtan + * @date 2026-03-16 + */ +public interface OmsService { + /** + * getUserNameFromOms gets the user name from the OMS service. + * + * @param authToken the auth token + * @param csrfToken the csrf token + * @param realIp the real ip + * @return the user name + * @throws IOException if an error occurs + */ + String getUserNameFromOms(String authToken, String csrfToken, String realIp); +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java new file mode 100644 index 000000000..1d356ed99 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java @@ -0,0 +1,91 @@ +package com.datamate.gateway.infrastructure.client.impl; + +import com.datamate.gateway.common.config.SslIgnoreHttpClientFactory; +import com.datamate.gateway.infrastructure.client.OmsService; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; + +/** + * OmsServiceImpl is a service that interacts with the OMS service. + * + * @author songyongtan + * @date 2026-03-16 + */ +@Slf4j +@Service +public class OmsServiceImpl implements OmsService { + private static final String AUTH_TOKEN_NEW_HEADER_KEY = "X-Auth-Token"; + private static final String CSRF_TOKEN_NEW_HEADER_KEY = "X-Csrf-Token"; + private static final String REAL_IP_HEADER_KEY = "X-Real-Ip"; + + @Value("${oms.service.url}") + private final String omsServiceUrl; + + private final ObjectMapper objectMapper; + + private final SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory; + + private CloseableHttpClient httpClient; + + public OmsServiceImpl( + @Value("${oms.service.url}") String omsServiceUrl, + ObjectMapper objectMapper, + SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory) { + this.omsServiceUrl = omsServiceUrl; + this.objectMapper = objectMapper; + this.sslIgnoreHttpClientFactory = sslIgnoreHttpClientFactory; + try { + this.httpClient = this.sslIgnoreHttpClientFactory.getHttpClient(); + } catch (Exception e) { + log.error("Failed to create SSL ignore HTTP client", e); + } + } + + @Override + public String getUserNameFromOms(String authToken, String csrfToken, String realIp) { + try { + String fullPath = this.omsServiceUrl + "/framework/v1/iam/roles/query-by-token"; + + HttpPost httpPost = new HttpPost(fullPath); + + httpPost.setHeader(AUTH_TOKEN_NEW_HEADER_KEY, authToken); + httpPost.setHeader(CSRF_TOKEN_NEW_HEADER_KEY, csrfToken); + httpPost.setHeader(REAL_IP_HEADER_KEY, realIp); + + CloseableHttpResponse response = httpClient.execute(httpPost); + String responseBody = EntityUtils.toString(response.getEntity()); + log.info("response code: {}, response body: {}", response.getCode(), responseBody); + + ResultVo> resultVo = objectMapper.readValue(responseBody, + objectMapper.getTypeFactory().constructParametricType(ResultVo.class, List.class)); + + if (resultVo.getData() == null || resultVo.getData().isEmpty()) { + return null; + } + + return resultVo.getData().get(0); + } catch (IOException | ParseException e) { + log.error("Failed to get user name from OMS service", e); + return null; + } + } + + @Data + public class ResultVo { + private Integer code; + private String msg; + private T data; + } +} diff --git a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java index a99cfd0a0..e8dbd3e36 100644 --- a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java +++ b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java @@ -1,10 +1,5 @@ package com.datamate.gateway.common.filter; -import com.datamate.gateway.common.config.SslIgnoreHttpClientFactory; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.core5.http.io.entity.StringEntity; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -18,9 +13,11 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.server.ServerWebExchange; + +import com.datamate.gateway.infrastructure.client.OmsService; + import reactor.core.publisher.Mono; -import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -38,23 +35,16 @@ class OmsAuthFilterTest { private GatewayFilterChain chain; @Mock - private CloseableHttpClient httpClient; - - @Mock - private CloseableHttpResponse httpResponse; - - @Mock - private SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory; + private OmsService omsService; private OmsAuthFilter omsAuthFilter; @BeforeEach void setUp() throws Exception { - when(sslIgnoreHttpClientFactory.getHttpClient()).thenReturn(httpClient); } private OmsAuthFilter createOmsAuthFilter(Boolean omsAuthEnable) { - return new OmsAuthFilter(omsAuthEnable, "http://localhost:8080", sslIgnoreHttpClientFactory); + return new OmsAuthFilter(omsAuthEnable, omsService); } @Test @@ -78,10 +68,7 @@ void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); - String successResponse = "{\"code\":200,\"msg\":\"success\",\"data\":[\"testuser\"]}"; - StringEntity entity = new StringEntity(successResponse, StandardCharsets.UTF_8); - when(httpResponse.getEntity()).thenReturn(entity); - when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + when(omsService.getUserNameFromOms(anyString(), anyString(), anyString())).thenReturn("testuser"); when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); @@ -101,10 +88,7 @@ void testFilter_WhenOmsAuthEnabledAndTokenInvalid_ShouldReturn401() throws Excep MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); - String failureResponse = "{\"code\":403,\"msg\":\"unauthorized\",\"data\":[]}"; - StringEntity entity = new StringEntity(failureResponse, StandardCharsets.UTF_8); - when(httpResponse.getEntity()).thenReturn(entity); - when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + when(omsService.getUserNameFromOms(anyString(), anyString(), anyString())).thenReturn(null); omsAuthFilter.filter(exchange, chain); @@ -120,10 +104,7 @@ void testFilter_WhenOmsAuthEnabledAndNoToken_ShouldReturn401() throws Exception MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); ServerWebExchange exchange = MockServerWebExchange.from(request); - String failureResponse = "{\"code\":401,\"msg\":\"unauthorized\",\"data\":[]}"; - StringEntity entity = new StringEntity(failureResponse, StandardCharsets.UTF_8); - when(httpResponse.getEntity()).thenReturn(entity); - when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + when(omsService.getUserNameFromOms(anyString(), anyString(), anyString())).thenReturn(null); omsAuthFilter.filter(exchange, chain); @@ -142,10 +123,7 @@ void testFilter_WhenOmsAuthEnabledAndTokenInCookie_ShouldUseToken() throws Excep .build(); ServerWebExchange exchange = MockServerWebExchange.from(request); - String successResponse = "{\"code\":200,\"msg\":\"success\",\"data\":[\"testuser\"]}"; - StringEntity entity = new StringEntity(successResponse, StandardCharsets.UTF_8); - when(httpResponse.getEntity()).thenReturn(entity); - when(httpClient.execute(any(HttpPost.class))).thenReturn(httpResponse); + when(omsService.getUserNameFromOms(anyString(), anyString(), anyString())).thenReturn("testuser"); when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); From 1f185236ee04e47b05647cede9d8c45df5d2ed01 Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Tue, 17 Mar 2026 14:15:46 +0800 Subject: [PATCH 11/18] feat(api-gateway): add OmsExtensionService for user group ID retrieval with comprehensive documentation --- .../gateway/common/filter/OmsAuthFilter.java | 36 ++++++++++++++++++- .../client/OmsExtensionService.java | 17 +++++++++ .../client/impl/OmsExtensionServiceImpl.java | 18 ++++++++++ .../common/filter/OmsAuthFilterTest.java | 11 ++++-- 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/OmsExtensionService.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index 9bc3d766f..d8cfaaaa9 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -12,6 +12,7 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; +import com.datamate.gateway.infrastructure.client.OmsExtensionService; import com.datamate.gateway.infrastructure.client.OmsService; import reactor.core.publisher.Mono; @@ -28,20 +29,38 @@ @Component public class OmsAuthFilter implements GlobalFilter { private static final String USER_NAME_HEADER = "X-User-Name"; + private static final String USER_GROUP_ID_HEADER = "X-User-Group-Id"; private static final String AUTH_TOKEN_KEY = "__Host-X-Auth-Token"; private static final String CSRF_TOKEN_KEY = "__Host-X-Csrf-Token"; private final Boolean omsAuthEnable; private final OmsService omsService; + private final OmsExtensionService omsExtensionService; + /** + * OmsAuthFilter constructor. + * + * @param omsAuthEnable whether OMS authentication is enabled + * @param omsService OMS service client + * @param omsExtensionService OMS extension service client + */ public OmsAuthFilter( @Value("${oms.auth.enabled:false}") Boolean omsAuthEnable, - OmsService omsService) { + OmsService omsService, + OmsExtensionService omsExtensionService) { log.info("OmsAuthFilter is apply, omsAuthEnable: {}", omsAuthEnable); this.omsAuthEnable = omsAuthEnable; this.omsService = omsService; + this.omsExtensionService = omsExtensionService; } + /** + * filter processes the request and adds authentication headers. + * + * @param exchange the server web exchange + * @param chain the gateway filter chain + * @return Mono completion signal + */ @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { if (!this.omsAuthEnable) { @@ -63,8 +82,10 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.error("Authentication failed: Token is null or invalid."); return exchange.getResponse().setComplete(); } + String userGroupId = this.omsExtensionService.getUserGroupId(userName); ServerHttpRequest newRequest = request.mutate() .header(USER_NAME_HEADER, userName) + .header(USER_GROUP_ID_HEADER, userGroupId) .build(); return chain.filter(exchange.mutate().request(newRequest).build()); @@ -75,6 +96,12 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { } } + /** + * getRealIp gets the real IP address from the request. + * + * @param request the HTTP request + * @return the real IP address + */ private String getRealIp(ServerHttpRequest request) { String ip = request.getHeaders().getFirst("X-Forwarded-For"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { @@ -95,6 +122,13 @@ private String getRealIp(ServerHttpRequest request) { return ip != null ? ip : ""; } + /** + * getToken gets the token value from cookies. + * + * @param cookies the cookies map + * @param tokenKey the token key + * @return the token value + */ private String getToken(MultiValueMap cookies, String tokenKey) { if (cookies.containsKey(tokenKey)) { return Objects.requireNonNull(cookies.getFirst(tokenKey)).getValue(); diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/OmsExtensionService.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/OmsExtensionService.java new file mode 100644 index 000000000..72fe884a2 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/OmsExtensionService.java @@ -0,0 +1,17 @@ +package com.datamate.gateway.infrastructure.client; + +/** + * OmsExtensionService is a service interface for OMS extension operations. + * + * @author songyongtan + * @date 2026-03-17 + */ +public interface OmsExtensionService { + /** + * getUserGroupId gets the user group ID by user name. + * + * @param userName the user name + * @return the user group ID + */ + String getUserGroupId(String userName); +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java new file mode 100644 index 000000000..270ad4926 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java @@ -0,0 +1,18 @@ +package com.datamate.gateway.infrastructure.client.impl; + +import com.datamate.gateway.infrastructure.client.OmsExtensionService; +import org.springframework.stereotype.Service; + +/** + * OmsExtensionServiceImpl is an implementation of OmsExtensionService. + * + * @author songyongtan + * @date 2026-03-17 + */ +@Service +public class OmsExtensionServiceImpl implements OmsExtensionService { + @Override + public String getUserGroupId(String userName) { + return userName; + } +} diff --git a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java index e8dbd3e36..0841724ca 100644 --- a/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java +++ b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java @@ -14,6 +14,7 @@ import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.server.ServerWebExchange; +import com.datamate.gateway.infrastructure.client.OmsExtensionService; import com.datamate.gateway.infrastructure.client.OmsService; import reactor.core.publisher.Mono; @@ -37,6 +38,9 @@ class OmsAuthFilterTest { @Mock private OmsService omsService; + @Mock + private OmsExtensionService omsExtensionService; + private OmsAuthFilter omsAuthFilter; @BeforeEach @@ -44,7 +48,7 @@ void setUp() throws Exception { } private OmsAuthFilter createOmsAuthFilter(Boolean omsAuthEnable) { - return new OmsAuthFilter(omsAuthEnable, omsService); + return new OmsAuthFilter(omsAuthEnable, omsService, omsExtensionService); } @Test @@ -69,6 +73,7 @@ void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws ServerWebExchange exchange = MockServerWebExchange.from(request); when(omsService.getUserNameFromOms(anyString(), anyString(), anyString())).thenReturn("testuser"); + when(omsExtensionService.getUserGroupId("testuser")).thenReturn("testuser"); when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); @@ -77,7 +82,9 @@ void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws verify(chain, times(1)).filter(argThat(ex -> { HttpHeaders headers = ex.getRequest().getHeaders(); return headers.containsKey("X-User-Name") && - "testuser".equals(headers.getFirst("X-User-Name")); + "testuser".equals(headers.getFirst("X-User-Name")) && + headers.containsKey("X-User-Group-Id") && + "testuser".equals(headers.getFirst("X-User-Group-Id")); })); } From a44bb83a4eabc21bdef7ef5ac64ce6a07e47e084 Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Tue, 17 Mar 2026 14:34:09 +0800 Subject: [PATCH 12/18] fix(api-gateway): fix static inner class ResultVo compilation error by using @Getter instead of @Data --- .../datamate/gateway/common/filter/OmsAuthFilter.java | 8 -------- .../infrastructure/client/impl/OmsServiceImpl.java | 9 +++++++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index d8cfaaaa9..e6af75684 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -1,6 +1,5 @@ package com.datamate.gateway.common.filter; -import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.GatewayFilterChain; @@ -135,11 +134,4 @@ private String getToken(MultiValueMap cookies, String tokenK } return ""; } - - @Data - public static class ResultVo { - private Integer code; - private String msg; - private T data; - } } diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java index 1d356ed99..57c585b6e 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java @@ -4,7 +4,7 @@ import com.datamate.gateway.infrastructure.client.OmsService; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Data; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -82,7 +82,12 @@ public String getUserNameFromOms(String authToken, String csrfToken, String real } } - @Data + /** + * ResultVo is a generic result wrapper. + * + * @param the type of data + */ + @Getter public class ResultVo { private Integer code; private String msg; From 4ff287b54c3b1010d450359f6e2d351cc29acda4 Mon Sep 17 00:00:00 2001 From: loveTsong <271667068@qq.com> Date: Tue, 17 Mar 2026 15:48:35 +0800 Subject: [PATCH 13/18] refactor(api-gateway): adjust IP retrieval priority order and revert ResultVo annotation --- .../com/datamate/gateway/common/filter/OmsAuthFilter.java | 4 ++-- .../infrastructure/client/impl/OmsServiceImpl.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index e6af75684..0bb4a4fd9 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -102,9 +102,9 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { * @return the real IP address */ private String getRealIp(ServerHttpRequest request) { - String ip = request.getHeaders().getFirst("X-Forwarded-For"); + String ip = request.getHeaders().getFirst("X-Real-IP"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeaders().getFirst("X-Real-IP"); + ip = request.getHeaders().getFirst("X-Forwarded-For"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeaders().getFirst("Proxy-Client-IP"); diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java index 57c585b6e..64970cd9c 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java @@ -4,7 +4,7 @@ import com.datamate.gateway.infrastructure.client.OmsService; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -28,7 +28,7 @@ public class OmsServiceImpl implements OmsService { private static final String AUTH_TOKEN_NEW_HEADER_KEY = "X-Auth-Token"; private static final String CSRF_TOKEN_NEW_HEADER_KEY = "X-Csrf-Token"; - private static final String REAL_IP_HEADER_KEY = "X-Real-Ip"; + private static final String REAL_IP_HEADER_KEY = "X-Real-IP"; @Value("${oms.service.url}") private final String omsServiceUrl; @@ -87,8 +87,8 @@ public String getUserNameFromOms(String authToken, String csrfToken, String real * * @param the type of data */ - @Getter - public class ResultVo { + @Data + public static class ResultVo { private Integer code; private String msg; private T data; From d64c6b27ca026d1b8b9bd455133298cdb6143be7 Mon Sep 17 00:00:00 2001 From: MoeexT Date: Thu, 19 Mar 2026 15:58:37 +0800 Subject: [PATCH 14/18] feat: implement querying group-id --- .../client/dto/ResourceGroup.java | 31 +++++++++ .../infrastructure/client/dto/Resp.java | 51 ++++++++++++++ .../client/impl/OmsExtensionServiceImpl.java | 67 +++++++++++++++++-- 3 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/dto/ResourceGroup.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/dto/Resp.java diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/dto/ResourceGroup.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/dto/ResourceGroup.java new file mode 100644 index 000000000..4f53bf85a --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/dto/ResourceGroup.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + */ + +package com.datamate.gateway.infrastructure.client.dto; + +import lombok.Getter; + +import java.time.LocalDateTime; + +/** + * 资源组信息 + */ +@Getter +public class ResourceGroup { + private String id; + + private String parentId; + + private String name; + + private String description; + + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + private boolean isDeleted; + + private boolean isBuiltin; +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/dto/Resp.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/dto/Resp.java new file mode 100644 index 000000000..e653d1eb7 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/dto/Resp.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + */ + +package com.datamate.gateway.infrastructure.client.dto; + +import lombok.Getter; + +/** + * oms-extension统一返回包装类 + * + * @param code 状态码 + * @param msg 消息 + * @param data 数据 + * @param 数据类 + */ +public record Resp(String code, String msg, T data) { + public static final String SUCCESS = "0"; + + /** + * 成功 + * @param data 数据 + * @return 响应体 + * @param 数据类型 + */ + public static Resp ok(T data) { + return new Resp<>(SUCCESS, "success", data); + } + + /** + * 成功 + * + * @return 响应体 + * @param 数据类型 + */ + public static Resp ok() { + return Resp.ok(null); + } + + /** + * 失败返回 + * + * @param code 状态码 + * @param message 错误信息 + * @return 响应体 + * @param 数据类型 + */ + public static Resp error(String code, String message) { + return new Resp<>(code, message, null); + } +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java index 270ad4926..888b333f7 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java @@ -1,18 +1,77 @@ package com.datamate.gateway.infrastructure.client.impl; +import com.datamate.gateway.common.config.SslIgnoreHttpClientFactory; import com.datamate.gateway.infrastructure.client.OmsExtensionService; +import com.datamate.gateway.infrastructure.client.dto.ResourceGroup; +import com.datamate.gateway.infrastructure.client.dto.Resp; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.extern.slf4j.Slf4j; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.io.IOException; +import java.util.List; + /** * OmsExtensionServiceImpl is an implementation of OmsExtensionService. - * - * @author songyongtan - * @date 2026-03-17 + * + * @author MoeexT + * @since 2026-03-17 */ +@Slf4j @Service public class OmsExtensionServiceImpl implements OmsExtensionService { + @Value("${OMS_EXTENSION_URL:https://oms-extension:8021}") + private final String omsExtensionUrl; + + private final ObjectMapper objectMapper; + + private CloseableHttpClient httpClient; + + public OmsExtensionServiceImpl(@Value("${oms.service.url}") String omsExtensionUrl, ObjectMapper objectMapper, + SslIgnoreHttpClientFactory sslIgnoreHttpClientFactory) { + this.omsExtensionUrl = omsExtensionUrl; + this.objectMapper = objectMapper; + try { + this.httpClient = sslIgnoreHttpClientFactory.getHttpClient(); + } catch (Exception e) { + log.error("Failed to create SSL ignore HTTP client", e); + } + } + + /** + * Get the group ID of the user's user group + * + * @param userName the username + * @return resource-group-id of this user + */ @Override public String getUserGroupId(String userName) { - return userName; + try { + String fullPath = this.omsExtensionUrl + "/ui/v1/resource-groups/user/" + userName + "/resource-group"; + HttpGet httpGet = new HttpGet(fullPath); + CloseableHttpResponse response = httpClient.execute(httpGet); + String responseBody = EntityUtils.toString(response.getEntity()); + log.info("response code: {}, response body: {}", response.getCode(), responseBody); + + Resp> resp = objectMapper.readValue(responseBody, + objectMapper.getTypeFactory().constructParametricType(Resp.class, List.class, ResourceGroup.class)); + + if (resp.data() == null || resp.data().isEmpty()) { + return null; + } + + return resp.data().getFirst().getId(); + } catch (IOException | ParseException e) { + log.error("Failed to get user name from OMS service", e); + return null; + } } } From f16987b35b2c4753c385fc8c895b7493511ba71e Mon Sep 17 00:00:00 2001 From: MoeexT Date: Tue, 24 Mar 2026 17:26:00 +0800 Subject: [PATCH 15/18] fix: deserialize resource-group --- .../client/impl/OmsExtensionServiceImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java index 888b333f7..fc25f218d 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java @@ -4,6 +4,7 @@ import com.datamate.gateway.infrastructure.client.OmsExtensionService; import com.datamate.gateway.infrastructure.client.dto.ResourceGroup; import com.datamate.gateway.infrastructure.client.dto.Resp; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; @@ -61,8 +62,12 @@ public String getUserGroupId(String userName) { String responseBody = EntityUtils.toString(response.getEntity()); log.info("response code: {}, response body: {}", response.getCode(), responseBody); + JavaType listType = objectMapper.getTypeFactory() + .constructParametricType(List.class, ResourceGroup.class); + JavaType respType = objectMapper.getTypeFactory() + .constructParametricType(Resp.class, listType); Resp> resp = objectMapper.readValue(responseBody, - objectMapper.getTypeFactory().constructParametricType(Resp.class, List.class, ResourceGroup.class)); + objectMapper.getTypeFactory().constructParametricType(Resp.class, respType)); if (resp.data() == null || resp.data().isEmpty()) { return null; From 432b07ce1a078db55101135230090e2e4cbd2102 Mon Sep 17 00:00:00 2001 From: MoeexT Date: Wed, 25 Mar 2026 15:00:43 +0800 Subject: [PATCH 16/18] feat: add switch to enable real-ip forwarding --- deployment/helm/datamate/values.yaml | 2 ++ scripts/images/frontend/Dockerfile | 2 ++ scripts/images/frontend/nginx.conf | 32 ++++++++++++++++++++++++++++ scripts/images/frontend/start.sh | 14 ++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 scripts/images/frontend/nginx.conf diff --git a/deployment/helm/datamate/values.yaml b/deployment/helm/datamate/values.yaml index a95bdf341..23b391a31 100644 --- a/deployment/helm/datamate/values.yaml +++ b/deployment/helm/datamate/values.yaml @@ -183,6 +183,8 @@ frontend: secretKeyRef: name: datamate-conf key: DOMAIN + - name: REAL_IP_MODE + value: "off" volumes: - *logVolume - name: cert-volume diff --git a/scripts/images/frontend/Dockerfile b/scripts/images/frontend/Dockerfile index 3aaf639c7..a2d91e6a1 100644 --- a/scripts/images/frontend/Dockerfile +++ b/scripts/images/frontend/Dockerfile @@ -22,6 +22,7 @@ COPY --from=builder /app/dist /opt/frontend/statics COPY scripts/images/frontend/routes.inc /opt/frontend/routes.inc COPY scripts/images/frontend/http_backend.conf /opt/frontend/http_backend.conf COPY scripts/images/frontend/https_backend.conf /opt/frontend/https_backend.conf +COPY scripts/images/frontend/nginx.conf /opt/frontend/nginx.conf COPY scripts/images/frontend/start.sh /opt/frontend/start.sh @@ -29,6 +30,7 @@ RUN dos2unix /opt/frontend/start.sh \ && chmod +x /opt/frontend/start.sh \ && mkdir -p /etc/nginx/cert \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && rm -f /etc/nginx/nginx.conf \ && rm -f /etc/nginx/conf.d/default.conf EXPOSE 3000 diff --git a/scripts/images/frontend/nginx.conf b/scripts/images/frontend/nginx.conf new file mode 100644 index 000000000..d4149dbcf --- /dev/null +++ b/scripts/images/frontend/nginx.conf @@ -0,0 +1,32 @@ + +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/scripts/images/frontend/start.sh b/scripts/images/frontend/start.sh index 457e4a547..9824aefe7 100644 --- a/scripts/images/frontend/start.sh +++ b/scripts/images/frontend/start.sh @@ -1,5 +1,17 @@ #!/bin/bash +function set_proxy_protocol() { + if [ "${REAL_IP_MODE}" != "proxy_protocol" ]; then + echo "REAL_IP_MODE is ${REAL_IP_MODE}, no need to update nginx configuration file." + return 0 + fi + sed -i 's/listen 3000;/listen 3000 proxy_protocol;/' /opt/frontend/http_backend.conf + sed -i 's/listen 3000;/listen 3000 proxy_protocol;/' /opt/frontend/https_backend.conf + sed -i '/access_log.*main/a\ set_real_ip_from 0.0.0.0/0;' /opt/frontend/nginx.conf + sed -i '/access_log.*main/a\ real_ip_header proxy_protocol;' /opt/frontend/nginx.conf + echo "ginx configuration file updated." +} + if [ -f "/cert/server.pem" ]; then cp /cert/server.pem /etc/nginx/cert/server.pem chown nginx:nginx /etc/nginx/cert/server.pem @@ -18,6 +30,8 @@ if [ -f "/cert/server.key" ]; then chown nginx:nginx /etc/nginx/cert/server.key fi +set_proxy_protocol +cp /opt/frontend/nginx.conf /etc/nginx/nginx.conf if [ -f "/etc/nginx/cert/server.pem" ]; then cp /opt/frontend/https_backend.conf /etc/nginx/conf.d/default.conf cp /opt/frontend/routes.inc /etc/nginx/conf.d/routes.inc From 481e887632155ee835c87617d717cc8bb4036f52 Mon Sep 17 00:00:00 2001 From: MoeexT Date: Thu, 26 Mar 2026 15:12:42 +0800 Subject: [PATCH 17/18] feat: add switch to enable real-ip forwarding --- scripts/images/frontend/start.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/images/frontend/start.sh b/scripts/images/frontend/start.sh index 9824aefe7..6102495fd 100644 --- a/scripts/images/frontend/start.sh +++ b/scripts/images/frontend/start.sh @@ -6,10 +6,10 @@ function set_proxy_protocol() { return 0 fi sed -i 's/listen 3000;/listen 3000 proxy_protocol;/' /opt/frontend/http_backend.conf - sed -i 's/listen 3000;/listen 3000 proxy_protocol;/' /opt/frontend/https_backend.conf + sed -i 's/listen 3000 ssl;/listen 3000 ssl proxy_protocol;/' /opt/frontend/https_backend.conf sed -i '/access_log.*main/a\ set_real_ip_from 0.0.0.0/0;' /opt/frontend/nginx.conf sed -i '/access_log.*main/a\ real_ip_header proxy_protocol;' /opt/frontend/nginx.conf - echo "ginx configuration file updated." + echo "Nginx configuration file updated." } if [ -f "/cert/server.pem" ]; then From 32acc540a4489795eed0acec0fc352a5f3912a83 Mon Sep 17 00:00:00 2001 From: MoeexT Date: Thu, 26 Mar 2026 20:03:23 +0800 Subject: [PATCH 18/18] feat: use session url --- .../gateway/common/filter/OmsAuthFilter.java | 3 +- .../client/impl/OmsServiceImpl.java | 42 +++++++------------ 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java index 0bb4a4fd9..0e182dcd1 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -81,10 +81,9 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.error("Authentication failed: Token is null or invalid."); return exchange.getResponse().setComplete(); } - String userGroupId = this.omsExtensionService.getUserGroupId(userName); + log.info("Current oms username is: {}", userName); ServerHttpRequest newRequest = request.mutate() .header(USER_NAME_HEADER, userName) - .header(USER_GROUP_ID_HEADER, userGroupId) .build(); return chain.filter(exchange.mutate().request(newRequest).build()); diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java index 64970cd9c..40cbaff1e 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java @@ -1,12 +1,15 @@ package com.datamate.gateway.infrastructure.client.impl; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONException; +import com.alibaba.fastjson2.JSONObject; import com.datamate.gateway.common.config.SslIgnoreHttpClientFactory; import com.datamate.gateway.infrastructure.client.OmsService; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.apache.hc.client5.http.classic.methods.HttpPost; + +import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.ParseException; @@ -15,7 +18,6 @@ import org.springframework.stereotype.Service; import java.io.IOException; -import java.util.List; /** * OmsServiceImpl is a service that interacts with the OMS service. @@ -56,41 +58,27 @@ public OmsServiceImpl( @Override public String getUserNameFromOms(String authToken, String csrfToken, String realIp) { try { - String fullPath = this.omsServiceUrl + "/framework/v1/iam/roles/query-by-token"; - - HttpPost httpPost = new HttpPost(fullPath); - + String fullPath = this.omsServiceUrl + "/framework/v1/sessions/current"; + HttpGet httpPost = new HttpGet(fullPath); httpPost.setHeader(AUTH_TOKEN_NEW_HEADER_KEY, authToken); httpPost.setHeader(CSRF_TOKEN_NEW_HEADER_KEY, csrfToken); httpPost.setHeader(REAL_IP_HEADER_KEY, realIp); CloseableHttpResponse response = httpClient.execute(httpPost); String responseBody = EntityUtils.toString(response.getEntity()); - log.info("response code: {}, response body: {}", response.getCode(), responseBody); - - ResultVo> resultVo = objectMapper.readValue(responseBody, - objectMapper.getTypeFactory().constructParametricType(ResultVo.class, List.class)); - - if (resultVo.getData() == null || resultVo.getData().isEmpty()) { + log.info("response code: {}", response.getCode()); + + try { + JSONObject jsonObject = JSON.parseObject(responseBody); + JSONObject data = jsonObject.getJSONObject("data"); + return data.getString("userName"); + } catch (JSONException e) { + log.error("Failed to parse response body: {}", e.getMessage()); return null; } - - return resultVo.getData().get(0); } catch (IOException | ParseException e) { log.error("Failed to get user name from OMS service", e); return null; } } - - /** - * ResultVo is a generic result wrapper. - * - * @param the type of data - */ - @Data - public static class ResultVo { - private Integer code; - private String msg; - private T data; - } }