[MODIFY] 외부 Edge 패키지 등록 API 보안 개선, Bearer 인증 설명 추가 및 입력 데이터 검증 로직 강화, WebClient 기본 인증 변경

main
bjkim 8 months ago
parent 16759d37ad
commit d04f68adc6

@ -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) { }

@ -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();
}

@ -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<String, Object> 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<Void> request = new HttpEntity<>(headers);
// 3) URI 생성 (edgeSearchUrl이 올바른 형식인지 확인)
URI uri = URI.create(url);
ResponseEntity<Map> 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<Void> request = new HttpEntity<>(headers);
// 5) 요청 수행
ResponseEntity<Map> 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 {

Loading…
Cancel
Save