fix:long转string
This commit is contained in:
parent
855ea1247b
commit
11e6049ae2
@ -0,0 +1,18 @@
|
||||
package com.cisd.tms.common.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class JacksonLongSerializationConfig {
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer longToStringJacksonCustomizer() {
|
||||
return builder -> {
|
||||
builder.serializerByType(Long.class, ToStringSerializer.instance);
|
||||
builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -74,6 +74,11 @@ public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(ReplayProtectionException.class)
|
||||
public ResponseEntity<ApiResponse<Void>> handleReplayProtectionException(ReplayProtectionException ex, HttpServletRequest request) {
|
||||
if (ex.isClientError()) {
|
||||
return response(HttpStatus.BAD_REQUEST,
|
||||
ApiResponse.fail(ErrorCode.BAD_REQUEST.getCode(), ex.getMessage())
|
||||
.withPath(request.getRequestURI()));
|
||||
}
|
||||
log.warn("Replay protection 执行失败", ex);
|
||||
return response(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
ApiResponse.fail(ErrorCode.SERVICE_UNAVAILABLE.getCode(), "replay protection unavailable")
|
||||
|
||||
@ -2,11 +2,31 @@ package com.cisd.tms.modules.security.replay.exception;
|
||||
|
||||
public class ReplayProtectionException extends RuntimeException {
|
||||
|
||||
private final boolean clientError;
|
||||
|
||||
public ReplayProtectionException(String message) {
|
||||
super(message);
|
||||
this(message, false);
|
||||
}
|
||||
|
||||
public ReplayProtectionException(String message, Throwable cause) {
|
||||
this(message, cause, false);
|
||||
}
|
||||
|
||||
private ReplayProtectionException(String message, boolean clientError) {
|
||||
super(message);
|
||||
this.clientError = clientError;
|
||||
}
|
||||
|
||||
private ReplayProtectionException(String message, Throwable cause, boolean clientError) {
|
||||
super(message, cause);
|
||||
this.clientError = clientError;
|
||||
}
|
||||
|
||||
public static ReplayProtectionException badRequest(String message) {
|
||||
return new ReplayProtectionException(message, true);
|
||||
}
|
||||
|
||||
public boolean isClientError() {
|
||||
return clientError;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,25 +103,25 @@ public class ReplayProtectionServiceImpl implements ReplayProtectionService {
|
||||
|
||||
private void validateRequest(ReplayCheckRequest request) {
|
||||
if (request == null) {
|
||||
throw new ReplayProtectionException("replay 请求不能为空");
|
||||
throw ReplayProtectionException.badRequest("replay 请求不能为空");
|
||||
}
|
||||
if (request.getScope() == null) {
|
||||
throw new ReplayProtectionException("replay scope不能为空");
|
||||
throw ReplayProtectionException.badRequest("replay scope不能为空");
|
||||
}
|
||||
if (isBlank(request.getPrincipalId())) {
|
||||
throw new ReplayProtectionException("principal id不能为空");
|
||||
throw ReplayProtectionException.badRequest("principal id不能为空");
|
||||
}
|
||||
if (isBlank(request.getNonce())) {
|
||||
throw new ReplayProtectionException("nonce不能为空");
|
||||
throw ReplayProtectionException.badRequest("nonce不能为空");
|
||||
}
|
||||
if (request.getRequestTimestamp() <= 0L) {
|
||||
throw new ReplayProtectionException("request timestamp不能为空");
|
||||
throw ReplayProtectionException.badRequest("request timestamp不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureTimestampWithinWindow(long requestTimestamp, long nowSeconds, long allowedSkewSeconds) {
|
||||
if (Math.abs(nowSeconds - requestTimestamp) > allowedSkewSeconds) {
|
||||
throw new ReplayProtectionException("请求时间戳超出允许窗口");
|
||||
throw ReplayProtectionException.badRequest("请求时间戳超出允许窗口");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -78,8 +78,7 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor {
|
||||
return false;
|
||||
}
|
||||
} catch (ReplayProtectionException ex) {
|
||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, ErrorCode.SERVICE_UNAVAILABLE.getCode(),
|
||||
"replay protection unavailable");
|
||||
writeReplayProtectionError(response, ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -147,4 +146,13 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor {
|
||||
private void writeJson(HttpServletResponse response, int status, int code, String message) throws IOException {
|
||||
HttpResponseUtil.writeJson(response, status, ApiResponse.fail(code, message), objectMapper);
|
||||
}
|
||||
|
||||
private void writeReplayProtectionError(HttpServletResponse response, ReplayProtectionException ex) throws IOException {
|
||||
if (ex.isClientError()) {
|
||||
writeJson(response, HttpServletResponse.SC_BAD_REQUEST, ErrorCode.BAD_REQUEST.getCode(), ex.getMessage());
|
||||
return;
|
||||
}
|
||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, ErrorCode.SERVICE_UNAVAILABLE.getCode(),
|
||||
"replay protection unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,8 +97,7 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
||||
return false;
|
||||
}
|
||||
} catch (ReplayProtectionException ex) {
|
||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE,
|
||||
ErrorCode.SERVICE_UNAVAILABLE.getCode(), "replay protection unavailable");
|
||||
writeReplayProtectionError(response, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -153,4 +152,13 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
||||
objectMapper
|
||||
);
|
||||
}
|
||||
|
||||
private void writeReplayProtectionError(HttpServletResponse response, ReplayProtectionException ex) throws IOException {
|
||||
if (ex.isClientError()) {
|
||||
writeJson(response, HttpServletResponse.SC_BAD_REQUEST, ErrorCode.BAD_REQUEST.getCode(), ex.getMessage());
|
||||
return;
|
||||
}
|
||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE,
|
||||
ErrorCode.SERVICE_UNAVAILABLE.getCode(), "replay protection unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package com.cisd.tms.common.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class JacksonLongSerializationConfigTest {
|
||||
|
||||
@Test
|
||||
void shouldSerializeLongValuesAsJsonStrings() throws Exception {
|
||||
JacksonLongSerializationConfig config = new JacksonLongSerializationConfig();
|
||||
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
|
||||
config.longToStringJacksonCustomizer().customize(builder);
|
||||
ObjectMapper objectMapper = builder.build();
|
||||
|
||||
String json = objectMapper.writeValueAsString(new LongIdPayload(
|
||||
2049756829261557762L,
|
||||
2049756829261557763L
|
||||
));
|
||||
|
||||
assertEquals("{\"id\":\"2049756829261557762\",\"primitiveId\":\"2049756829261557763\"}", json);
|
||||
}
|
||||
|
||||
private record LongIdPayload(Long id, long primitiveId) {
|
||||
}
|
||||
}
|
||||
@ -180,7 +180,8 @@ class ReplayProtectionServiceTest {
|
||||
ReplayCheckRequest request = request(ReplayScope.OPENAPI, "demo-app", "nonce-old", EXPIRED_REQUEST_TIMESTAMP,
|
||||
"POST", "/openapi/v1/demo", "abc123");
|
||||
|
||||
Assertions.assertThrows(ReplayProtectionException.class, () -> service.check(request));
|
||||
ReplayProtectionException ex = Assertions.assertThrows(ReplayProtectionException.class, () -> service.check(request));
|
||||
Assertions.assertTrue(ex.isClientError());
|
||||
Mockito.verifyNoInteractions(replayNonceRepository);
|
||||
Mockito.verifyNoInteractions(securityEventRepository);
|
||||
}
|
||||
|
||||
@ -157,6 +157,32 @@ class InternalApiReplayInterceptorTest {
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SERVICE_UNAVAILABLE.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnBadRequestWhenReplayRequestIsInvalid() throws Exception {
|
||||
ReplayProtectionService replayProtectionService = Mockito.mock(ReplayProtectionService.class);
|
||||
Mockito.when(replayProtectionService.check(Mockito.any(ReplayCheckRequest.class)))
|
||||
.thenThrow(ReplayProtectionException.badRequest("请求时间戳超出允许窗口"));
|
||||
InternalApiReplayInterceptor interceptor = new InternalApiReplayInterceptor(
|
||||
replayProtectionService,
|
||||
new TmsSecurityProperties(),
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK
|
||||
);
|
||||
MockHttpServletRequest sourceRequest = new MockHttpServletRequest("POST", "/api/v1/init/tasks/task-001/execute");
|
||||
sourceRequest.addHeader("X-Request-Timestamp", Long.toString(FIXED_REQUEST_TIMESTAMP - 3600L));
|
||||
sourceRequest.addHeader("X-Request-Nonce", "nonce-001");
|
||||
CachedBodyHttpServletRequest request = new CachedBodyHttpServletRequest(sourceRequest);
|
||||
request.setAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN, "session-001");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
boolean allowed = interceptor.preHandle(request, response, protectedHandler("execute"));
|
||||
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(400, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("请求时间戳超出允许窗口"));
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.BAD_REQUEST.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipReplayProtectionWhenDisabled() throws Exception {
|
||||
ReplayProtectionService replayProtectionService = Mockito.mock(ReplayProtectionService.class);
|
||||
|
||||
@ -151,6 +151,36 @@ class OpenApiSignAuthInterceptorTest {
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SERVICE_UNAVAILABLE.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnBadRequestWhenReplayRequestIsInvalid() throws Exception {
|
||||
TmsSecurityProperties securityProperties = securityProperties("demo-app", "demo-secret", 300L);
|
||||
ReplayProtectionService replayProtectionService = Mockito.mock(ReplayProtectionService.class);
|
||||
Mockito.when(replayProtectionService.check(Mockito.any(ReplayCheckRequest.class)))
|
||||
.thenThrow(ReplayProtectionException.badRequest("请求时间戳超出允许窗口"));
|
||||
OpenApiSignAuthInterceptor interceptor = new OpenApiSignAuthInterceptor(
|
||||
securityProperties,
|
||||
replayProtectionService,
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK
|
||||
);
|
||||
MockHttpServletRequest request = signedRequest(
|
||||
"demo-app",
|
||||
"demo-secret",
|
||||
FIXED_NOW_SECONDS,
|
||||
"nonce-001",
|
||||
"POST",
|
||||
"/openapi/v1/demo"
|
||||
);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
boolean allowed = interceptor.preHandle(request, response, new Object());
|
||||
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(400, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("请求时间戳超出允许窗口"));
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.BAD_REQUEST.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipTimestampWindowAndNonceClaimWhenReplayProtectionIsDisabled() throws Exception {
|
||||
TmsSecurityProperties securityProperties = securityProperties("demo-app", "demo-secret", 300L);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user