Skip to content

Commit 5ea214d

Browse files
MoeexTloveTsong
andauthored
feat: add oms authentication (#452)
* feat(api-gateway): implement OMS authentication filter with comprehensive tests * refactor(api-gateway): optimize OMS auth filter to apply configuration switch in apply method * refactor(api-gateway): change OMS auth token retrieval from header to cookies * refactor(api-gateway): support config oms.gateway.url with env * fix(api-gateway): config omsservice url * fix(api-gateway): return UNAUTHORIZED when get user fail * feat(api-gateway): ignore ssl verify * 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 * 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 * refactor(api-gateway): extract OMS authentication logic to service layer with DDD architecture * feat(api-gateway): add OmsExtensionService for user group ID retrieval with comprehensive documentation * fix(api-gateway): fix static inner class ResultVo compilation error by using @Getter instead of @DaTa * refactor(api-gateway): adjust IP retrieval priority order and revert ResultVo annotation * feat: implement querying group-id * fix: deserialize resource-group * feat: add switch to enable real-ip forwarding * feat: add switch to enable real-ip forwarding * feat: use session url --------- Co-authored-by: loveTsong <271667068@qq.com>
1 parent 28154e9 commit 5ea214d

15 files changed

Lines changed: 684 additions & 1 deletion

File tree

backend/api-gateway/pom.xml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@
6868
<artifactId>postgresql</artifactId>
6969
</dependency>
7070

71+
<!-- Apache HttpClient -->
72+
<dependency>
73+
<groupId>org.apache.httpcomponents.client5</groupId>
74+
<artifactId>httpclient5</artifactId>
75+
</dependency>
76+
7177
<!-- 使用新版本的 JJWT -->
7278
<dependency>
7379
<groupId>io.jsonwebtoken</groupId>
@@ -82,10 +88,27 @@
8288
</dependency>
8389
<dependency>
8490
<groupId>io.jsonwebtoken</groupId>
85-
<artifactId>jjwt-jackson</artifactId> <!-- 或 jjwt-gson -->
91+
<artifactId>jjwt-jackson</artifactId>
8692
<version>${jjwt.version}</version>
8793
<scope>runtime</scope>
8894
</dependency>
95+
96+
<!-- Test dependencies -->
97+
<dependency>
98+
<groupId>org.springframework.boot</groupId>
99+
<artifactId>spring-boot-starter-test</artifactId>
100+
<scope>test</scope>
101+
</dependency>
102+
<dependency>
103+
<groupId>org.mockito</groupId>
104+
<artifactId>mockito-core</artifactId>
105+
<scope>test</scope>
106+
</dependency>
107+
<dependency>
108+
<groupId>org.mockito</groupId>
109+
<artifactId>mockito-junit-jupiter</artifactId>
110+
<scope>test</scope>
111+
</dependency>
89112
</dependencies>
90113

91114
<build>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.datamate.gateway.common.config;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
5+
import org.apache.hc.client5.http.impl.classic.HttpClients;
6+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
7+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
8+
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
9+
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
10+
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
11+
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
12+
import org.apache.hc.core5.ssl.SSLContextBuilder;
13+
import org.springframework.stereotype.Component;
14+
15+
import java.security.KeyManagementException;
16+
import java.security.KeyStoreException;
17+
import java.security.NoSuchAlgorithmException;
18+
19+
/**
20+
* SslIgnoreHttpClientFactory is a factory that creates a CloseableHttpClient that ignores SSL errors.
21+
*
22+
* @author songyongtan
23+
* @date 2026-03-16
24+
*/
25+
@Slf4j
26+
@Component
27+
public class SslIgnoreHttpClientFactory {
28+
public CloseableHttpClient getHttpClient()
29+
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
30+
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
31+
.setSslContext(SSLContextBuilder.create()
32+
.setProtocol("TLSv1.2")
33+
.loadTrustMaterial(TrustAllStrategy.INSTANCE)
34+
.build())
35+
.setHostnameVerifier(NoopHostnameVerifier.INSTANCE)
36+
.build();
37+
PoolingHttpClientConnectionManager connManager =
38+
PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(sslSocketFactory).build();
39+
return HttpClients.custom().setConnectionManager(connManager).build();
40+
}
41+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.datamate.gateway.common.filter;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
6+
import org.springframework.cloud.gateway.filter.GlobalFilter;
7+
import org.springframework.http.HttpCookie;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.http.server.reactive.ServerHttpRequest;
10+
import org.springframework.stereotype.Component;
11+
import org.springframework.util.MultiValueMap;
12+
import org.springframework.web.server.ServerWebExchange;
13+
14+
import com.datamate.gateway.infrastructure.client.OmsExtensionService;
15+
import com.datamate.gateway.infrastructure.client.OmsService;
16+
17+
import reactor.core.publisher.Mono;
18+
19+
import java.util.Objects;
20+
21+
/**
22+
* OmsAuthFilter is a global filter that authenticates requests to the OMS service.
23+
*
24+
* @author songyongtan
25+
* @date 2026-03-16
26+
*/
27+
@Slf4j
28+
@Component
29+
public class OmsAuthFilter implements GlobalFilter {
30+
private static final String USER_NAME_HEADER = "X-User-Name";
31+
private static final String USER_GROUP_ID_HEADER = "X-User-Group-Id";
32+
private static final String AUTH_TOKEN_KEY = "__Host-X-Auth-Token";
33+
private static final String CSRF_TOKEN_KEY = "__Host-X-Csrf-Token";
34+
35+
private final Boolean omsAuthEnable;
36+
private final OmsService omsService;
37+
private final OmsExtensionService omsExtensionService;
38+
39+
/**
40+
* OmsAuthFilter constructor.
41+
*
42+
* @param omsAuthEnable whether OMS authentication is enabled
43+
* @param omsService OMS service client
44+
* @param omsExtensionService OMS extension service client
45+
*/
46+
public OmsAuthFilter(
47+
@Value("${oms.auth.enabled:false}") Boolean omsAuthEnable,
48+
OmsService omsService,
49+
OmsExtensionService omsExtensionService) {
50+
log.info("OmsAuthFilter is apply, omsAuthEnable: {}", omsAuthEnable);
51+
this.omsAuthEnable = omsAuthEnable;
52+
this.omsService = omsService;
53+
this.omsExtensionService = omsExtensionService;
54+
}
55+
56+
/**
57+
* filter processes the request and adds authentication headers.
58+
*
59+
* @param exchange the server web exchange
60+
* @param chain the gateway filter chain
61+
* @return Mono<Void> completion signal
62+
*/
63+
@Override
64+
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
65+
if (!this.omsAuthEnable) {
66+
return chain.filter(exchange);
67+
}
68+
ServerHttpRequest request = exchange.getRequest();
69+
String uri = request.getURI().getPath();
70+
log.info("Oms auth filter uri: {}", uri);
71+
72+
try {
73+
MultiValueMap<String, HttpCookie> cookies = request.getCookies();
74+
String authToken = getToken(cookies, AUTH_TOKEN_KEY);
75+
String csrfToken = getToken(cookies, CSRF_TOKEN_KEY);
76+
String realIp = getRealIp(request);
77+
78+
String userName = this.omsService.getUserNameFromOms(authToken, csrfToken, realIp);
79+
if (userName == null) {
80+
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
81+
log.error("Authentication failed: Token is null or invalid.");
82+
return exchange.getResponse().setComplete();
83+
}
84+
log.info("Current oms username is: {}", userName);
85+
ServerHttpRequest newRequest = request.mutate()
86+
.header(USER_NAME_HEADER, userName)
87+
.build();
88+
89+
return chain.filter(exchange.mutate().request(newRequest).build());
90+
} catch (Exception e) {
91+
log.error("Exception occurred during POST request: {}", e.getMessage(), e);
92+
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
93+
return exchange.getResponse().setComplete();
94+
}
95+
}
96+
97+
/**
98+
* getRealIp gets the real IP address from the request.
99+
*
100+
* @param request the HTTP request
101+
* @return the real IP address
102+
*/
103+
private String getRealIp(ServerHttpRequest request) {
104+
String ip = request.getHeaders().getFirst("X-Real-IP");
105+
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
106+
ip = request.getHeaders().getFirst("X-Forwarded-For");
107+
}
108+
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
109+
ip = request.getHeaders().getFirst("Proxy-Client-IP");
110+
}
111+
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
112+
ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");
113+
}
114+
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
115+
ip = request.getRemoteAddress() != null ? request.getRemoteAddress().getAddress().getHostAddress() : "";
116+
}
117+
if (ip != null && ip.contains(",")) {
118+
ip = ip.split(",")[0].trim();
119+
}
120+
return ip != null ? ip : "";
121+
}
122+
123+
/**
124+
* getToken gets the token value from cookies.
125+
*
126+
* @param cookies the cookies map
127+
* @param tokenKey the token key
128+
* @return the token value
129+
*/
130+
private String getToken(MultiValueMap<String, HttpCookie> cookies, String tokenKey) {
131+
if (cookies.containsKey(tokenKey)) {
132+
return Objects.requireNonNull(cookies.getFirst(tokenKey)).getValue();
133+
}
134+
return "";
135+
}
136+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.datamate.gateway.infrastructure.client;
2+
3+
/**
4+
* OmsExtensionService is a service interface for OMS extension operations.
5+
*
6+
* @author songyongtan
7+
* @date 2026-03-17
8+
*/
9+
public interface OmsExtensionService {
10+
/**
11+
* getUserGroupId gets the user group ID by user name.
12+
*
13+
* @param userName the user name
14+
* @return the user group ID
15+
*/
16+
String getUserGroupId(String userName);
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.datamate.gateway.infrastructure.client;
2+
3+
import java.io.IOException;
4+
5+
/**
6+
* OmsService is a service that interacts with the OMS service.
7+
*
8+
* @author songyongtan
9+
* @date 2026-03-16
10+
*/
11+
public interface OmsService {
12+
/**
13+
* getUserNameFromOms gets the user name from the OMS service.
14+
*
15+
* @param authToken the auth token
16+
* @param csrfToken the csrf token
17+
* @param realIp the real ip
18+
* @return the user name
19+
* @throws IOException if an error occurs
20+
*/
21+
String getUserNameFromOms(String authToken, String csrfToken, String realIp);
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
3+
*/
4+
5+
package com.datamate.gateway.infrastructure.client.dto;
6+
7+
import lombok.Getter;
8+
9+
import java.time.LocalDateTime;
10+
11+
/**
12+
* 资源组信息
13+
*/
14+
@Getter
15+
public class ResourceGroup {
16+
private String id;
17+
18+
private String parentId;
19+
20+
private String name;
21+
22+
private String description;
23+
24+
private LocalDateTime createTime;
25+
26+
private LocalDateTime updateTime;
27+
28+
private boolean isDeleted;
29+
30+
private boolean isBuiltin;
31+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
3+
*/
4+
5+
package com.datamate.gateway.infrastructure.client.dto;
6+
7+
import lombok.Getter;
8+
9+
/**
10+
* oms-extension统一返回包装类
11+
*
12+
* @param code 状态码
13+
* @param msg 消息
14+
* @param data 数据
15+
* @param <T> 数据类
16+
*/
17+
public record Resp<T>(String code, String msg, T data) {
18+
public static final String SUCCESS = "0";
19+
20+
/**
21+
* 成功
22+
* @param data 数据
23+
* @return 响应体
24+
* @param <T> 数据类型
25+
*/
26+
public static <T> Resp<T> ok(T data) {
27+
return new Resp<>(SUCCESS, "success", data);
28+
}
29+
30+
/**
31+
* 成功
32+
*
33+
* @return 响应体
34+
* @param <T> 数据类型
35+
*/
36+
public static <T> Resp<T> ok() {
37+
return Resp.ok(null);
38+
}
39+
40+
/**
41+
* 失败返回
42+
*
43+
* @param code 状态码
44+
* @param message 错误信息
45+
* @return 响应体
46+
* @param <T> 数据类型
47+
*/
48+
public static <T> Resp<T> error(String code, String message) {
49+
return new Resp<>(code, message, null);
50+
}
51+
}

0 commit comments

Comments
 (0)