fix:权限拦截增加主密钥生成前后判断
This commit is contained in:
parent
722106edc7
commit
9a1c4b8354
@ -101,13 +101,11 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
"/api/v1/masterKey/recover",
|
||||
"/api/v1/auth/password-login",
|
||||
"/api/v1/system/health",
|
||||
"/api/v1/device/time-config",
|
||||
"/api/v1/auth/ukey-login",
|
||||
"/api/v1/init/template",
|
||||
"/api/v1/auth/ukey-login/randoms",
|
||||
"/api/v1/auth/captcha",
|
||||
"/api/v1/auth/super-admin/ukeys/issue-sign",
|
||||
"/api/v1/device/network/**",
|
||||
"/api/v1/masterKey/activate",
|
||||
"/api/v1/masterKey/activate/info"
|
||||
);
|
||||
|
||||
@ -33,6 +33,9 @@ public class InternalAuthorizationInterceptor implements HandlerInterceptor {
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return true;
|
||||
}
|
||||
if (Boolean.TRUE.equals(request.getAttribute(InternalApiAuthInterceptor.ATTR_BOOTSTRAP_AUTH_BYPASS))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RequireInternalAuth requireInternalAuth = findAnnotation(handlerMethod, RequireInternalAuth.class);
|
||||
if (requireInternalAuth != null) {
|
||||
|
||||
@ -16,4 +16,6 @@ public @interface RequireInternalAuth {
|
||||
RoleCode[] anyRole() default {};
|
||||
|
||||
AuthLevel authLevel();
|
||||
|
||||
boolean allowBeforeMasterKeyInitialized() default false;
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package com.cisd.tms.modules.device.controller;
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
import com.cisd.tms.modules.device.dto.network.*;
|
||||
import com.cisd.tms.modules.device.entity.PageResult;
|
||||
import com.cisd.tms.modules.device.service.NetworkConfigService;
|
||||
@ -17,6 +20,7 @@ import java.util.List;
|
||||
@Tag(name = "网络配置管理", description = "提供设备网络信息查询、IPv4/IPv6配置、Bond配置及路由管理等接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/device/network")
|
||||
@RequireInternalAuth(role = RoleCode.OPS_ADMIN, authLevel = AuthLevel.FULL, allowBeforeMasterKeyInitialized = true)
|
||||
public class NetworkConfigController {
|
||||
|
||||
private final NetworkConfigService networkConfigService;
|
||||
|
||||
@ -2,7 +2,6 @@ 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.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
@ -11,7 +10,6 @@ import com.cisd.tms.modules.device.service.TimeConfigService;
|
||||
import com.cisd.tms.modules.log.annotation.AuditedOperation;
|
||||
import com.cisd.tms.modules.log.enums.ActionType;
|
||||
import com.cisd.tms.modules.log.enums.ModuleCode;
|
||||
import com.cisd.tms.security.internal.ReplayProtected;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@ -28,11 +26,10 @@ public class TimeConfigController {
|
||||
private final TimeConfigService timeConfigService;
|
||||
|
||||
@PostMapping("/time-config")
|
||||
@RequireInternalAuth(role = RoleCode.OPS_ADMIN, authLevel = AuthLevel.FULL, allowBeforeMasterKeyInitialized = true)
|
||||
@AuditedOperation(module = ModuleCode.DEVICE, action = ActionType.UPDATE, summary = "配置系统时间")
|
||||
public ApiResponse<Void> configureTime(@RequestBody TimeConfigRequest request) {
|
||||
//todo 鉴权
|
||||
timeConfigService.processTimeConfig(request);
|
||||
|
||||
return ApiResponse.success();
|
||||
timeConfigService.processTimeConfig(request);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,9 @@ import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.common.config.properties.TmsSecurityProperties;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.util.HttpResponseUtil;
|
||||
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
|
||||
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@ -15,9 +17,10 @@ import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.cors.CorsUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@Component
|
||||
@ -27,6 +30,7 @@ public class InternalApiAuthInterceptor implements HandlerInterceptor {
|
||||
public static final String ATTR_ROLE_CODE = "CURRENT_ROLE_CODE";
|
||||
public static final String ATTR_AUTH_LEVEL = "CURRENT_AUTH_LEVEL";
|
||||
public static final String ATTR_SESSION_TOKEN = "CURRENT_SESSION_TOKEN";
|
||||
public static final String ATTR_BOOTSTRAP_AUTH_BYPASS = "BOOTSTRAP_AUTH_BYPASS";
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
private static final String SESSION_HEADER = "X-Session-Token";
|
||||
|
||||
@ -37,6 +41,7 @@ public class InternalApiAuthInterceptor implements HandlerInterceptor {
|
||||
private final TmsSecurityProperties securityProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Clock clock;
|
||||
private final PcieCryptoService pcieCryptoService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
@ -51,6 +56,11 @@ public class InternalApiAuthInterceptor implements HandlerInterceptor {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowBootstrapWithoutSession(handler)) {
|
||||
request.setAttribute(ATTR_BOOTSTRAP_AUTH_BYPASS, Boolean.TRUE);
|
||||
return true;
|
||||
}
|
||||
|
||||
String sessionToken = extractSessionToken(request);
|
||||
if (sessionToken == null || sessionToken.isBlank()) {
|
||||
writeSessionInvalid(response, "会话令牌缺失");
|
||||
@ -89,6 +99,24 @@ public class InternalApiAuthInterceptor implements HandlerInterceptor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean allowBootstrapWithoutSession(Object handler) {
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return false;
|
||||
}
|
||||
RequireInternalAuth requireInternalAuth = findAnnotation(handlerMethod, RequireInternalAuth.class);
|
||||
return requireInternalAuth != null
|
||||
&& requireInternalAuth.allowBeforeMasterKeyInitialized()
|
||||
&& !pcieCryptoService.checkLmk();
|
||||
}
|
||||
|
||||
private <T extends java.lang.annotation.Annotation> T findAnnotation(HandlerMethod handlerMethod, Class<T> annotationType) {
|
||||
T methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getMethod(), annotationType);
|
||||
if (methodAnnotation != null) {
|
||||
return methodAnnotation;
|
||||
}
|
||||
return AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), annotationType);
|
||||
}
|
||||
|
||||
private LocalDateTime now() {
|
||||
return LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
@ -667,7 +667,7 @@ CREATE TABLE IF NOT EXISTS tms_certificate_crl_import_task (
|
||||
CREATE TABLE IF NOT EXISTS tms_operation_audit_log (
|
||||
id BIGINT PRIMARY KEY,
|
||||
log_id VARCHAR(64) NOT NULL COMMENT '日志唯一标识',
|
||||
operator_role_code VARCHAR(64) NOT NULL COMMENT '操作角色编码',
|
||||
operator_role_code VARCHAR(64) NULL COMMENT '操作角色编码',
|
||||
operator_auth_level VARCHAR(32) NULL COMMENT '操作时认证等级',
|
||||
module_code VARCHAR(32) NOT NULL COMMENT '业务模块',
|
||||
action_type VARCHAR(32) NOT NULL COMMENT '操作动作',
|
||||
|
||||
@ -94,6 +94,20 @@ class InternalAuthorizationInterceptorTest {
|
||||
Mockito.verifyNoInteractions(operationAuditService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowBootstrapBypassBeforeRoleAuthorization() throws Exception {
|
||||
OperationAuditService operationAuditService = Mockito.mock(OperationAuditService.class);
|
||||
InternalAuthorizationInterceptor interceptor = new InternalAuthorizationInterceptor(new ObjectMapper(), operationAuditService);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/device/time-config");
|
||||
request.setAttribute(InternalApiAuthInterceptor.ATTR_BOOTSTRAP_AUTH_BYPASS, Boolean.TRUE);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
boolean allowed = interceptor.preHandle(request, response, handler("bootstrapOpsEndpoint"));
|
||||
|
||||
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));
|
||||
@ -119,5 +133,13 @@ class InternalAuthorizationInterceptorTest {
|
||||
)
|
||||
public void opsOrSuperAdminFullEndpoint() {
|
||||
}
|
||||
|
||||
@RequireInternalAuth(
|
||||
role = com.cisd.tms.modules.auth.enums.RoleCode.OPS_ADMIN,
|
||||
authLevel = com.cisd.tms.modules.auth.enums.AuthLevel.FULL,
|
||||
allowBeforeMasterKeyInitialized = true
|
||||
)
|
||||
public void bootstrapOpsEndpoint() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,10 @@ package com.cisd.tms.security.internal;
|
||||
import com.cisd.tms.common.config.properties.TmsSecurityProperties;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
|
||||
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
@ -15,6 +18,7 @@ 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 InternalApiAuthInterceptorTest {
|
||||
|
||||
@ -27,7 +31,8 @@ class InternalApiAuthInterceptorTest {
|
||||
authSessionRepository,
|
||||
enabledSecurityProperties(),
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK
|
||||
FIXED_CLOCK,
|
||||
Mockito.mock(PcieCryptoService.class)
|
||||
);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/v1/auth/me");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@ -55,7 +60,8 @@ class InternalApiAuthInterceptorTest {
|
||||
authSessionRepository,
|
||||
enabledSecurityProperties(),
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK
|
||||
FIXED_CLOCK,
|
||||
Mockito.mock(PcieCryptoService.class)
|
||||
);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/v1/auth/me");
|
||||
request.addHeader("X-Session-Token", "token-expired-001");
|
||||
@ -69,9 +75,66 @@ class InternalApiAuthInterceptorTest {
|
||||
Assertions.assertTrue(response.getContentAsString().contains("会话已过期"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBypassSessionForBootstrapEndpointBeforeMasterKeyInitialized() throws Exception {
|
||||
AuthSessionRepository authSessionRepository = Mockito.mock(AuthSessionRepository.class);
|
||||
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
|
||||
Mockito.when(pcieCryptoService.checkLmk()).thenReturn(false);
|
||||
InternalApiAuthInterceptor interceptor = new InternalApiAuthInterceptor(
|
||||
authSessionRepository,
|
||||
enabledSecurityProperties(),
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
pcieCryptoService
|
||||
);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/device/time-config");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
boolean allowed = interceptor.preHandle(request, response, handler("bootstrapOpsEndpoint"));
|
||||
|
||||
Assertions.assertTrue(allowed);
|
||||
Assertions.assertEquals(Boolean.TRUE, request.getAttribute(InternalApiAuthInterceptor.ATTR_BOOTSTRAP_AUTH_BYPASS));
|
||||
Assertions.assertNull(request.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN));
|
||||
Mockito.verifyNoInteractions(authSessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireSessionForBootstrapEndpointAfterMasterKeyInitialized() throws Exception {
|
||||
AuthSessionRepository authSessionRepository = Mockito.mock(AuthSessionRepository.class);
|
||||
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
|
||||
Mockito.when(pcieCryptoService.checkLmk()).thenReturn(true);
|
||||
InternalApiAuthInterceptor interceptor = new InternalApiAuthInterceptor(
|
||||
authSessionRepository,
|
||||
enabledSecurityProperties(),
|
||||
new ObjectMapper(),
|
||||
FIXED_CLOCK,
|
||||
pcieCryptoService
|
||||
);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/device/time-config");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
boolean allowed = interceptor.preHandle(request, response, handler("bootstrapOpsEndpoint"));
|
||||
|
||||
Assertions.assertFalse(allowed);
|
||||
Assertions.assertEquals(401, response.getStatus());
|
||||
Assertions.assertTrue(response.getContentAsString().contains("会话令牌缺失"));
|
||||
}
|
||||
|
||||
private static TmsSecurityProperties enabledSecurityProperties() {
|
||||
TmsSecurityProperties properties = new TmsSecurityProperties();
|
||||
properties.getInternalAuth().setEnabled(true);
|
||||
return properties;
|
||||
}
|
||||
|
||||
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(authLevel = AuthLevel.FULL, allowBeforeMasterKeyInitialized = true)
|
||||
public void bootstrapOpsEndpoint() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user