From 3f8e2f278bdcf3fe0e9988cf58e3d74d19741289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B2=9C=20=EC=A7=84=EA=B0=95?= <jjjjjk12@ajou.ac.kr> Date: Tue, 18 Mar 2025 16:23:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8?= =?UTF-8?q?=EB=B3=84=20=EA=B6=8C=ED=95=9C=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aolda/itda/config/AuthInterceptor.java | 47 +++++++++++++++---- .../forwarding/ForwardingController.java | 17 ++++--- .../controller/routing/RoutingController.java | 18 ++++--- .../com/aolda/itda/exception/ErrorCode.java | 1 + .../com/aolda/itda/service/AuthService.java | 8 +++- .../service/forwarding/ForwardingService.java | 20 ++++++-- .../itda/service/routing/RoutingService.java | 20 ++++++-- 7 files changed, 103 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/aolda/itda/config/AuthInterceptor.java b/src/main/java/com/aolda/itda/config/AuthInterceptor.java index 63f2320..83c3821 100644 --- a/src/main/java/com/aolda/itda/config/AuthInterceptor.java +++ b/src/main/java/com/aolda/itda/config/AuthInterceptor.java @@ -1,8 +1,10 @@ package com.aolda.itda.config; +import com.aolda.itda.dto.auth.ProjectIdAndNameDTO; import com.aolda.itda.exception.CustomException; import com.aolda.itda.exception.ErrorCode; import com.aolda.itda.service.AuthService; +import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -10,6 +12,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @RequiredArgsConstructor @Component @Slf4j @@ -20,18 +26,43 @@ public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("X-Subject-Token"); + + /* 토큰 헤더 검증 */ if (token == null || token.isEmpty()) { throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); } - // 어드민과 일반 유저 구분 필요 - try { - if (authService.validateTokenAndGetUserId(token) != null) { - return true; - } - } catch (Exception e) { - log.error("Token validation failed for URI {}: {}", request.getRequestURI(), e.getMessage(), e); + + /* 유효 토큰 검증 */ + String userId = authService.validateTokenAndGetUserId(token); + if (userId == null) { + log.error("Token validation failed for URI {}: {}", request.getRequestURI(), request.getRemoteAddr()); throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); } - throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); + + /* 프로젝트 권한 검증 */ + String projectId = request.getParameter("projectId"); + if (projectId != null) { + + try { + String role = authService.getBestRoleWithinProject(token, projectId).get("role"); + if (!role.equals("admin")) { + log.error("Unauthorized Token for URI {}: {}", request.getRequestURI(), request.getRemoteAddr()); + throw new CustomException(ErrorCode.UNAUTHORIZED_USER, request.getRequestURI()); + } + } catch (Exception e) { + throw new CustomException(ErrorCode.UNAUTHORIZED_USER, request.getRequestURI()); + } + + + + } + + /* 프로젝트 리스트 조회 */ + List<String> projects = authService.getProjectsWithUser(Map.of("id", userId, "token", token)) + .stream().map(ProjectIdAndNameDTO::getId) + .toList(); + request.setAttribute("projects", projects); + return true; + } } diff --git a/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java b/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java index a6b67ec..36e6f9a 100644 --- a/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java +++ b/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java @@ -2,11 +2,14 @@ package com.aolda.itda.controller.forwarding; import com.aolda.itda.dto.forwarding.ForwardingDTO; import com.aolda.itda.service.forwarding.ForwardingService; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/api") @RequiredArgsConstructor @@ -22,8 +25,8 @@ public class ForwardingController { } @GetMapping("/forwarding") - public ResponseEntity<Object> view(@RequestParam Long forwardingId) { - return ResponseEntity.ok(forwardingService.getForwarding(forwardingId)); + public ResponseEntity<Object> view(@RequestParam Long forwardingId, HttpServletRequest request) { + return ResponseEntity.ok(forwardingService.getForwarding(forwardingId, (List<String>) request.getAttribute("projects"))); } @GetMapping("/forwardings") @@ -33,14 +36,16 @@ public class ForwardingController { @PatchMapping("/forwarding") public ResponseEntity<Object> edit(@RequestParam Long forwardingId, - @RequestBody ForwardingDTO dto) { - forwardingService.editForwarding(forwardingId, dto); + @RequestBody ForwardingDTO dto, + HttpServletRequest request) { + forwardingService.editForwarding(forwardingId, dto, (List<String>) request.getAttribute("projects") ); return ResponseEntity.ok().build(); } @DeleteMapping("/forwarding") - public ResponseEntity<Object> delete(@RequestParam Long forwardingId) { - forwardingService.deleteForwarding(forwardingId); + public ResponseEntity<Object> delete(@RequestParam Long forwardingId, + HttpServletRequest request) { + forwardingService.deleteForwarding(forwardingId, (List<String>) request.getAttribute("projects")); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/aolda/itda/controller/routing/RoutingController.java b/src/main/java/com/aolda/itda/controller/routing/RoutingController.java index dc4d0db..4b9d551 100644 --- a/src/main/java/com/aolda/itda/controller/routing/RoutingController.java +++ b/src/main/java/com/aolda/itda/controller/routing/RoutingController.java @@ -3,10 +3,13 @@ package com.aolda.itda.controller.routing; import com.aolda.itda.dto.forwarding.ForwardingDTO; import com.aolda.itda.dto.routing.RoutingDTO; import com.aolda.itda.service.routing.RoutingService; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/api") @RequiredArgsConstructor @@ -22,8 +25,9 @@ public class RoutingController { } @GetMapping("/routing") - public ResponseEntity<Object> view(@RequestParam Long routingId) { - return ResponseEntity.ok(routingService.getRouting(routingId)); + public ResponseEntity<Object> view(@RequestParam Long routingId, + HttpServletRequest request) { + return ResponseEntity.ok(routingService.getRouting(routingId, (List<String>) request.getAttribute("projects"))); } @GetMapping("/routings") @@ -33,14 +37,16 @@ public class RoutingController { @PatchMapping("/routing") public ResponseEntity<Object> edit(@RequestParam Long routingId, - @RequestBody RoutingDTO dto) { - routingService.editRouting(routingId, dto); + @RequestBody RoutingDTO dto, + HttpServletRequest request) { + routingService.editRouting(routingId, dto, (List<String>) request.getAttribute("projects")); return ResponseEntity.ok().build(); } @DeleteMapping("/routing") - public ResponseEntity<Object> delete(@RequestParam Long routingId) { - routingService.deleteRouting(routingId); + public ResponseEntity<Object> delete(@RequestParam Long routingId, + HttpServletRequest request) { + routingService.deleteRouting(routingId, (List<String>) request.getAttribute("projects")); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/aolda/itda/exception/ErrorCode.java b/src/main/java/com/aolda/itda/exception/ErrorCode.java index 321bdad..6eea5fb 100644 --- a/src/main/java/com/aolda/itda/exception/ErrorCode.java +++ b/src/main/java/com/aolda/itda/exception/ErrorCode.java @@ -10,6 +10,7 @@ public enum ErrorCode { // User INVALID_USER_INFO(HttpStatus.BAD_REQUEST, "잘못된 회원 정보입니다"), + UNAUTHORIZED_USER(HttpStatus.BAD_REQUEST, "권한이 없는 사용자입니다"), // Token INVALID_TOKEN(HttpStatus.BAD_REQUEST, "잘못된 토큰입니다"), diff --git a/src/main/java/com/aolda/itda/service/AuthService.java b/src/main/java/com/aolda/itda/service/AuthService.java index cc523fa..cbc157f 100644 --- a/src/main/java/com/aolda/itda/service/AuthService.java +++ b/src/main/java/com/aolda/itda/service/AuthService.java @@ -239,7 +239,7 @@ public class AuthService { } // 특정 사용자의 참여 프로젝트 반환 - private List<ProjectIdAndNameDTO> getProjectsWithUser(Map<String, String> user) throws JsonProcessingException { + public List<ProjectIdAndNameDTO> getProjectsWithUser(Map<String, String> user) throws JsonProcessingException { String userId = user.get("id"); String token = user.get("token"); if (userId == null || token == null) { @@ -283,6 +283,12 @@ public class AuthService { } + public void validateProjectAuth(List<String> projects, String projectId) { + if (!projects.contains(projectId)) { + throw new CustomException(ErrorCode.UNAUTHORIZED_USER); + } + } + private Boolean isAdmin(Map<String, String> user) throws JsonProcessingException { String url = keystone + "/role_assignments?user.id=" + user.get("id") + "&scope.system&include_names"; HttpHeaders headers = new HttpHeaders(); diff --git a/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java b/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java index 9ec9343..441090b 100644 --- a/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java +++ b/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java @@ -1,13 +1,16 @@ package com.aolda.itda.service.forwarding; import com.aolda.itda.dto.PageResp; +import com.aolda.itda.dto.auth.ProjectIdAndNameDTO; import com.aolda.itda.dto.forwarding.ForwardingDTO; import com.aolda.itda.entity.forwarding.Forwarding; import com.aolda.itda.exception.CustomException; import com.aolda.itda.exception.ErrorCode; import com.aolda.itda.repository.forwarding.ForwardingRepository; +import com.aolda.itda.service.AuthService; import com.aolda.itda.template.ForwardingTemplate; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import lombok.RequiredArgsConstructor; @@ -30,6 +33,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.List; import java.util.Map; @Service @@ -42,12 +46,17 @@ public class ForwardingService { private String serverBaseIp; private final ForwardingTemplate forwardingTemplate; private final ForwardingRepository forwardingRepository; + private final AuthService authService; private final RestTemplate restTemplate = new RestTemplate(); /* 포트포워딩 정보 조회 */ - public ForwardingDTO getForwarding(Long forwardingId) { + public ForwardingDTO getForwarding(Long forwardingId, List<String> projects) { Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, forwarding.getProjectId()); + return forwarding.toForwardingDTO(); } @@ -158,10 +167,12 @@ public class ForwardingService { } /* 포트포워딩 정보 수정 */ - public void editForwarding(Long forwardingId, ForwardingDTO dto) { + public void editForwarding(Long forwardingId, ForwardingDTO dto, List<String> projects) { Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, forwarding.getProjectId()); /* 중복 검증 */ if (dto.getServerPort() != null && forwardingRepository.existsByServerPortAndIsDeleted(dto.getServerPort(), false)) { @@ -270,10 +281,13 @@ public class ForwardingService { } /* 포트포워딩 삭제 (소프트) */ - public void deleteForwarding(Long forwardingId) { + public void deleteForwarding(Long forwardingId, List<String> projects) { Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, forwarding.getProjectId()); + /* 파일 삭제 */ String confPath = "/data/nginx/stream/" + forwarding.getForwardingId() + ".conf"; String deletePath = confPath + ".deleted"; diff --git a/src/main/java/com/aolda/itda/service/routing/RoutingService.java b/src/main/java/com/aolda/itda/service/routing/RoutingService.java index 9cfad7d..eb3777b 100644 --- a/src/main/java/com/aolda/itda/service/routing/RoutingService.java +++ b/src/main/java/com/aolda/itda/service/routing/RoutingService.java @@ -8,6 +8,7 @@ import com.aolda.itda.exception.CustomException; import com.aolda.itda.exception.ErrorCode; import com.aolda.itda.repository.certificate.CertificateRepository; import com.aolda.itda.repository.routing.RoutingRepository; +import com.aolda.itda.service.AuthService; import com.aolda.itda.template.RoutingTemplate; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; @@ -26,6 +27,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.List; @Service @Transactional @@ -35,14 +37,18 @@ public class RoutingService { private final RoutingRepository routingRepository; private final CertificateRepository certificateRepository; + private final AuthService authService; private final RoutingTemplate routingTemplate; private final RestTemplate restTemplate = new RestTemplate(); /* Routing 조회 */ - public RoutingDTO getRouting(Long routingId) { - // project id 확인 필요 + public RoutingDTO getRouting(Long routingId, List<String> projects) { Routing routing = routingRepository.findByRoutingIdAndIsDeleted(routingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTING)); + + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, routing.getProjectId()); + return routing.toRoutingDTO(); } @@ -154,10 +160,13 @@ public class RoutingService { } /* Routing 수정 */ - public void editRouting(Long routingId, RoutingDTO dto) { + public void editRouting(Long routingId, RoutingDTO dto, List<String> projects) { Routing routing = routingRepository.findByRoutingIdAndIsDeleted(routingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTING)); + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, routing.getProjectId()); + /* 입력 DTO 검증 */ validateDTO(dto); @@ -253,10 +262,13 @@ public class RoutingService { } /* Routing 삭제 */ - public void deleteRouting(Long routingId) { + public void deleteRouting(Long routingId, List<String> projects) { Routing routing = routingRepository.findByRoutingIdAndIsDeleted(routingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTING)); + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, routing.getProjectId()); + /* 파일 삭제 */ String confPath = "/data/nginx/proxy_host/" + routing.getRoutingId() + ".conf"; String deletePath = confPath + ".deleted"; -- GitLab