|
|
@@ -1,21 +1,26 @@
|
|
1
|
1
|
package com.bjtdba.gateWay.filter;
|
|
2
|
|
-// ApiAuthFilter.java
|
|
|
2
|
+
|
|
3
|
3
|
import org.springframework.beans.factory.annotation.Value;
|
|
4
|
4
|
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
|
5
|
5
|
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
|
|
6
|
+import org.springframework.core.io.buffer.DataBuffer;
|
|
6
|
7
|
import org.springframework.http.HttpStatus;
|
|
|
8
|
+import org.springframework.http.MediaType;
|
|
7
|
9
|
import org.springframework.stereotype.Component;
|
|
8
|
10
|
import org.springframework.util.DigestUtils;
|
|
9
|
11
|
import org.springframework.web.server.ServerWebExchange;
|
|
10
|
12
|
import reactor.core.publisher.Mono;
|
|
11
|
13
|
|
|
|
14
|
+import java.nio.charset.StandardCharsets;
|
|
|
15
|
+import java.util.HashMap;
|
|
|
16
|
+import java.util.Map;
|
|
|
17
|
+
|
|
12
|
18
|
@Component
|
|
13
|
19
|
public class ApiAuthFilter extends AbstractGatewayFilterFactory<ApiAuthFilter.Config> {
|
|
14
|
20
|
|
|
15
|
21
|
@Value("${external.api.secret}")
|
|
16
|
|
- private String apiSecret; // 从配置文件注入密钥
|
|
17
|
|
-
|
|
18
|
|
- private static final long TIMESTAMP_THRESHOLD = 300000; // 5分钟有效期
|
|
|
22
|
+ private String apiSecret;
|
|
|
23
|
+ private static final long TIMESTAMP_THRESHOLD = 300000;
|
|
19
|
24
|
|
|
20
|
25
|
public ApiAuthFilter() {
|
|
21
|
26
|
super(Config.class);
|
|
|
@@ -24,47 +29,80 @@ public class ApiAuthFilter extends AbstractGatewayFilterFactory<ApiAuthFilter.Co
|
|
24
|
29
|
@Override
|
|
25
|
30
|
public GatewayFilter apply(Config config) {
|
|
26
|
31
|
return (exchange, chain) -> {
|
|
27
|
|
- // 仅处理/external路径的请求
|
|
28
|
32
|
if (!exchange.getRequest().getPath().value().startsWith("/external")) {
|
|
29
|
33
|
return chain.filter(exchange);
|
|
30
|
34
|
}
|
|
31
|
35
|
|
|
32
|
|
- // 1. 获取认证头信息
|
|
33
|
36
|
String apiKey = exchange.getRequest().getHeaders().getFirst("X-API-KEY");
|
|
34
|
37
|
String timestamp = exchange.getRequest().getHeaders().getFirst("X-TIMESTAMP");
|
|
35
|
38
|
String signature = exchange.getRequest().getHeaders().getFirst("X-SIGNATURE");
|
|
36
|
39
|
|
|
37
|
|
- // 2. 验证必要头信息存在
|
|
38
|
40
|
if (apiKey == null || timestamp == null || signature == null) {
|
|
39
|
|
- return unauthorized(exchange, "Missing authentication headers");
|
|
|
41
|
+ return createErrorResponse(exchange, 401, "Missing authentication headers", HttpStatus.UNAUTHORIZED);
|
|
40
|
42
|
}
|
|
41
|
43
|
|
|
42
|
|
- // 3. 验证时间戳有效性
|
|
43
|
44
|
try {
|
|
44
|
45
|
long requestTime = Long.parseLong(timestamp);
|
|
45
|
46
|
long currentTime = System.currentTimeMillis();
|
|
46
|
47
|
|
|
47
|
48
|
if (Math.abs(currentTime - requestTime) > TIMESTAMP_THRESHOLD) {
|
|
48
|
|
- return unauthorized(exchange, "Timestamp expired");
|
|
|
49
|
+ return createErrorResponse(exchange, 401, "Timestamp expired", HttpStatus.UNAUTHORIZED);
|
|
49
|
50
|
}
|
|
50
|
51
|
} catch (NumberFormatException e) {
|
|
51
|
|
- return unauthorized(exchange, "Invalid timestamp format");
|
|
|
52
|
+ return createErrorResponse(exchange, 400, "Invalid timestamp format", HttpStatus.BAD_REQUEST);
|
|
52
|
53
|
}
|
|
53
|
54
|
|
|
54
|
|
- // 4. 生成并验证签名
|
|
55
|
55
|
String expectedSignature = generateSignature(apiKey, timestamp);
|
|
56
|
56
|
if (!expectedSignature.equalsIgnoreCase(signature)) {
|
|
57
|
|
- return unauthorized(exchange, "Invalid signature");
|
|
|
57
|
+ return createErrorResponse(exchange, 401, "Invalid signature", HttpStatus.UNAUTHORIZED);
|
|
58
|
58
|
}
|
|
59
|
|
- System.out.println("Forwarding to: " + exchange.getRequest().getURI());
|
|
|
59
|
+
|
|
60
|
60
|
return chain.filter(exchange);
|
|
61
|
61
|
};
|
|
62
|
62
|
}
|
|
63
|
63
|
|
|
64
|
|
- private Mono<Void> unauthorized(ServerWebExchange exchange, String reason) {
|
|
65
|
|
- exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
|
66
|
|
- exchange.getResponse().getHeaders().add("X-Auth-Error", reason);
|
|
67
|
|
- return exchange.getResponse().setComplete();
|
|
|
64
|
+ /**
|
|
|
65
|
+ * 创建标准错误响应
|
|
|
66
|
+ *
|
|
|
67
|
+ * @param exchange 请求上下文
|
|
|
68
|
+ * @param code 业务错误码
|
|
|
69
|
+ * @param message 错误消息
|
|
|
70
|
+ * @param httpStatus HTTP状态码
|
|
|
71
|
+ * @return Mono<Void>
|
|
|
72
|
+ */
|
|
|
73
|
+ private Mono<Void> createErrorResponse(ServerWebExchange exchange, int code,
|
|
|
74
|
+ String message, HttpStatus httpStatus) {
|
|
|
75
|
+ exchange.getResponse().setStatusCode(httpStatus);
|
|
|
76
|
+ exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
|
|
77
|
+
|
|
|
78
|
+ // 构建标准响应格式
|
|
|
79
|
+ Map<String, Object> responseMap = new HashMap<>();
|
|
|
80
|
+ responseMap.put("code", code);
|
|
|
81
|
+ responseMap.put("message", message);
|
|
|
82
|
+ responseMap.put("data", null);
|
|
|
83
|
+
|
|
|
84
|
+ // 手动序列化JSON
|
|
|
85
|
+ String jsonResponse = "{\"code\":" + code +
|
|
|
86
|
+ ",\"message\":\"" + escapeJson(message) +
|
|
|
87
|
+ "\",\"data\":null}";
|
|
|
88
|
+
|
|
|
89
|
+ byte[] bytes = jsonResponse.getBytes(StandardCharsets.UTF_8);
|
|
|
90
|
+ DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
|
|
|
91
|
+
|
|
|
92
|
+ return exchange.getResponse().writeWith(Mono.just(buffer));
|
|
|
93
|
+ }
|
|
|
94
|
+
|
|
|
95
|
+ /**
|
|
|
96
|
+ * 转义JSON字符串中的特殊字符
|
|
|
97
|
+ */
|
|
|
98
|
+ private String escapeJson(String input) {
|
|
|
99
|
+ return input.replace("\"", "\\\"")
|
|
|
100
|
+ .replace("\\", "\\\\")
|
|
|
101
|
+ .replace("\b", "\\b")
|
|
|
102
|
+ .replace("\f", "\\f")
|
|
|
103
|
+ .replace("\n", "\\n")
|
|
|
104
|
+ .replace("\r", "\\r")
|
|
|
105
|
+ .replace("\t", "\\t");
|
|
68
|
106
|
}
|
|
69
|
107
|
|
|
70
|
108
|
private String generateSignature(String apiKey, String timestamp) {
|