diff --git a/src/main/java/kr/re/etri/autoflow/controllers/ExternalAuthController.java b/src/main/java/kr/re/etri/autoflow/controllers/ExternalAuthController.java index 4b06325..ead3311 100644 --- a/src/main/java/kr/re/etri/autoflow/controllers/ExternalAuthController.java +++ b/src/main/java/kr/re/etri/autoflow/controllers/ExternalAuthController.java @@ -1,7 +1,5 @@ package kr.re.etri.autoflow.controllers; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -53,28 +51,38 @@ public class ExternalAuthController { @Operation( summary = "외부 Edge 패키지 등록", description = "외부 서버로 Edge 패키지 정보를 파일과 함께 전송하여 등록합니다.", - security = @SecurityRequirement(name = "bearerAuth") + security = @SecurityRequirement(name = "bearerAuth") // 이 컨트롤러만 Bearer 적용 ) @PostMapping(value = "/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity addEdgePackage( + @Parameter(description = "로그인 시 발급받은 Bearer 토큰") @RequestHeader("Authorization") String bearerToken, - @RequestPart("edgePkgInfoVO") String edgePkgInfoJson, + + @Parameter(description = "Edge 패키지 등록 요청 데이터(JSON 형식)") + @RequestPart(value = "edgePkgInfoVO", required = true) EdgePkgInfoRequest edgePkgInfoRequest, + + @Parameter(description = "업로드할 패키지 파일") @RequestPart(value = "file", required = false) MultipartFile file ) { try { - ObjectMapper mapper = new ObjectMapper(); - EdgePkgInfoRequest edgePkgInfoRequest = mapper.readValue(edgePkgInfoJson, EdgePkgInfoRequest.class); - + // 서비스 호출 Object response = externalAuthService.uploadEdgePackage(bearerToken, edgePkgInfoRequest, file); return ResponseEntity.ok(response); - } catch (JsonProcessingException e) { - return ResponseEntity.badRequest().body("잘못된 JSON 형식: " + e.getMessage()); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body( + Map.of("success", false, "message", "잘못된 요청: " + e.getMessage()) + ); + } catch (Exception e) { - return ResponseEntity.internalServerError().body("파일 업로드 실패: " + e.getMessage()); + return ResponseEntity.internalServerError().body( + Map.of("success", false, "message", "파일 업로드 실패: " + e.getMessage()) + ); } } + + // DTO: 요청 public static record SigninRequest(String id, String password) { } diff --git a/src/main/java/kr/re/etri/autoflow/controllers/MlflowController.java b/src/main/java/kr/re/etri/autoflow/controllers/MlflowController.java index d7aadc3..7c9a452 100644 --- a/src/main/java/kr/re/etri/autoflow/controllers/MlflowController.java +++ b/src/main/java/kr/re/etri/autoflow/controllers/MlflowController.java @@ -23,7 +23,7 @@ public class MlflowController { public MlflowController() { this.webClient = WebClient.builder() .baseUrl("http://192.168.10.135:30128/api/2.0/mlflow") - .defaultHeaders(headers -> headers.setBasicAuth("user", "LImQa2Me37nu")) + .defaultHeaders(headers -> headers.setBasicAuth("user", "aBZ1RzEDGutI")) .build(); } diff --git a/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java b/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java index 3c88c11..d758042 100644 --- a/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java +++ b/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java @@ -15,6 +15,9 @@ import org.springframework.web.multipart.MultipartFile; import javax.net.ssl.*; import java.io.IOException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; @@ -94,23 +97,52 @@ public class ExternalAuthService { * Edge 패키지 검색 API 호출 */ public Map getEdgePackageList(String id, String token) { - // URL 구성 - String url = String.format("%s?sw_group=-1&sw_type=-1&searchType=&searchText=&pageNum=1&pageSize=10&auth_id=%s&user_id=", - edgeSearchUrl, id); + try { + // 1) 입력 검증: id에 URL/공격 문자열 포함 금지 + if (id == null || id.isBlank()) { + throw new IllegalArgumentException("잘못된 사용자 ID입니다."); + } + if (id.contains("http://") || id.contains("https://")) { + throw new IllegalArgumentException("ID에 URL을 포함할 수 없습니다."); + } - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(token); - headers.setAccept(java.util.List.of(MediaType.APPLICATION_JSON)); + // 2) 안전하게 파라미터 인코딩 및 URL 구성 (base URL은 외부 입력에서 분리) + String safeId = URLEncoder.encode(id, StandardCharsets.UTF_8); + String url = String.format("%s?sw_group=-1&sw_type=-1&searchType=&searchText=&pageNum=1&pageSize=10&auth_id=%s&user_id=", + edgeSearchUrl, safeId); - HttpEntity request = new HttpEntity<>(headers); + // 3) URI 생성 (edgeSearchUrl이 올바른 형식인지 확인) + URI uri = URI.create(url); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, request, Map.class); + // 추가 안전권장(선택) : 실제 호출되는 호스트가 설정한 base URL의 호스트와 동일한지 확인 + // (내부망 차단을 원치 않으셨으므로 private IP 검사 코드는 제외) + URI baseUri = URI.create(edgeSearchUrl); + if (!uri.getHost().equalsIgnoreCase(baseUri.getHost())) { + throw new SecurityException("요청 호스트가 허용된 서비스와 일치하지 않습니다."); + } - if (response.getStatusCode() == HttpStatus.OK) { - return response.getBody(); - } + // 4) 요청 헤더 구성 + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(token); + headers.setAccept(java.util.List.of(MediaType.APPLICATION_JSON)); + HttpEntity request = new HttpEntity<>(headers); + + // 5) 요청 수행 + ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, request, Map.class); + + if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { + return response.getBody(); + } else { + throw new RuntimeException("외부 Edge 패키지 조회 실패"); + } - throw new RuntimeException("Failed to fetch edge package list"); + } catch (IllegalArgumentException iae) { + // 입력 검증 실패는 호출자에게 명확히 알릴 수 있음 + throw iae; + } catch (Exception e) { + // 내부 상세 예외는 래핑하여 외부에 노출하지 않음 + throw new RuntimeException("외부 Edge API 호출 중 오류가 발생했습니다."); + } } public Object uploadEdgePackage(String bearerToken, EdgePkgInfoRequest edgePkgInfoRequest, MultipartFile file) throws IOException {