1. 修复界面bug
2. 新增 api 可配置匿名访问固定用户配置 3. 新增密码弱口令校验规则 4. e 办使用 loginName 确认唯一用户逻辑
This commit is contained in:
@@ -3,12 +3,15 @@ package com.zt.plat.module.databus.controller.admin.gateway;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayExecutionService;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
|
||||
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
@@ -25,7 +28,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
@WebMvcTest(ApiGatewayController.class)
|
||||
@AutoConfigureMockMvc(addFilters = false)
|
||||
@TestPropertySource(properties = {
|
||||
"spring.config.import=optional:",
|
||||
"spring.cloud.nacos.config.enabled=false",
|
||||
"spring.cloud.nacos.discovery.enabled=false"
|
||||
})
|
||||
@@ -40,6 +42,15 @@ class ApiGatewayControllerTest {
|
||||
@MockBean
|
||||
private ApiDefinitionService apiDefinitionService;
|
||||
|
||||
@MockBean
|
||||
private ApiGatewayProperties apiGatewayProperties;
|
||||
|
||||
@MockBean
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@MockBean
|
||||
private ApiClientCredentialService apiClientCredentialService;
|
||||
|
||||
@Test
|
||||
void invokeShouldReturnGatewayEnvelope() throws Exception {
|
||||
ApiGatewayResponse response = ApiGatewayResponse.builder()
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.sample;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 可直接运行的示例,演示如何使用 appId=test 与对应密钥调用本地 Databus API。
|
||||
*/
|
||||
public final class DatabusApiInvocationExample {
|
||||
|
||||
public static final String TIMESTAMP = Long.toString(System.currentTimeMillis());
|
||||
// private static final String APP_ID = "ztmy";
|
||||
// private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ=";
|
||||
private static final String APP_ID = "test";
|
||||
private static final String APP_SECRET = "RSYtKXrXPLMy3oeh0cOro6QCioRUgqfnKCkDkNq78sI=";
|
||||
private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
||||
// private static final String TARGET_API = "http://127.0.0.1:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||
private static final String TARGET_API = "http://127.0.0.1:48080/admin-api/databus/api/portal/test11111/233";
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final PrintStream OUT = buildConsolePrintStream();
|
||||
public static final String ZT_APP_ID = "ZT-App-Id";
|
||||
public static final String ZT_TIMESTAMP = "ZT-Timestamp";
|
||||
public static final String ZT_NONCE = "ZT-Nonce";
|
||||
public static final String ZT_SIGNATURE = "ZT-Signature";
|
||||
public static final String ZT_AUTH_TOKEN = "ZT-Auth-Token";
|
||||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
|
||||
private DatabusApiInvocationExample() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
OUT.println("=== GET 请求示例 ===");
|
||||
executeGetExample();
|
||||
// OUT.println();
|
||||
// OUT.println("=== POST 请求示例 ===");
|
||||
// executePostExample();
|
||||
}
|
||||
|
||||
private static void executeGetExample() throws Exception {
|
||||
Map<String, Object> queryParams = new LinkedHashMap<>();
|
||||
queryParams.put("businessCode", "waybillUnLoadingImage");
|
||||
queryParams.put("fileId", "1979463299195412481");
|
||||
String signature = generateSignature(queryParams, Map.of());
|
||||
URI requestUri = buildUri(TARGET_API, queryParams);
|
||||
String nonce = randomNonce();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(requestUri)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.header(ZT_APP_ID, APP_ID)
|
||||
.header(ZT_TIMESTAMP, TIMESTAMP)
|
||||
.header(ZT_NONCE, nonce)
|
||||
.header(ZT_SIGNATURE, signature)
|
||||
// .header("ZT-Auth-Token", "a75c0ea94c7f4a88b86b60bbc0b432c3")
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
printResponse(response);
|
||||
}
|
||||
|
||||
private static void executePostExample() throws Exception {
|
||||
Map<String, Object> queryParams = new LinkedHashMap<>();
|
||||
|
||||
LinkedHashMap<String, Object> bodyParams = new LinkedHashMap<>();
|
||||
bodyParams.put("businessCode", "waybillUnLoadingImage");
|
||||
bodyParams.put("fileId", "1979463299195412481");
|
||||
|
||||
LinkedHashMap<String, Object> extra = new LinkedHashMap<>();
|
||||
extra.put("remark", "demo invocation");
|
||||
extra.put("timestamp", System.currentTimeMillis());
|
||||
bodyParams.put("extra", extra);
|
||||
|
||||
String signature = generateSignature(queryParams, bodyParams);
|
||||
URI requestUri = buildUri(TARGET_API, queryParams);
|
||||
String nonce = randomNonce();
|
||||
String bodyJson = OBJECT_MAPPER.writeValueAsString(bodyParams);
|
||||
String cipherBody = encryptPayload(bodyJson);
|
||||
OUT.println("原始 Request Body: " + bodyJson);
|
||||
OUT.println("加密 Request Body: " + cipherBody);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(requestUri)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.header(ZT_APP_ID, APP_ID)
|
||||
.header(ZT_TIMESTAMP, TIMESTAMP)
|
||||
.header(ZT_NONCE, nonce)
|
||||
.header(ZT_SIGNATURE, signature)
|
||||
.header(ZT_AUTH_TOKEN, "a5d7cf609c0b47038ea405c660726ee9")
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(cipherBody, StandardCharsets.UTF_8))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
printResponse(response);
|
||||
}
|
||||
|
||||
private static String encryptPayload(String plaintext) {
|
||||
try {
|
||||
return CryptoSignatureUtils.encrypt(plaintext, APP_SECRET, ENCRYPTION_TYPE);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to encrypt request body", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printResponse(HttpResponse<String> response) {
|
||||
OUT.println("HTTP Status: " + response.statusCode());
|
||||
String cipherText = response.body();
|
||||
OUT.println("加密 Response: " + cipherText);
|
||||
String plain = tryDecrypt(cipherText);
|
||||
OUT.println("原始 Response: " + normalizePotentialMojibake(plain));
|
||||
}
|
||||
|
||||
private static String randomNonce() {
|
||||
return UUID.randomUUID().toString().replace("-", "");
|
||||
}
|
||||
|
||||
private static URI buildUri(String baseUrl, Map<String, Object> queryParams) {
|
||||
if (queryParams == null || queryParams.isEmpty()) {
|
||||
return URI.create(baseUrl);
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(baseUrl);
|
||||
builder.append(baseUrl.contains("?") ? '&' : '?');
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, Object> entry : queryParams.entrySet()) {
|
||||
if (!first) {
|
||||
builder.append('&');
|
||||
}
|
||||
first = false;
|
||||
builder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
|
||||
builder.append('=');
|
||||
builder.append(URLEncoder.encode(String.valueOf(entry.getValue()), StandardCharsets.UTF_8));
|
||||
}
|
||||
return URI.create(builder.toString());
|
||||
}
|
||||
|
||||
private static String generateSignature(Map<String, Object> queryParams, Map<String, Object> bodyParams) {
|
||||
TreeMap<String, Object> sorted = new TreeMap<>();
|
||||
if (queryParams != null) {
|
||||
queryParams.forEach((key, value) -> sorted.put(key, normalizeValue(value)));
|
||||
}
|
||||
if (bodyParams != null) {
|
||||
bodyParams.forEach((key, value) -> sorted.put(key, normalizeValue(value)));
|
||||
}
|
||||
sorted.put(ZT_APP_ID, APP_ID);
|
||||
sorted.put(ZT_TIMESTAMP, TIMESTAMP);
|
||||
StringBuilder canonical = new StringBuilder();
|
||||
sorted.forEach((key, value) -> {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (canonical.length() > 0) {
|
||||
canonical.append('&');
|
||||
}
|
||||
canonical.append(key).append('=').append(value);
|
||||
});
|
||||
return md5Hex(canonical.toString());
|
||||
}
|
||||
|
||||
private static Object normalizeValue(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Map || value instanceof Iterable) {
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(value);
|
||||
} catch (JsonProcessingException ignored) {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String md5Hex(String input) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
byte[] bytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder hex = new StringBuilder(bytes.length * 2);
|
||||
for (byte b : bytes) {
|
||||
String segment = Integer.toHexString(b & 0xFF);
|
||||
if (segment.length() == 1) {
|
||||
hex.append('0');
|
||||
}
|
||||
hex.append(segment);
|
||||
}
|
||||
return hex.toString();
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new IllegalStateException("MD5 algorithm not available", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static String tryDecrypt(String cipherText) {
|
||||
if (cipherText == null || cipherText.isBlank()) {
|
||||
return cipherText;
|
||||
}
|
||||
try {
|
||||
// Databus 会在凭证开启加密时返回密文,这里做一次解密展示真实响应。
|
||||
return CryptoSignatureUtils.decrypt(cipherText, APP_SECRET, ENCRYPTION_TYPE);
|
||||
} catch (Exception ex) {
|
||||
return "<unable to decrypt> " + ex.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// 解决控制台打印 乱码问题
|
||||
private static String normalizePotentialMojibake(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return value;
|
||||
}
|
||||
long suspectCount = value.chars().filter(ch -> ch >= 0x80 && ch <= 0xFF).count();
|
||||
long highCount = value.chars().filter(ch -> ch > 0xFF).count();
|
||||
if (suspectCount > 0 && highCount == 0) {
|
||||
try {
|
||||
byte[] decoded = value.getBytes(StandardCharsets.ISO_8859_1);
|
||||
String converted = new String(decoded, StandardCharsets.UTF_8);
|
||||
if (converted.chars().anyMatch(ch -> ch > 0xFF)) {
|
||||
return converted;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出流编码与当前控制台保持一致,避免中文字符再次出现编码差异。
|
||||
*/
|
||||
private static PrintStream buildConsolePrintStream() {
|
||||
try {
|
||||
String consoleEncoding = System.getProperty("sun.stdout.encoding");
|
||||
if (consoleEncoding != null && !consoleEncoding.isBlank()) {
|
||||
return new PrintStream(System.out, true, Charset.forName(consoleEncoding));
|
||||
}
|
||||
return new PrintStream(System.out, true, Charset.defaultCharset());
|
||||
} catch (Exception ignored) {
|
||||
return System.out;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +1,34 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.security;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
||||
import com.zt.plat.framework.security.core.LoginUser;
|
||||
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.zt.plat.framework.web.core.util.WebFrameworkUtils;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
|
||||
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiAnonymousUserService;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService;
|
||||
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -27,13 +37,21 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
class GatewaySecurityFilterTest {
|
||||
|
||||
@AfterEach
|
||||
void clearSecurityContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowRequestWhenIpPermitted() throws Exception {
|
||||
ApiGatewayProperties properties = createProperties();
|
||||
properties.getSecurity().setEnabled(false);
|
||||
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class);
|
||||
when(anonymousUserService.issueAccessToken(any())).thenReturn(Optional.empty());
|
||||
when(anonymousUserService.issueAccessToken(any())).thenReturn(Optional.empty());
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
@@ -54,7 +72,9 @@ class GatewaySecurityFilterTest {
|
||||
properties.getSecurity().setEnabled(false);
|
||||
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
|
||||
ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class);
|
||||
when(anonymousUserService.issueAccessToken(any())).thenReturn(Optional.empty());
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("10.0.0.1");
|
||||
@@ -76,6 +96,8 @@ class GatewaySecurityFilterTest {
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), any(Duration.class))).thenReturn(Boolean.TRUE);
|
||||
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class);
|
||||
when(anonymousUserService.issueAccessToken(any())).thenReturn(Optional.empty());
|
||||
ApiClientCredentialDO credential = new ApiClientCredentialDO();
|
||||
credential.setAppId("demo-app");
|
||||
credential.setSignatureType(null);
|
||||
@@ -86,17 +108,17 @@ class GatewaySecurityFilterTest {
|
||||
properties.getSecurity().setRequireBodyEncryption(false);
|
||||
properties.getSecurity().setEncryptResponse(false);
|
||||
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
String signature = "d41d8cd98f00b204e9800998ecf8427e";
|
||||
request.addHeader(properties.getSecurity().getAppIdHeader(), "demo-app");
|
||||
request.addHeader(properties.getSecurity().getTimestampHeader(), String.valueOf(timestamp));
|
||||
request.addHeader(properties.getSecurity().getNonceHeader(), nonce);
|
||||
request.addHeader(properties.getSecurity().getSignatureHeader(), signature);
|
||||
String signature = signatureForApp("demo-app");
|
||||
request.addHeader(APP_ID_HEADER, "demo-app");
|
||||
request.addHeader(TIMESTAMP_HEADER, String.valueOf(timestamp));
|
||||
request.addHeader(NONCE_HEADER, nonce);
|
||||
request.addHeader(SIGNATURE_HEADER, signature);
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
@@ -119,22 +141,23 @@ class GatewaySecurityFilterTest {
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), any(Duration.class))).thenReturn(Boolean.TRUE);
|
||||
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class);
|
||||
ApiClientCredentialDO credential = new ApiClientCredentialDO();
|
||||
credential.setAppId("demo-app");
|
||||
credential.setEncryptionKey("demo-secret-key");
|
||||
credential.setEncryptionType(CryptoSignatureUtils.ENCRYPT_TYPE_AES);
|
||||
when(credentialService.findActiveCredential("demo-app")).thenReturn(Optional.of(credential));
|
||||
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
request.addHeader(properties.getSecurity().getAppIdHeader(), "demo-app");
|
||||
request.addHeader(properties.getSecurity().getTimestampHeader(), String.valueOf(timestamp));
|
||||
request.addHeader(properties.getSecurity().getNonceHeader(), nonce);
|
||||
request.addHeader(properties.getSecurity().getSignatureHeader(), "invalid-signature");
|
||||
request.addHeader(APP_ID_HEADER, "demo-app");
|
||||
request.addHeader(TIMESTAMP_HEADER, String.valueOf(timestamp));
|
||||
request.addHeader(NONCE_HEADER, nonce);
|
||||
request.addHeader(SIGNATURE_HEADER, "invalid-signature");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(request, response, new MockFilterChain());
|
||||
@@ -150,10 +173,65 @@ class GatewaySecurityFilterTest {
|
||||
assertThat(node.get("message").asText()).isEqualTo("签名校验失败");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAuthenticateWithAnonymousUserWhenConfigured() throws Exception {
|
||||
ApiGatewayProperties properties = createProperties();
|
||||
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||
ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), any(Duration.class))).thenReturn(Boolean.TRUE);
|
||||
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
ApiAnonymousUserService anonymousUserService = mock(ApiAnonymousUserService.class);
|
||||
ApiClientCredentialDO credential = new ApiClientCredentialDO();
|
||||
credential.setAppId("demo-app");
|
||||
credential.setSignatureType(null);
|
||||
credential.setEncryptionKey(null);
|
||||
credential.setEncryptionType(null);
|
||||
credential.setAllowAnonymous(Boolean.TRUE);
|
||||
credential.setAnonymousUserId(99L);
|
||||
when(credentialService.findActiveCredential("demo-app")).thenReturn(Optional.of(credential));
|
||||
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setId(999L);
|
||||
loginUser.setUserType(2);
|
||||
loginUser.setTenantId(123L);
|
||||
ApiAnonymousUserService.AnonymousUserDetails details = new ApiAnonymousUserService.AnonymousUserDetails(99L, "匿名", loginUser);
|
||||
when(anonymousUserService.find(99L)).thenReturn(Optional.of(details));
|
||||
when(anonymousUserService.issueAccessToken(details)).thenReturn(Optional.of("mock-token"));
|
||||
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, anonymousUserService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
request.addHeader(APP_ID_HEADER, "demo-app");
|
||||
request.addHeader(TIMESTAMP_HEADER, String.valueOf(timestamp));
|
||||
request.addHeader(NONCE_HEADER, nonce);
|
||||
request.addHeader(SIGNATURE_HEADER, signatureForApp("demo-app"));
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(SecurityFrameworkUtils.getLoginUser()).isNotNull();
|
||||
assertThat(SecurityFrameworkUtils.getLoginUser().getId()).isEqualTo(999L);
|
||||
assertThat(((HttpServletRequest) chain.getRequest()).getHeader(WebFrameworkUtils.HEADER_TENANT_ID)).isEqualTo("123");
|
||||
assertThat(((HttpServletRequest) chain.getRequest()).getHeader(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN)).isEqualTo("mock-token");
|
||||
assertThat(((HttpServletRequest) chain.getRequest()).getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer mock-token");
|
||||
}
|
||||
|
||||
private ApiGatewayProperties createProperties() {
|
||||
ApiGatewayProperties properties = new ApiGatewayProperties();
|
||||
properties.setBasePath("/admin-api/databus/api/portal");
|
||||
properties.setAllowedIps(Collections.singletonList("127.0.0.1"));
|
||||
return properties;
|
||||
}
|
||||
|
||||
private String signatureForApp(String appId) {
|
||||
return SecureUtil.md5("appId=" + appId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user