You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
autoflow-server-mgmt/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java

181 lines
7.4 KiB

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<String, Object> getUserInfo(String id, String password) {
// 요청 본문
Map<String, String> body = Map.of("id", id, "password", password);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers);
ResponseEntity<Map> response = restTemplate.exchange(
signinUrl, HttpMethod.POST, request, Map.class);
if (response.getStatusCode() == HttpStatus.OK) {
Map<String, Object> respBody = response.getBody();
if (respBody != null && respBody.get("data") instanceof Map dataMap) {
Map<String, Object> 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<String, Object> 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<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 패키지 조회 실패");
}
} 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<String, Object> 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<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);
ResponseEntity<Object> response = restTemplate.exchange(
edgeAddUrl,
HttpMethod.POST,
request,
Object.class
);
return response.getBody();
}
}