package kr.re.etri.autoflow.service; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.PostConstruct; import kr.re.etri.autoflow.payload.request.EdgePkgInfoRequest; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import org.springframework.http.client.SimpleClientHttpRequestFactory; 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; @Service public class ExternalAuthService { private RestTemplate restTemplate; @Value("${external.auth.signin-url}") private String signinUrl; @Value("${external.auth.edge-search-url}") private String edgeSearchUrl; @Value("${external.edge.add-url:https://cuuva.com:24443/api/datamanager/edge-pkg/add}") private String edgeAddUrl; @PostConstruct public void init() { this.restTemplate = createUnsafeRestTemplate(); } /** SSL 무시용 RestTemplate 생성 */ private RestTemplate createUnsafeRestTemplate() { try { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); return new RestTemplate(requestFactory); } catch (Exception e) { throw new RuntimeException("Failed to create unsafe RestTemplate", e); } } /** Bearer 토큰 발급 */ public Map getUserInfo(String id, String password) { // 요청 본문 Map body = Map.of("id", id, "password", password); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity> request = new HttpEntity<>(body, headers); ResponseEntity response = restTemplate.exchange( signinUrl, HttpMethod.POST, request, Map.class); if (response.getStatusCode() == HttpStatus.OK) { Map respBody = response.getBody(); if (respBody != null && respBody.get("data") instanceof Map dataMap) { Map result = new HashMap<>(); result.put("id", dataMap.get("id")); result.put("name", dataMap.get("name")); result.put("token", dataMap.get("token")); return result; } } throw new RuntimeException("Failed to get user info from external server"); } /** * Edge 패키지 검색 API 호출 */ public Map getEdgePackageList(String id, String token) { 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을 포함할 수 없습니다."); } // 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); // 3) URI 생성 (edgeSearchUrl이 올바른 형식인지 확인) URI uri = URI.create(url); // 추가 안전권장(선택) : 실제 호출되는 호스트가 설정한 base URL의 호스트와 동일한지 확인 // (내부망 차단을 원치 않으셨으므로 private IP 검사 코드는 제외) URI baseUri = URI.create(edgeSearchUrl); if (!uri.getHost().equalsIgnoreCase(baseUri.getHost())) { throw new SecurityException("요청 호스트가 허용된 서비스와 일치하지 않습니다."); } // 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 패키지 조회 실패"); } } catch (IllegalArgumentException iae) { // 입력 검증 실패는 호출자에게 명확히 알릴 수 있음 throw iae; } catch (Exception e) { // 내부 상세 예외는 래핑하여 외부에 노출하지 않음 throw new RuntimeException("외부 Edge API 호출 중 오류가 발생했습니다."); } } public Object uploadEdgePackage(String bearerToken, EdgePkgInfoRequest edgePkgInfoRequest, MultipartFile file) throws IOException { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); headers.set("Authorization", bearerToken.startsWith("Bearer ") ? bearerToken : "Bearer " + bearerToken); // Object를 JSON 문자열로 변환 ObjectMapper objectMapper = new ObjectMapper(); String edgePkgInfoJson = objectMapper.writeValueAsString(edgePkgInfoRequest); MultiValueMap body = new LinkedMultiValueMap<>(); body.add("edgePkgInfoVO", edgePkgInfoJson); if (file != null && !file.isEmpty()) { body.add("file", new ByteArrayResource(file.getBytes()) { @Override public String getFilename() { return file.getOriginalFilename(); } }); } HttpEntity> request = new HttpEntity<>(body, headers); ResponseEntity response = restTemplate.exchange( edgeAddUrl, HttpMethod.POST, request, Object.class ); return response.getBody(); } }