错误码梳理
This commit is contained in:
@@ -3,9 +3,13 @@ package com.cisd.tms.common.enums;
|
||||
public enum ErrorCode {
|
||||
BAD_REQUEST(400, "bad request"),
|
||||
UNAUTHORIZED(4010, "unauthorized"),
|
||||
SESSION_INVALID(4011, "session invalid"),
|
||||
FORBIDDEN(4030, "forbidden"),
|
||||
VALIDATE_FAILED(4001, "parameter validation failed"),
|
||||
CONFLICT(4090, "conflict"),
|
||||
BIZ_ERROR(5001, "business exception"),
|
||||
CRYPTO_CARD_ERROR(5201, "crypto card adapter error"),
|
||||
SERVICE_UNAVAILABLE(5030, "service unavailable"),
|
||||
INTERNAL_ERROR(5000, "internal server error");
|
||||
|
||||
private final int code;
|
||||
|
||||
@@ -8,6 +8,8 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
@@ -20,7 +22,7 @@ public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ApiResponse<Void> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpServletRequest request) {
|
||||
public ResponseEntity<ApiResponse<Void>> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpServletRequest request) {
|
||||
String msg = ex.getBindingResult().getAllErrors().stream()
|
||||
.findFirst()
|
||||
.map(error -> {
|
||||
@@ -30,53 +32,84 @@ public class GlobalExceptionHandler {
|
||||
return error.getDefaultMessage();
|
||||
})
|
||||
.orElse(ErrorCode.VALIDATE_FAILED.getMessage());
|
||||
return ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), msg).withPath(request.getRequestURI());
|
||||
return response(HttpStatus.BAD_REQUEST, ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), msg).withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ApiResponse<Void> handleConstraintViolation(ConstraintViolationException ex, HttpServletRequest request) {
|
||||
return ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage()).withPath(request.getRequestURI());
|
||||
public ResponseEntity<ApiResponse<Void>> handleConstraintViolation(ConstraintViolationException ex, HttpServletRequest request) {
|
||||
return response(HttpStatus.BAD_REQUEST,
|
||||
ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage()).withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(BizException.class)
|
||||
public ApiResponse<Void> handleBizException(BizException ex, HttpServletRequest request) {
|
||||
return ApiResponse.fail(ex.getCode(), ex.getMessage()).withPath(request.getRequestURI());
|
||||
public ResponseEntity<ApiResponse<Void>> handleBizException(BizException ex, HttpServletRequest request) {
|
||||
return response(resolveHttpStatus(ex.getCode()),
|
||||
ApiResponse.fail(ex.getCode(), ex.getMessage()).withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ApiResponse<Void> handleIllegalArgument(IllegalArgumentException ex, HttpServletRequest request) {
|
||||
return ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage()).withPath(request.getRequestURI());
|
||||
public ResponseEntity<ApiResponse<Void>> handleIllegalArgument(IllegalArgumentException ex, HttpServletRequest request) {
|
||||
return response(HttpStatus.BAD_REQUEST,
|
||||
ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage()).withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||
public ApiResponse<Void> handleMaxUploadSizeExceeded(MaxUploadSizeExceededException ex, HttpServletRequest request) {
|
||||
return ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "upload file size exceeds limit")
|
||||
.withPath(request.getRequestURI());
|
||||
public ResponseEntity<ApiResponse<Void>> handleMaxUploadSizeExceeded(MaxUploadSizeExceededException ex, HttpServletRequest request) {
|
||||
return response(HttpStatus.BAD_REQUEST, ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "upload file size exceeds limit")
|
||||
.withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MultipartException.class)
|
||||
public ApiResponse<Void> handleMultipartException(MultipartException ex, HttpServletRequest request) {
|
||||
return ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "failed to parse multipart request")
|
||||
.withPath(request.getRequestURI());
|
||||
public ResponseEntity<ApiResponse<Void>> handleMultipartException(MultipartException ex, HttpServletRequest request) {
|
||||
return response(HttpStatus.BAD_REQUEST, ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "failed to parse multipart request")
|
||||
.withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(PcieCryptoException.class)
|
||||
public ApiResponse<Void> handleCryptoCardException(PcieCryptoException ex, HttpServletRequest request) {
|
||||
return ApiResponse.fail(ErrorCode.CRYPTO_CARD_ERROR.getCode(), ex.getMessage())
|
||||
.withPath(request.getRequestURI());
|
||||
public ResponseEntity<ApiResponse<Void>> handleCryptoCardException(PcieCryptoException ex, HttpServletRequest request) {
|
||||
return response(HttpStatus.INTERNAL_SERVER_ERROR, ApiResponse.fail(ErrorCode.CRYPTO_CARD_ERROR.getCode(), ex.getMessage())
|
||||
.withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(ReplayProtectionException.class)
|
||||
public ApiResponse<Void> handleReplayProtectionException(ReplayProtectionException ex, HttpServletRequest request) {
|
||||
public ResponseEntity<ApiResponse<Void>> handleReplayProtectionException(ReplayProtectionException ex, HttpServletRequest request) {
|
||||
log.warn("Replay protection failed", ex);
|
||||
return ApiResponse.fail(ErrorCode.INTERNAL_ERROR.getCode(), "replay protection unavailable")
|
||||
.withPath(request.getRequestURI());
|
||||
return response(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
ApiResponse.fail(ErrorCode.SERVICE_UNAVAILABLE.getCode(), "replay protection unavailable")
|
||||
.withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ApiResponse<Void> handleException(Exception ex, HttpServletRequest request) {
|
||||
public ResponseEntity<ApiResponse<Void>> handleException(Exception ex, HttpServletRequest request) {
|
||||
log.error("Unhandled exception", ex);
|
||||
return ApiResponse.fail(ErrorCode.INTERNAL_ERROR.getCode(), ErrorCode.INTERNAL_ERROR.getMessage())
|
||||
.withPath(request.getRequestURI());
|
||||
return response(HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
ApiResponse.fail(ErrorCode.INTERNAL_ERROR.getCode(), ErrorCode.INTERNAL_ERROR.getMessage())
|
||||
.withPath(request.getRequestURI()));
|
||||
}
|
||||
|
||||
private ResponseEntity<ApiResponse<Void>> response(HttpStatus status, ApiResponse<Void> body) {
|
||||
return ResponseEntity.status(status).body(body);
|
||||
}
|
||||
|
||||
private HttpStatus resolveHttpStatus(int code) {
|
||||
if (code == ErrorCode.BAD_REQUEST.getCode() || code == ErrorCode.VALIDATE_FAILED.getCode()) {
|
||||
return HttpStatus.BAD_REQUEST;
|
||||
}
|
||||
if (code == ErrorCode.UNAUTHORIZED.getCode()) {
|
||||
return HttpStatus.UNAUTHORIZED;
|
||||
}
|
||||
if (code == ErrorCode.SESSION_INVALID.getCode()) {
|
||||
return HttpStatus.UNAUTHORIZED;
|
||||
}
|
||||
if (code == ErrorCode.FORBIDDEN.getCode()) {
|
||||
return HttpStatus.FORBIDDEN;
|
||||
}
|
||||
if (code == ErrorCode.CONFLICT.getCode()) {
|
||||
return HttpStatus.CONFLICT;
|
||||
}
|
||||
if (code == ErrorCode.SERVICE_UNAVAILABLE.getCode()) {
|
||||
return HttpStatus.SERVICE_UNAVAILABLE;
|
||||
}
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ public class InternalAuthorizationInterceptor implements HandlerInterceptor {
|
||||
HttpResponseUtil.writeJson(
|
||||
response,
|
||||
HttpServletResponse.SC_FORBIDDEN,
|
||||
ApiResponse.fail(ErrorCode.UNAUTHORIZED.getCode(), message),
|
||||
ApiResponse.fail(ErrorCode.FORBIDDEN.getCode(), message),
|
||||
objectMapper
|
||||
);
|
||||
}
|
||||
|
||||
@@ -461,9 +461,9 @@ public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private AuthSessionEntity requireActiveSession(String sessionToken) {
|
||||
AuthSessionEntity session = authSessionRepository.findBySessionToken(sessionToken)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "session not found"));
|
||||
.orElseThrow(() -> new BizException(ErrorCode.SESSION_INVALID.getCode(), "session not found"));
|
||||
if (session.getLogoutAt() != null || session.getExpiresAt() == null || !session.getExpiresAt().isAfter(now())) {
|
||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "session expired");
|
||||
throw new BizException(ErrorCode.SESSION_INVALID.getCode(), "session expired");
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.cisd.tms.modules.device.controller;
|
||||
|
||||
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.device.dto.network.*;
|
||||
import com.cisd.tms.modules.device.service.NetworkConfigService;
|
||||
import com.cisd.tms.modules.log.annotation.AuditedOperation;
|
||||
@@ -122,7 +124,7 @@ public class NetworkConfigController {
|
||||
String mode = networkConfigService.getBondMode(bondName);
|
||||
|
||||
if (mode == null) {
|
||||
return ApiResponse.fail(400, "未找到该连接的 Bond 模式", null);
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "未找到该连接的 Bond 模式");
|
||||
}
|
||||
|
||||
return ApiResponse.success(mode);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package com.cisd.tms.modules.device.filter;
|
||||
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.util.HttpResponseUtil;
|
||||
import com.cisd.tms.modules.device.service.IpWhitelistService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -17,6 +21,9 @@ public class IpWhitelistFilter implements Filter {
|
||||
@Autowired
|
||||
private IpWhitelistService ipWhitelistService;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
@@ -39,9 +46,7 @@ public class IpWhitelistFilter implements Filter {
|
||||
try {
|
||||
clientIpInt = ipToInt(clientIp);
|
||||
} catch (IllegalArgumentException e) {
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
response.getWriter().write("{\"code\": 403, \"msg\": \"非法的客户端 IP 格式,拒绝访问\"}");
|
||||
writeForbidden(response, "非法的客户端 IP 格式,拒绝访问");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,9 +77,7 @@ public class IpWhitelistFilter implements Filter {
|
||||
|
||||
// 默认拒绝
|
||||
if (!isAllowed) {
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
response.getWriter().write("{\"code\": 403, \"msg\": \"非法访问:您的IP不在允许的白名单内\"}");
|
||||
writeForbidden(response, "非法访问:您的IP不在允许的白名单内");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,4 +139,13 @@ public class IpWhitelistFilter implements Filter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private void writeForbidden(HttpServletResponse response, String message) throws IOException {
|
||||
HttpResponseUtil.writeJson(
|
||||
response,
|
||||
HttpServletResponse.SC_FORBIDDEN,
|
||||
ApiResponse.fail(ErrorCode.FORBIDDEN.getCode(), message),
|
||||
objectMapper
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.cisd.tms.modules.log.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.log.dto.AuditLogReviewRequest;
|
||||
import com.cisd.tms.modules.log.dto.OperationAuditLogPageRequest;
|
||||
import com.cisd.tms.modules.log.dto.OperationAuditLogPageResponse;
|
||||
@@ -50,7 +52,7 @@ public class OperationAuditController {
|
||||
|
||||
// 权限校验:仅审计管理员可操作
|
||||
if (roleObj == null || !roleObj.toString().equals("AUDIT_ADMIN")) {
|
||||
return ApiResponse.fail(403, "权限不足:仅审计管理员可执行此操作");
|
||||
throw new BizException(ErrorCode.FORBIDDEN.getCode(), "权限不足:仅审计管理员可执行此操作");
|
||||
}
|
||||
|
||||
auditorUser = roleObj.toString();
|
||||
@@ -59,4 +61,4 @@ public class OperationAuditController {
|
||||
auditService.reviewLog(logId, req, auditorUser);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,17 +51,17 @@ public class InternalApiAuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
String sessionToken = extractSessionToken(request);
|
||||
if (sessionToken == null || sessionToken.isBlank()) {
|
||||
writeUnauthorized(response, "session token missing");
|
||||
writeSessionInvalid(response, "session token missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthSessionEntity session = authSessionRepository.findBySessionToken(sessionToken).orElse(null);
|
||||
if (session == null || session.getLogoutAt() != null) {
|
||||
writeUnauthorized(response, "session not found");
|
||||
writeSessionInvalid(response, "session not found");
|
||||
return false;
|
||||
}
|
||||
if (session.getExpiresAt() == null || !session.getExpiresAt().isAfter(now())) {
|
||||
writeUnauthorized(response, "session expired");
|
||||
writeSessionInvalid(response, "session expired");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -99,4 +99,13 @@ public class InternalApiAuthInterceptor implements HandlerInterceptor {
|
||||
objectMapper
|
||||
);
|
||||
}
|
||||
|
||||
private void writeSessionInvalid(HttpServletResponse response, String message) throws IOException {
|
||||
HttpResponseUtil.writeJson(
|
||||
response,
|
||||
HttpServletResponse.SC_UNAUTHORIZED,
|
||||
ApiResponse.fail(ErrorCode.SESSION_INVALID.getCode(), message),
|
||||
objectMapper
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +69,11 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor {
|
||||
try {
|
||||
ReplayCheckResult result = replayProtectionService.check(replayRequest);
|
||||
if (result.isReplayed()) {
|
||||
writeJson(response, HttpServletResponse.SC_CONFLICT, ErrorCode.UNAUTHORIZED.getCode(), "replayed nonce");
|
||||
writeJson(response, HttpServletResponse.SC_CONFLICT, ErrorCode.CONFLICT.getCode(), "replayed nonce");
|
||||
return false;
|
||||
}
|
||||
} catch (ReplayProtectionException ex) {
|
||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, ErrorCode.INTERNAL_ERROR.getCode(),
|
||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, ErrorCode.SERVICE_UNAVAILABLE.getCode(),
|
||||
"replay protection unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,11 +89,12 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
||||
// 先完成签名校验,再占用 nonce,避免伪造请求提前消耗合法 nonce。
|
||||
ReplayCheckResult result = replayProtectionService.check(buildReplayRequest(request, appId, nonce, requestEpoch));
|
||||
if (result.isReplayed()) {
|
||||
writeUnauthorized(response, "replayed nonce");
|
||||
writeJson(response, HttpServletResponse.SC_CONFLICT, ErrorCode.CONFLICT.getCode(), "replayed nonce");
|
||||
return false;
|
||||
}
|
||||
} catch (ReplayProtectionException ex) {
|
||||
writeUnauthorized(response, "replay protection unavailable");
|
||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE,
|
||||
ErrorCode.SERVICE_UNAVAILABLE.getCode(), "replay protection unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -136,10 +137,14 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
||||
}
|
||||
|
||||
private void writeUnauthorized(HttpServletResponse response, String message) throws IOException {
|
||||
writeJson(response, HttpServletResponse.SC_UNAUTHORIZED, ErrorCode.UNAUTHORIZED.getCode(), message);
|
||||
}
|
||||
|
||||
private void writeJson(HttpServletResponse response, int status, int code, String message) throws IOException {
|
||||
HttpResponseUtil.writeJson(
|
||||
response,
|
||||
HttpServletResponse.SC_UNAUTHORIZED,
|
||||
ApiResponse.fail(ErrorCode.UNAUTHORIZED.getCode(), message),
|
||||
status,
|
||||
ApiResponse.fail(code, message),
|
||||
objectMapper
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
@@ -15,15 +17,17 @@ class GlobalExceptionHandlerTest {
|
||||
GlobalExceptionHandler handler = new GlobalExceptionHandler();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/upgrade-packages");
|
||||
|
||||
ApiResponse<Void> response = handler.handleMaxUploadSizeExceeded(
|
||||
ResponseEntity<ApiResponse<Void>> response = handler.handleMaxUploadSizeExceeded(
|
||||
new MaxUploadSizeExceededException(104857600),
|
||||
request
|
||||
);
|
||||
|
||||
Assertions.assertFalse(response.isSuccess());
|
||||
Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getCode());
|
||||
Assertions.assertEquals("upload file size exceeds limit", response.getMsg());
|
||||
Assertions.assertEquals("/api/v1/upgrade-packages", response.getPath());
|
||||
Assertions.assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
Assertions.assertNotNull(response.getBody());
|
||||
Assertions.assertFalse(response.getBody().isSuccess());
|
||||
Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getBody().getCode());
|
||||
Assertions.assertEquals("upload file size exceeds limit", response.getBody().getMsg());
|
||||
Assertions.assertEquals("/api/v1/upgrade-packages", response.getBody().getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -31,14 +35,16 @@ class GlobalExceptionHandlerTest {
|
||||
GlobalExceptionHandler handler = new GlobalExceptionHandler();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/files/upload");
|
||||
|
||||
ApiResponse<Void> response = handler.handleMultipartException(
|
||||
ResponseEntity<ApiResponse<Void>> response = handler.handleMultipartException(
|
||||
new MultipartException("Failed to parse multipart servlet request"),
|
||||
request
|
||||
);
|
||||
|
||||
Assertions.assertFalse(response.isSuccess());
|
||||
Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getCode());
|
||||
Assertions.assertEquals("failed to parse multipart request", response.getMsg());
|
||||
Assertions.assertEquals("/api/v1/files/upload", response.getPath());
|
||||
Assertions.assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
Assertions.assertNotNull(response.getBody());
|
||||
Assertions.assertFalse(response.getBody().isSuccess());
|
||||
Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getBody().getCode());
|
||||
Assertions.assertEquals("failed to parse multipart request", response.getBody().getMsg());
|
||||
Assertions.assertEquals("/api/v1/files/upload", response.getBody().getPath());
|
||||
}
|
||||
}
|
||||
|
||||
+79
-86
@@ -1,86 +1,79 @@
|
||||
//package com.cisd.tms.modules.auth.security;
|
||||
//
|
||||
//import com.cisd.tms.modules.log.service.OperationAuditService;
|
||||
//import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
|
||||
//import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
//import org.junit.jupiter.api.Assertions;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.springframework.mock.web.MockHttpServletRequest;
|
||||
//import org.springframework.mock.web.MockHttpServletResponse;
|
||||
//import org.springframework.web.method.HandlerMethod;
|
||||
//
|
||||
//import java.lang.reflect.Method;
|
||||
//
|
||||
//class InternalAuthorizationInterceptorTest {
|
||||
//
|
||||
// @Test
|
||||
// void shouldAllowRequestWhenHandlerHasNoAuthorizationAnnotations() throws Exception {
|
||||
// InternalAuthorizationInterceptor interceptor = new InternalAuthorizationInterceptor(new ObjectMapper());
|
||||
// MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/v1/public");
|
||||
// MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
//
|
||||
// boolean allowed = interceptor.preHandle(request, response, handler("openEndpoint"));
|
||||
//
|
||||
// Assertions.assertTrue(allowed);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void shouldRejectWhenRoleDoesNotMatchRequiredRole() throws Exception {
|
||||
// InternalAuthorizationInterceptor interceptor = new InternalAuthorizationInterceptor(new ObjectMapper());
|
||||
// MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/auth/roles/AUDIT_ADMIN/enable");
|
||||
// request.setAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "AUDIT_ADMIN");
|
||||
// request.setAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "FULL");
|
||||
// MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
//
|
||||
// boolean allowed = interceptor.preHandle(request, response, handler("keyAdminFullEndpoint"));
|
||||
//
|
||||
// Assertions.assertFalse(allowed);
|
||||
// Assertions.assertEquals(403, response.getStatus());
|
||||
// Assertions.assertTrue(response.getContentAsString().contains("role not allowed"));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void shouldRejectWhenAuthLevelDoesNotMatchRequirement() throws Exception {
|
||||
// InternalAuthorizationInterceptor interceptor = new InternalAuthorizationInterceptor(new ObjectMapper());
|
||||
// MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/auth/roles/AUDIT_ADMIN/reset-password");
|
||||
// request.setAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "KEY_ADMIN");
|
||||
// request.setAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "LIMITED");
|
||||
// MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
//
|
||||
// boolean allowed = interceptor.preHandle(request, response, handler("keyAdminFullEndpoint"));
|
||||
//
|
||||
// Assertions.assertFalse(allowed);
|
||||
// Assertions.assertEquals(403, response.getStatus());
|
||||
// Assertions.assertTrue(response.getContentAsString().contains("auth level not allowed"));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void shouldAllowWhenRoleAndAuthLevelMatch() throws Exception {
|
||||
// InternalAuthorizationInterceptor interceptor = new InternalAuthorizationInterceptor(new ObjectMapper());
|
||||
// MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/auth/roles/AUDIT_ADMIN/reset-password");
|
||||
// request.setAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "KEY_ADMIN");
|
||||
// request.setAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "FULL");
|
||||
// MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
//
|
||||
// boolean allowed = interceptor.preHandle(request, response, handler("keyAdminFullEndpoint"));
|
||||
//
|
||||
// Assertions.assertTrue(allowed);
|
||||
// }
|
||||
//
|
||||
// private HandlerMethod handler(String methodName) throws NoSuchMethodException {
|
||||
// DemoController controller = new DemoController();
|
||||
// Method method = DemoController.class.getDeclaredMethod(methodName);
|
||||
// return new HandlerMethod(controller, method);
|
||||
// }
|
||||
//
|
||||
// @SuppressWarnings("unused")
|
||||
// private static class DemoController {
|
||||
//
|
||||
// public void openEndpoint() {
|
||||
// }
|
||||
//
|
||||
// @RequireInternalAuth(role = com.cisd.tms.modules.auth.enums.RoleCode.KEY_ADMIN, authLevel = com.cisd.tms.modules.auth.enums.AuthLevel.FULL)
|
||||
// public void keyAdminFullEndpoint() {
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
package com.cisd.tms.modules.auth.security;
|
||||
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.modules.log.service.OperationAuditService;
|
||||
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
class InternalAuthorizationInterceptorTest {
|
||||
|
||||
@Test
|
||||
void shouldRejectWhenRoleDoesNotMatchRequiredRole() throws Exception {
|
||||
OperationAuditService operationAuditService = Mockito.mock(OperationAuditService.class);
|
||||
InternalAuthorizationInterceptor interceptor = new InternalAuthorizationInterceptor(new ObjectMapper(), operationAuditService);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/auth/roles/AUDIT_ADMIN/enable");
|
||||
request.setAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "AUDIT_ADMIN");
|
||||
request.setAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "FULL");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
boolean allowed = interceptor.preHandle(request, response, handler("keyAdminFullEndpoint"));
|
||||
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(403, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.FORBIDDEN.getCode()));
|
||||
Assertions.assertTrue(response.getContentAsString().contains("role not allowed"));
|
||||
Mockito.verify(operationAuditService).recordDeniedLog(Mockito.eq(request), Mockito.any(HandlerMethod.class), Mockito.eq("role not allowed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectWhenAuthLevelDoesNotMatchRequirement() throws Exception {
|
||||
OperationAuditService operationAuditService = Mockito.mock(OperationAuditService.class);
|
||||
InternalAuthorizationInterceptor interceptor = new InternalAuthorizationInterceptor(new ObjectMapper(), operationAuditService);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/auth/roles/AUDIT_ADMIN/reset-password");
|
||||
request.setAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "KEY_ADMIN");
|
||||
request.setAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "LIMITED");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
boolean allowed = interceptor.preHandle(request, response, handler("keyAdminFullEndpoint"));
|
||||
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(403, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.FORBIDDEN.getCode()));
|
||||
Assertions.assertTrue(response.getContentAsString().contains("auth level not allowed"));
|
||||
Mockito.verify(operationAuditService).recordDeniedLog(Mockito.eq(request), Mockito.any(HandlerMethod.class), Mockito.eq("auth level not allowed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowWhenRoleAndAuthLevelMatch() throws Exception {
|
||||
OperationAuditService operationAuditService = Mockito.mock(OperationAuditService.class);
|
||||
InternalAuthorizationInterceptor interceptor = new InternalAuthorizationInterceptor(new ObjectMapper(), operationAuditService);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/auth/roles/AUDIT_ADMIN/reset-password");
|
||||
request.setAttribute(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "KEY_ADMIN");
|
||||
request.setAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL, "FULL");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
boolean allowed = interceptor.preHandle(request, response, handler("keyAdminFullEndpoint"));
|
||||
|
||||
Assertions.assertTrue(allowed);
|
||||
Mockito.verifyNoInteractions(operationAuditService);
|
||||
}
|
||||
|
||||
private static HandlerMethod handler(String methodName) throws NoSuchMethodException {
|
||||
DemoController controller = new DemoController();
|
||||
return new HandlerMethod(controller, DemoController.class.getDeclaredMethod(methodName));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class DemoController {
|
||||
|
||||
@RequireInternalAuth(role = com.cisd.tms.modules.auth.enums.RoleCode.KEY_ADMIN, authLevel = com.cisd.tms.modules.auth.enums.AuthLevel.FULL)
|
||||
public void keyAdminFullEndpoint() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cisd.tms.modules.auth.service;
|
||||
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.BizException;
|
||||
import com.cisd.tms.modules.auth.dto.CaptchaResponse;
|
||||
import com.cisd.tms.modules.auth.dto.CurrentUserResponse;
|
||||
@@ -193,6 +194,31 @@ class AuthServiceTest {
|
||||
Assertions.assertEquals(LocalDateTime.of(2026, 3, 23, 2, 0), changed.getLastActiveAt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowSessionInvalidWhenChangingPasswordWithExpiredSession() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
InMemoryAuthSessionRepository sessions = new InMemoryAuthSessionRepository();
|
||||
roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "12345678", "ROLE-SALT-K", 0, null, true));
|
||||
AuthSessionEntity expiredSession = session("token-expired-001", RoleCode.KEY_ADMIN.getCode(), AuthLevel.FULL.name());
|
||||
expiredSession.setExpiresAt(LocalDateTime.of(2026, 3, 23, 1, 59));
|
||||
sessions.save(expiredSession);
|
||||
|
||||
AuthService service = newAuthService(
|
||||
roleAccounts,
|
||||
new InMemoryAuthUserAccountRepository(),
|
||||
sessions,
|
||||
new InMemoryRoleUkeyBindingRepository(),
|
||||
FIXED_CLOCK,
|
||||
() -> "unused"
|
||||
);
|
||||
|
||||
BizException exception = Assertions.assertThrows(BizException.class,
|
||||
() -> service.changePassword("token-expired-001", "12345678", "87654321"));
|
||||
|
||||
Assertions.assertEquals(ErrorCode.SESSION_INVALID.getCode(), exception.getCode());
|
||||
Assertions.assertEquals("session expired", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIssueCaptchaAndAcceptPasswordLoginWithValidCaptcha() {
|
||||
InMemoryRoleAccountRepository roleAccounts = new InMemoryRoleAccountRepository();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.cisd.tms.modules.log.controller;
|
||||
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.exception.GlobalExceptionHandler;
|
||||
import com.cisd.tms.modules.log.dto.AuditLogReviewRequest;
|
||||
import com.cisd.tms.modules.log.enums.AuditResult;
|
||||
import com.cisd.tms.modules.log.service.OperationAuditService;
|
||||
@@ -34,6 +36,7 @@ class OperationAuditControllerTest {
|
||||
|
||||
mockMvc = MockMvcBuilders
|
||||
.standaloneSetup(new OperationAuditController(auditService))
|
||||
.setControllerAdvice(new GlobalExceptionHandler())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -64,8 +67,8 @@ class OperationAuditControllerTest {
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(req))
|
||||
.requestAttr(InternalApiAuthInterceptor.ATTR_ROLE_CODE, "SYS_ADMIN"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(403))
|
||||
.andExpect(status().isForbidden())
|
||||
.andExpect(jsonPath("$.code").value(ErrorCode.FORBIDDEN.getCode()))
|
||||
.andExpect(jsonPath("$.msg").value("权限不足:仅审计管理员可执行此操作"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
class LmkControllerTest {
|
||||
@@ -22,7 +23,7 @@ class LmkControllerTest {
|
||||
|
||||
mockMvc.perform(post("/api/v1/masterKey/recover")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
.content("""
|
||||
{
|
||||
"packets": [
|
||||
{
|
||||
@@ -48,7 +49,8 @@ class LmkControllerTest {
|
||||
]
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk());
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value(4001));
|
||||
|
||||
Mockito.verifyNoInteractions(lmkService);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.cisd.tms.modules.security.replay.enums.ReplayCheckStatus;
|
||||
import com.cisd.tms.modules.security.replay.enums.ReplayScope;
|
||||
import com.cisd.tms.modules.security.replay.exception.ReplayProtectionException;
|
||||
import com.cisd.tms.modules.security.replay.service.ReplayProtectionService;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
@@ -122,6 +123,7 @@ class InternalApiReplayInterceptorTest {
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(409, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("replayed nonce"));
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.CONFLICT.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,6 +148,7 @@ class InternalApiReplayInterceptorTest {
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(503, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("replay protection unavailable"));
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SERVICE_UNAVAILABLE.getCode()));
|
||||
}
|
||||
|
||||
private static HandlerMethod protectedHandler(String methodName) throws NoSuchMethodException {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cisd.tms.security.openapi;
|
||||
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.config.properties.TmsSecurityProperties;
|
||||
import com.cisd.tms.common.util.HmacUtil;
|
||||
import com.cisd.tms.modules.security.replay.dto.ReplayCheckRequest;
|
||||
@@ -115,8 +116,9 @@ class OpenApiSignAuthInterceptorTest {
|
||||
boolean allowed = interceptor.preHandle(request, response, new Object());
|
||||
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(401, response.getStatus());
|
||||
Assertions.assertEquals(409, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("replayed nonce"));
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.CONFLICT.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -144,8 +146,9 @@ class OpenApiSignAuthInterceptorTest {
|
||||
boolean allowed = interceptor.preHandle(request, response, new Object());
|
||||
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(401, response.getStatus());
|
||||
Assertions.assertEquals(503, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("replay protection unavailable"));
|
||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SERVICE_UNAVAILABLE.getCode()));
|
||||
}
|
||||
|
||||
private static TmsSecurityProperties securityProperties(String appId, String secret, long skewSeconds) {
|
||||
|
||||
Reference in New Issue
Block a user