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/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 new file mode 100644 index 000000000..0e182dcd1 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/OmsAuthFilter.java @@ -0,0 +1,136 @@ +package com.datamate.gateway.common.filter; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +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 com.datamate.gateway.infrastructure.client.OmsExtensionService; +import com.datamate.gateway.infrastructure.client.OmsService; + +import reactor.core.publisher.Mono; + +import java.util.Objects; + +/** + * OmsAuthFilter is a global filter that authenticates requests to the OMS service. + * + * @author songyongtan + * @date 2026-03-16 + */ +@Slf4j +@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, + 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) { + return chain.filter(exchange); + } + ServerHttpRequest request = exchange.getRequest(); + String uri = request.getURI().getPath(); + log.info("Oms auth filter uri: {}", uri); + + try { + 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."); + return exchange.getResponse().setComplete(); + } + log.info("Current oms username is: {}", userName); + ServerHttpRequest newRequest = request.mutate() + .header(USER_NAME_HEADER, userName) + .build(); + + return chain.filter(exchange.mutate().request(newRequest).build()); + } catch (Exception e) { + log.error("Exception occurred during POST request: {}", e.getMessage(), e); + exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); + return exchange.getResponse().setComplete(); + } + } + + /** + * 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-Real-IP"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeaders().getFirst("X-Forwarded-For"); + } + 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 : ""; + } + + /** + * 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(); + } + return ""; + } +} 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/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/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 new file mode 100644 index 000000000..fc25f218d --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsExtensionServiceImpl.java @@ -0,0 +1,82 @@ +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.JavaType; +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 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) { + 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); + + 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, respType)); + + 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; + } + } +} 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..40cbaff1e --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/client/impl/OmsServiceImpl.java @@ -0,0 +1,84 @@ +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.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; + +/** + * 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/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.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; + } + } catch (IOException | ParseException e) { + log.error("Failed to get user name from OMS service", e); + return null; + } + } +} diff --git a/backend/api-gateway/src/main/resources/application.yml b/backend/api-gateway/src/main/resources/application.yml index a6603b6c9..99167734b 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: ${OMS_AUTH_ENABLED:false} + service: + url: ${OMS_SERVICE_URL:https://omsservice:18082} # 服务器端口配置 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..0841724ca --- /dev/null +++ b/backend/api-gateway/src/test/java/com/datamate/gateway/common/filter/OmsAuthFilterTest.java @@ -0,0 +1,141 @@ +package com.datamate.gateway.common.filter; + +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.GatewayFilterChain; +import org.springframework.http.HttpCookie; +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 com.datamate.gateway.infrastructure.client.OmsExtensionService; +import com.datamate.gateway.infrastructure.client.OmsService; + +import reactor.core.publisher.Mono; + +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 { + + @Mock + private GatewayFilterChain chain; + + @Mock + private OmsService omsService; + + @Mock + private OmsExtensionService omsExtensionService; + + private OmsAuthFilter omsAuthFilter; + + @BeforeEach + void setUp() throws Exception { + } + + private OmsAuthFilter createOmsAuthFilter(Boolean omsAuthEnable) { + return new OmsAuthFilter(omsAuthEnable, omsService, omsExtensionService); + } + + @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()); + + omsAuthFilter.filter(exchange, chain); + + verify(chain, times(1)).filter(any(ServerWebExchange.class)); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndTokenValid_ShouldAddUserNameHeader() throws Exception { + omsAuthFilter = createOmsAuthFilter(true); + + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); + 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()); + + omsAuthFilter.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")) && + headers.containsKey("X-User-Group-Id") && + "testuser".equals(headers.getFirst("X-User-Group-Id")); + })); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndTokenInvalid_ShouldReturn401() throws Exception { + omsAuthFilter = createOmsAuthFilter(true); + + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + when(omsService.getUserNameFromOms(anyString(), anyString(), anyString())).thenReturn(null); + + omsAuthFilter.filter(exchange, chain); + + ServerHttpResponse response = exchange.getResponse(); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + verify(chain, never()).filter(any(ServerWebExchange.class)); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndNoToken_ShouldReturn401() throws Exception { + omsAuthFilter = createOmsAuthFilter(true); + + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test").build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + when(omsService.getUserNameFromOms(anyString(), anyString(), anyString())).thenReturn(null); + + omsAuthFilter.filter(exchange, chain); + + ServerHttpResponse response = exchange.getResponse(); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + verify(chain, never()).filter(any(ServerWebExchange.class)); + } + + @Test + void testFilter_WhenOmsAuthEnabledAndTokenInCookie_ShouldUseToken() throws Exception { + omsAuthFilter = createOmsAuthFilter(true); + + HttpCookie authCookie = new HttpCookie("__Host-X-Auth-Token", "test-token"); + MockServerHttpRequest request = MockServerHttpRequest.get("/api/test") + .cookie(authCookie) + .build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + when(omsService.getUserNameFromOms(anyString(), anyString(), anyString())).thenReturn("testuser"); + + when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); + + omsAuthFilter.filter(exchange, chain); + + verify(chain, times(1)).filter(any(ServerWebExchange.class)); + } +} 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..6102495fd 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 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 "Nginx 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