diff --git a/Deploy/bin/startup.bat b/Deploy/bin/startup.bat index adc27a8..7c802c0 100644 --- a/Deploy/bin/startup.bat +++ b/Deploy/bin/startup.bat @@ -1,8 +1,8 @@ @echo off rem Started... -rem java -jar ..\webapp\ci-1.0.0-SNAPSHOT.war +rem java -jar ..\webapp\ci-1.0.0.war rem Started background... @START /b shutdown.bat -@START /b C:\xit\spring-tool-suite-4-4.11.0.RELEASE-e4.20.0-win32.win32.x86_64.self-extracting\env-setting\Java\jdk1.8.0_121\bin\java-app-ci "-Dspring.profiles.active=prod" -jar ..\webapp\ci-1.0.0-SNAPSHOT.war \ No newline at end of file +@START /b C:\xit\spring-tool-suite-4-4.11.0.RELEASE-e4.20.0-win32.win32.x86_64.self-extracting\env-setting\Java\jdk1.8.0_121\bin\java-app-ci "-Dspring.profiles.active=prod" -jar ..\webapp\ci-1.0.0.war \ No newline at end of file diff --git a/build.gradle b/build.gradle index cb73829..24c0b24 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group = 'cokr.xit' -version = '1.0.0-SNAPSHOT' +version = '1.0.0' sourceCompatibility = '1.8' configurations { @@ -20,17 +20,49 @@ repositories { } dependencies { + /* ================================================================================== */ + /* Core */ + /* ================================================================================== */ implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' + testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-devtools' + + + /* ================================================================================== */ + /* Jsp */ + /* ================================================================================== */ implementation 'org.apache.tomcat.embed:tomcat-embed-jasper' - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' + /* ================================================================================== */ + /* HttpClient */ + /* ================================================================================== */ + implementation 'org.apache.httpcomponents:httpcore:4.4.6' + implementation 'org.apache.httpcomponents:httpclient:4.5.11' + implementation 'org.apache.commons:commons-io:1.3.2' - providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' + + + /* ================================================================================== */ + /* Springdoc */ + /* ================================================================================== */ + implementation 'org.springdoc:springdoc-openapi-ui:1.6.3' + - testImplementation 'org.springframework.boot:spring-boot-starter-test' + /* ================================================================================== */ + /* Database */ + /* ================================================================================== */ + runtimeOnly 'com.h2database:h2:1.4.199' + + + /* ================================================================================== */ + /* Lombok */ + /* ================================================================================== */ + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' /* =================================================================================== */ diff --git a/src/main/java/cokr/xit/ci/AppCiApplication.java b/src/main/java/cokr/xit/ci/AppCiApplication.java index ea04460..c0c8346 100644 --- a/src/main/java/cokr/xit/ci/AppCiApplication.java +++ b/src/main/java/cokr/xit/ci/AppCiApplication.java @@ -6,13 +6,14 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.ApplicationPidFileWriter; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import java.util.Optional; @Slf4j @ComponentScan(basePackages = "cokr.xit") @SpringBootApplication -@ServletComponentScan +//@ServletComponentScan public class AppCiApplication { public static void main(String[] args) { diff --git a/src/main/java/cokr/xit/ci/api/code/ErrCd.java b/src/main/java/cokr/xit/ci/api/code/ErrCd.java index 9906ad6..0205177 100644 --- a/src/main/java/cokr/xit/ci/api/code/ErrCd.java +++ b/src/main/java/cokr/xit/ci/api/code/ErrCd.java @@ -31,6 +31,7 @@ public enum ErrCd { , ERR603("유효하지 않은 토큰(OTT) 값") , ERR610("응답 데이터에 필수값이 없음") , ERR620("API Response Error") + , ERR699("API 기타 오류") //기타오류 , ERR999("기타 오류") , ERR901("권한 없음") diff --git a/src/main/java/cokr/xit/ci/api/domain/NiceCiSymkeyMng.java b/src/main/java/cokr/xit/ci/api/domain/NiceCiSymkeyMng.java new file mode 100644 index 0000000..8f87f5f --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/domain/NiceCiSymkeyMng.java @@ -0,0 +1,66 @@ +package cokr.xit.ci.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Getter @ToString +@Builder +//@DynamicUpdate //변경된 필드에 대해서만 update SQL문 생성 +/* + * @NoArgsConstructor, @AllArgsConstructor 추가 + * -.@Builder만 사용할 경우 queryDsl Select 시 기본생성자 오류("No default constructor for entity") 발생 + */ +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "ens_nice_ci_symkey_mng", schema = "", catalog = "") +@Schema(name = "NiceCiSymkeyMng", description = "나이스 CI 대칭키 관리") +public class NiceCiSymkeyMng { + + @Id + @GeneratedValue(strategy=GenerationType.AUTO) + private Long niceCiSymkeyMngId; //ID(PK) + + @Schema(required = true, title = "공개키(PK)", example = " ", description = "") + @Column(nullable = false, length = 1000) + private String pubkey; + + @Schema(required = true, title = "대칭키", example = " ", description = "대칭키등록은 1일 1회만 가능하며, 6개월 내 갱신(재등록) 필요") + @Column(nullable = false, length = 32) + private String symkey; + + @Schema(required = false, title = "버전", example = " ", description = "대칭키 현재 버전") + @Column(nullable = true, length = 50) + private String version; + + @Schema(required = true, title = "Initial Vector 값", example = " ", description = "CI 송수신 시 사용할 암복호화 key 값") + @Column(nullable = false, length = 16) + private String iv; + + @Schema(required = true, title = "hmac key", example = " ", description = "무결성체크 시 사용할 HMAC KEY") + @Column(nullable = false, length = 32) + private String hmacKey; + + @Schema(required = true, title = "대체키등록API 응답 Json 데이터", example = " ", description = "DataBodySymkeyResp 객체") + @Column(nullable = false, length = 1000) + private String respJsonData; + + + @Schema(required = false, title = "등록일시", example = " ", description = "") + @Column(name = "regist_dt", nullable = true) + @CreationTimestamp + private LocalDateTime registDt; + + @Schema(required = false, title = "최종 수정일시", example = " ", description = "") + @Column(name = "last_updt_dt", nullable = true) + @UpdateTimestamp + private LocalDateTime lastUpdtDt; + + + +} diff --git a/src/main/java/cokr/xit/ci/api/domain/repository/NiceCiSymkeyMngRepository.java b/src/main/java/cokr/xit/ci/api/domain/repository/NiceCiSymkeyMngRepository.java new file mode 100644 index 0000000..be78ee8 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/domain/repository/NiceCiSymkeyMngRepository.java @@ -0,0 +1,11 @@ +package cokr.xit.ci.api.domain.repository; + +import cokr.xit.ci.api.domain.NiceCiSymkeyMng; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface NiceCiSymkeyMngRepository extends JpaRepository { + + Optional findByPubkey(String pubkey); +} diff --git a/src/main/java/cokr/xit/ci/api/presentation/NiceCiController.java b/src/main/java/cokr/xit/ci/api/presentation/NiceCiController.java index 75c707e..a4a6281 100644 --- a/src/main/java/cokr/xit/ci/api/presentation/NiceCiController.java +++ b/src/main/java/cokr/xit/ci/api/presentation/NiceCiController.java @@ -22,11 +22,11 @@ public class NiceCiController { private final NiceCiService diCiService; -// @Value("${nice.ci.socket.site-code ?: }") - @Value("${nice.ci.socket.site-code}") +// @Value("${contract.nice.ci.socket.site-code ?: }") + @Value("${contract.nice.ci.socket.site-code}") private String SITE_CODE; -// @Value("${nice.ci.socket.site-pw ?: }") - @Value("${nice.ci.socket.site-pw}") +// @Value("${contract.nice.ci.socket.site-pw ?: }") + @Value("${contract.nice.ci.socket.site-pw}") private String SITE_PW; @SuppressWarnings("deprecation") diff --git a/src/main/java/cokr/xit/ci/api/service/NiceCiService.java b/src/main/java/cokr/xit/ci/api/service/NiceCiService.java index 94e7829..26dd1fa 100644 --- a/src/main/java/cokr/xit/ci/api/service/NiceCiService.java +++ b/src/main/java/cokr/xit/ci/api/service/NiceCiService.java @@ -2,9 +2,11 @@ package cokr.xit.ci.api.service; import cokr.xit.ci.api.code.ErrCd; import cokr.xit.ci.api.model.ResponseVO; +import cokr.xit.ci.api.service.support.NiceCiGenerator; import cokr.xit.ci.api.service.support.socket.Interop; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; @@ -21,6 +23,11 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class NiceCiService { + private final NiceCiGenerator niceCiGenerator; + + @Value("${contract.nice.ci.type}") + private String type; + /** * 주민번호로 CI를 취득 한다. * -.CI: 연계정보(Connecting Information) @@ -61,7 +68,11 @@ public class NiceCiService { /* ======================== * api call ======================== */ - responseVO = Interop.getCI(siteCode, sitePw, jid); + if("socket".equals(type)) { + responseVO = Interop.getCI(siteCode, sitePw, jid); + }else{ + responseVO = niceCiGenerator.getCI(jid, null); + } } catch (Exception e){ log.error(e.getMessage()); @@ -79,8 +90,8 @@ public class NiceCiService { .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> m.get("value"), (k1, k2)->k1))) .build(); } - - + + /** * sha256 암호화 @@ -106,6 +117,6 @@ public class NiceCiService { return sbuf.toString(); } - - + + } diff --git a/src/main/java/cokr/xit/ci/api/service/support/NiceCiGenerator.java b/src/main/java/cokr/xit/ci/api/service/support/NiceCiGenerator.java new file mode 100644 index 0000000..6aeb0d9 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/NiceCiGenerator.java @@ -0,0 +1,130 @@ +package cokr.xit.ci.api.service.support; + +import cokr.xit.ci.api.code.ErrCd; +import cokr.xit.ci.api.domain.NiceCiSymkeyMng; +import cokr.xit.ci.api.domain.repository.NiceCiSymkeyMngRepository; +import cokr.xit.ci.api.model.ResponseVO; +import cokr.xit.ci.api.service.support.rest.NiceCiApiExecutor; +import cokr.xit.ci.api.service.support.rest.code.NiceCiApiCd; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespVO; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyCiResp; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyGenerateTokenResp; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyPubkeyResp; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodySymkeyResp; +import cokr.xit.ci.api.service.support.rest.utils.SymmetricKey; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class NiceCiGenerator { + + private final NiceCiSymkeyMngRepository niceCiSymkeyMngRepository; + + + @Value("${contract.nice.ci.rest.host}") + private String HOST; + @Value("${contract.nice.ci.rest.client-id}") + private String CLIENT_ID; + @Value("${contract.nice.ci.rest.client-secret}") + private String CLIENT_SECRET; + @Value("${contract.nice.ci.rest.api.generate-token}") + private String API_GENERATE_TOKEN; + @Value("${contract.nice.ci.rest.api.revoke-token}") + private String API_REVOKE_TOKEN; + @Value("${contract.nice.ci.rest.api.publickey}") + private String API_PUBLICKEY; + @Value("${contract.nice.ci.rest.api.symmetrickey}") + private String API_SYMMETRICKEY; + @Value("${contract.nice.ci.rest.api.ci}") + private String API_CI; + + public ResponseVO getCI(String jid, String clientIp) { + + NiceCiApiExecutor executor = NiceCiApiExecutor.builder() + .HOST(this.HOST) + .CLIENT_ID(this.CLIENT_ID) + .CLIENT_SECRET(this.CLIENT_SECRET) + .API_GENERATE_TOKEN(this.API_GENERATE_TOKEN) + .API_REVOKE_TOKEN(this.API_REVOKE_TOKEN) + .API_PUBLICKEY(this.API_PUBLICKEY) + .API_SYMMETRICKEY(this.API_SYMMETRICKEY) + .API_CI(this.API_CI) + .build(); + try { + + /* ========================================================================== + * 1. 토큰 요청 + ========================================================================== */ + NiceCiRespVO tokenResponseVO = executor.token(); + if (!NiceCiApiCd.OK.equals(tokenResponseVO.getErrCode())) + return ResponseVO.builder().errCode(ErrCd.ERR600).errMsg(tokenResponseVO.getErrCode().getCode() + " " + tokenResponseVO.getErrMsg()).build(); + + /* ========================================================================== + * 2. 공개키 요청 + ========================================================================== */ + NiceCiRespVO pubkeyResponseVO = executor.pubkey(); + if (!NiceCiApiCd.OK.equals(pubkeyResponseVO.getErrCode())) + return ResponseVO.builder().errCode(ErrCd.ERR600).errMsg(pubkeyResponseVO.getErrCode().getCode() + " " + pubkeyResponseVO.getErrMsg()).build(); + + /* ========================================================================== + * 3. 대칭키 등록 요청 + * -. 대칭키는 1일 1회만 등록이 가능하며, 1일 2회 이상 등록요청 시 "0099 기타오류" 가 발생 하므로 + * -. 등록요청에 성공한 대칭키는 DB에 저장하여, 서버 재기동 시에도 휘발되지 않도록 한다. + ========================================================================== */ +// NiceCiRespVO symkeyResponseVO = executor.symkey(); +// if (!NiceCiApiCd.OK.equals(symkeyResponseVO.getErrCode())) +// return ResponseVO.builder().errCode(ErrCd.ERR600).errMsg(symkeyResponseVO.getErrCode().getCode() + " " + symkeyResponseVO.getErrMsg()).build(); + ObjectMapper mapper = new ObjectMapper(); + DataBodySymkeyResp dataBodySymkeyResp = null; + if (SymmetricKey.isValidStat()) { + dataBodySymkeyResp = SymmetricKey.getInstance().getData(); + } else { //대칭키 상태가 유효하지 않으면... + //현재 대칭키 조회(by 공개키) + Optional niceCiSymkeyMng = niceCiSymkeyMngRepository.findByPubkey(pubkeyResponseVO.getResultInfo().getPublicKey()); + + if (niceCiSymkeyMng.isPresent()) { + dataBodySymkeyResp = mapper.readValue(niceCiSymkeyMng.get().getRespJsonData(), DataBodySymkeyResp.class); + + //대칭키 싱글톤 객체 초기화 + SymmetricKey.getInstance(dataBodySymkeyResp); + + } else { + //3. 대칭키 등록 요청 + NiceCiRespVO symkeyResponseVO = executor.symkey(); + if (!NiceCiApiCd.OK.equals(symkeyResponseVO.getErrCode())) + return ResponseVO.builder().errCode(ErrCd.ERR600).errMsg(symkeyResponseVO.getErrCode().getCode() + " " + symkeyResponseVO.getErrMsg()).build(); + dataBodySymkeyResp = symkeyResponseVO.getResultInfo(); + + //대칭키 정보 DB 등록 + niceCiSymkeyMngRepository.save(NiceCiSymkeyMng.builder() + .pubkey(pubkeyResponseVO.getResultInfo().getPublicKey()) + .symkey(dataBodySymkeyResp.getKey()) + .version(dataBodySymkeyResp.getSymkeyStatInfo().getCurSymkeyVersion()) + .iv(dataBodySymkeyResp.getIv()) + .hmacKey(dataBodySymkeyResp.getHmacKey()) + .respJsonData(mapper.writeValueAsString(dataBodySymkeyResp)) + .build()); + } + } + + /* ========================================================================== + * 4. 아이핀 CI 요청 + ========================================================================== */ + NiceCiRespVO ciResponseVO = executor.ci(jid, clientIp); + if (!NiceCiApiCd.OK.equals(pubkeyResponseVO.getErrCode())) + return ResponseVO.builder().errCode(ErrCd.ERR600).errMsg(ciResponseVO.getErrCode().getCode() + " " + ciResponseVO.getErrMsg()).build(); + + + return ResponseVO.builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(ciResponseVO.getResultInfo().getDecEncData().getCi1()).build(); + } catch (Exception e) { + return ResponseVO.builder().errCode(ErrCd.ERR699).errMsg(e.getMessage()).build(); + } + } +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/MapDeserailizer.java b/src/main/java/cokr/xit/ci/api/service/support/rest/MapDeserailizer.java deleted file mode 100644 index 2af735e..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/MapDeserailizer.java +++ /dev/null @@ -1,57 +0,0 @@ -package cokr.xit.ci.api.service.support.rest; - -import com.google.gson.*; - -import java.lang.reflect.Type; -import java.util.*; - -public class MapDeserailizer implements JsonDeserializer> { - - @Override @SuppressWarnings("unchecked") - public Map deserialize(JsonElement json, Type typeOfT, - JsonDeserializationContext context) throws JsonParseException { - return (Map) read(json); - } - - public Object read(JsonElement in) { - if(in.isJsonArray()){ //JsonArray 이면... - //JsonArray인 경우 - List list = new ArrayList(); - JsonArray arr = in.getAsJsonArray(); - for (JsonElement anArr : arr) { - //JsonPrimitive 나올 떄까지 for문 - list.add(read(anArr)); - } - return list; - }else if(in.isJsonObject()){ //JsonObject 이면... - Map map = new HashMap(); - JsonObject obj = in.getAsJsonObject(); - Set> entitySet = obj.entrySet(); - for(Map.Entry entry: entitySet){ - //JsonPrimitive 나올 떄까지 for문 - map.put(entry.getKey(), read(entry.getValue())); - } - return map; - }else if( in.isJsonPrimitive()){ //원시 Json 데이터 이면.. - JsonPrimitive prim = in.getAsJsonPrimitive(); - if(prim.isBoolean()){ - //true , fales 형으로 - return prim.getAsBoolean(); - }else if(prim.isString()){ - //String으로 - return prim.getAsString(); - }else if(prim.isNumber()){ - Number num = prim.getAsNumber(); - //Math.ceil 소수점을 올림한다. - if(Math.ceil(num.doubleValue()) == num.longValue()) - //소수점 버림, Int형으로. - return num.longValue(); - else{ - //소수점 안버림, Double 형으로 - return num.doubleValue(); - } - } - } - return null; - } -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApi.java b/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApi.java deleted file mode 100644 index 82bc33d..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApi.java +++ /dev/null @@ -1,567 +0,0 @@ -package cokr.xit.ci.api.service.support.rest; - -import cokr.xit.ci.api.code.ErrCd; -import cokr.xit.ci.api.model.ResponseVO; -import cokr.xit.ci.api.service.support.rest.code.NiceCiApiCd; -import cokr.xit.ci.api.service.support.rest.model.*; -import cokr.xit.ci.core.utils.DateUtil; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import lombok.extern.slf4j.Slf4j; -import org.apache.logging.log4j.util.Base64Util; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import java.net.URI; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -@Slf4j -@Component -//public class NiceCiApi implements NiceCiApiStruct { -public class NiceCiApi { - - private final String PRODUCT_ID = "2101466024"; - @Value("${nice.ci.api.host}") - private String HOST; - @Value("${nice.ci.api.generate-token}") - private String API_GENERATE_TOKEN; - @Value("${nice.ci.api.revoke-token}") - private String API_REVOKE_TOKEN; - @Value("${nice.ci.api.publickey}") - private String API_PUBLICKEY; - @Value("${nice.ci.api.symmetrickey}") - private String API_SYMMETRICKEY; - @Value("${nice.ci.api.ci}") - private String API_CI; - - - private Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).disableHtmlEscaping().create(); - - //@Override - public ResponseVO generateToken(String clientId, String clientSecret) { - ErrCd errCd = ErrCd.ERR999; - String errMsg = ErrCd.ERR999.getCodeNm(); - try { - /* ============================================================================== - * 유효성 확인 - ============================================================================== */ - if (StringUtils.isEmpty(clientId)) { - errCd = ErrCd.ERR401; - errMsg = "클라이언트ID는 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(clientSecret)) { - errCd = ErrCd.ERR401; - errMsg = "클라이언트비밀번호는 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - - - /* ============================================================================== - * HEADER 설정 - ============================================================================== */ - String authorizationToken = Base64Util.encode(String.format("%s:%s", clientId, clientSecret)); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8"))); - headers.set("Authorization", String.format("Basic %s", authorizationToken)); - - - /* ============================================================================== - * URL 설정 - ============================================================================== */ - StringBuilder url = new StringBuilder(); - url.append(this.HOST) - .append(API_GENERATE_TOKEN); - Map mParam = new HashMap<>(); - mParam.put("grant_type", "client_credentials"); - mParam.put("scope", "default"); - String jsonStr = gson.toJson(mParam); - - - /* ============================================================================== - * API 호출 - ============================================================================== */ - ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), jsonStr, headers); - log.info("=================================================================================="); - log.info("==== 토큰 생성 ===="); - log.info("[Headers]: " + resp.getHeaders().toString()); - log.info("[Body]: " + resp.getBody()); - log.info("=================================================================================="); - - - - /* ============================================================================== - * 결과 확인 - ============================================================================== */ - if (!"1200".equals(resp.getHeaders().get("GW_RSLT_CD").get(0))) { - errCd = ErrCd.ERR620; - errMsg = String.format("[%s] %s.%s", resp.getHeaders().get("GW_RSLT_CD").get(0), resp.getHeaders().get("GW_RSLT_MSG").get(0), resp.getBody()); - throw new RuntimeException("응답 오류(GW_RSLT_CD) 입니다."); - } -// Map resultInfo = gson.fromJson(resp.getBody(), Map.class); -// String accessToken = (String) resultInfo.get("access_token"); //사용자 엑세스 토큰 값(모든 API 요청시 헤더에 access_token을 포함하여 전송) -// Long expiresIn = (Long) resultInfo.get("expires_in"); //access token 만료까지 남은시간(초) -// String tokenType = (String) resultInfo.get("token_type"); //bearer로 고정 -// String scope = (String) resultInfo.get("scope"); //요청한 scope값(기본 default) -// String expiredDt = DateUtil.absTimeSecToDate(expiresIn, "yyyyMMddHHmmss"); -// log.info("[엑세스토큰]: " + accessToken); -// log.info("[엑세스토큰 만료시간]: " + expiredDt); - GenerateTokenRespDTO resultInfo = gson.fromJson(resp.getBody(), GenerateTokenRespDTO.class); - resultInfo.setExpiredDt(DateUtil.absTimeSecToDate(resultInfo.getExpiresIn(), "yyyyMMddHHmmss")); - log.info("[응답데이터]: " + resultInfo.toString()); - - - return ResponseVO.builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build(); - } catch (Exception e) { - return ResponseVO.builder().errCode(errCd).errMsg(errMsg + " " + errMsg).build(); - } - } - - - //@Override - public ResponseVO revokeToken(String accessToken, String clientId) { - ErrCd errCd = ErrCd.ERR999; - String errMsg = ErrCd.ERR999.getCodeNm(); - try { - /* ============================================================================== - * 유효성 확인 - ============================================================================== */ - if (StringUtils.isEmpty(accessToken)) { - errCd = ErrCd.ERR401; - errMsg = "엑세스토큰은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(clientId)) { - errCd = ErrCd.ERR401; - errMsg = "클라이언트ID는 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - - - /* ============================================================================== - * HEADER 설정 - ============================================================================== */ - String bearerToken = Base64Util.encode(String.format("%s:%s:%s", accessToken, (new Date().getTime() / 1000), clientId)); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8"))); - headers.set("Authorization", String.format("bearer %s", bearerToken)); - - - /* ============================================================================== - * URL 설정 - ============================================================================== */ - StringBuilder url = new StringBuilder(); - url.append(this.HOST) - .append(API_REVOKE_TOKEN); - - - /* ============================================================================== - * API 호출 - ============================================================================== */ - ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), null, headers); - log.info("=================================================================================="); - log.info("==== 토큰 폐기 ===="); - log.info("[Headers]: " + resp.getHeaders().toString()); - log.info("[Body]: " + resp.getBody()); - log.info("=================================================================================="); - - - - /* ============================================================================== - * 결과 확인 - ============================================================================== */ - if (!"1200".equals(resp.getHeaders().get("GW_RSLT_CD").get(0))) { - errCd = ErrCd.ERR620; - errMsg = String.format("[%s] %s.%s", resp.getHeaders().get("GW_RSLT_CD").get(0), resp.getHeaders().get("GW_RSLT_MSG").get(0), resp.getBody()); - throw new RuntimeException("응답 오류 입니다. "); - } - RevokeTokenRespDTO resultInfo = gson.fromJson(resp.getBody(), RevokeTokenRespDTO.class); - log.info("[응답데이터]: " + resultInfo.toString()); - - - return ResponseVO.builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build(); - } catch (Exception e) { - return ResponseVO.builder().errCode(errCd).errMsg(e.getMessage() + " " + errMsg).build(); - } - } - - - //@Override - public ResponseVO generatePublickey(String accessToken, String clientId, String pubkeyVersion, String symkeyRegInfo) { - ErrCd errCd = ErrCd.ERR999; - String errMsg = ErrCd.ERR999.getCodeNm(); - try { - /* ============================================================================== - * 유효성 확인 - ============================================================================== */ - if (StringUtils.isEmpty(accessToken)) { - errCd = ErrCd.ERR401; - errMsg = "엑세스토큰은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(clientId)) { - errCd = ErrCd.ERR401; - errMsg = "클라이언트ID는 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(pubkeyVersion)) { - errCd = ErrCd.ERR401; - errMsg = "공개키버전은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(symkeyRegInfo)) { - errCd = ErrCd.ERR401; - errMsg = "공개키암호화 값(대칭키를 공개키로 암호화)은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - - - /* ============================================================================== - * HEADER 설정 - ============================================================================== */ - String bearerToken = Base64Util.encode(String.format("%s:%s:%s", accessToken, (new Date().getTime() / 1000), clientId)); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8"))); - headers.set("Authorization", String.format("bearer %s", bearerToken)); - headers.set("client_id", clientId); - headers.set("productID", PRODUCT_ID); - headers.set("CNTY_CD", "ko"); //이용언어: ko, en, cn... -// headers.set("TRAN_ID", ); //API통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호 - - - /* ============================================================================== - * URL 설정 - ============================================================================== */ - StringBuilder url = new StringBuilder(); - url.append(this.HOST) - .append(API_PUBLICKEY); - Map mParam = new HashMap<>(); - mParam.put("req_dtim", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); - String jsonStr = gson.toJson(mParam); - - - /* ============================================================================== - * API 호출 - ============================================================================== */ - ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), jsonStr, headers); - log.info("=================================================================================="); - log.info("==== 공개키 요청 ===="); - log.info("[Headers]: " + resp.getHeaders().toString()); - log.info("[Body]: " + resp.getBody()); - log.info("=================================================================================="); - - - - /* ============================================================================== - * 결과 확인 - ============================================================================== */ - if (!"1200".equals(resp.getHeaders().get("GW_RSLT_CD").get(0))) { - errCd = ErrCd.ERR620; - errMsg = String.format("[%s] %s.%s", resp.getHeaders().get("GW_RSLT_CD").get(0), resp.getHeaders().get("GW_RSLT_MSG").get(0), resp.getBody()); - throw new RuntimeException("응답 오류(GW_RSLT_CD) 입니다."); - } - PublickeyRespDTO resultInfo = gson.fromJson(resp.getBody(), PublickeyRespDTO.class); - log.info("[응답데이터]: " + resultInfo.toString()); - if (!"P000".equals(resultInfo.getRspCd())) { - errCd = ErrCd.ERR601; - errMsg = String.format("[%s]%s", resultInfo.getRspCd(), NiceCiApiCd.valueOfEnum(resultInfo.getRspCd()).getCodeNm()); - throw new RuntimeException("응답코드(rsp_cd) 오류."); - } - if (!"0000".equals(resultInfo.getResultCd())) { - errCd = ErrCd.ERR601; - errMsg = String.format("[%s]%s", resultInfo.getResultCd(), NiceCiApiCd.valueOfEnum("DRSLT_" + resultInfo.getResultCd()).getCodeNm()); - throw new RuntimeException("상세결과코드(result_cd) 오류."); - } - - - return ResponseVO.builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build(); - } catch (Exception e) { - return ResponseVO.builder().errCode(errCd).errMsg(e.getMessage() + " " + errMsg).build(); - } - } - - - //@Override - public ResponseVO generateSymmetrickey(String accessToken, String clientId, String pubkeyVersion, String symkeyRegInfo) { - ErrCd errCd = ErrCd.ERR999; - String errMsg = ErrCd.ERR999.getCodeNm(); - try { - /* ============================================================================== - * 유효성 확인 - ============================================================================== */ - if (StringUtils.isEmpty(accessToken)) { - errCd = ErrCd.ERR401; - errMsg = "엑세스토큰은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(clientId)) { - errCd = ErrCd.ERR401; - errMsg = "클라이언트ID는 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(pubkeyVersion)) { - errCd = ErrCd.ERR401; - errMsg = "공개키버전은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(symkeyRegInfo)) { - errCd = ErrCd.ERR401; - errMsg = "JSON암호화값은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - - - /* ============================================================================== - * HEADER 설정 - ============================================================================== */ - String bearerToken = Base64Util.encode(String.format("%s:%s:%s", accessToken, (new Date().getTime() / 1000), clientId)); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8"))); - headers.set("Authorization", String.format("bearer %s", bearerToken)); - headers.set("client_id", clientId); - headers.set("productID", PRODUCT_ID); - headers.set("CNTY_CD", "ko"); //이용언어: ko, en, cn... -// headers.set("TRAN_ID", ); //API통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호 - - - /* ============================================================================== - * URL 설정 - ============================================================================== */ - StringBuilder url = new StringBuilder(); - url.append(this.HOST) - .append(API_PUBLICKEY); - Map mParam = new HashMap<>(); - mParam.put("pubkey_version", pubkeyVersion); - mParam.put("symkey_reg_info", symkeyRegInfo); - String jsonStr = gson.toJson(mParam); - - - /* ============================================================================== - * API 호출 - ============================================================================== */ - ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), jsonStr, headers); - log.info("=================================================================================="); - log.info("==== 대칭키 요청 ===="); - log.info("[Headers]: " + resp.getHeaders().toString()); - log.info("[Body]: " + resp.getBody()); - log.info("=================================================================================="); - - - - /* ============================================================================== - * 결과 확인 - ============================================================================== */ - if (!"1200".equals(resp.getHeaders().get("GW_RSLT_CD").get(0))) { - errCd = ErrCd.ERR620; - errMsg = String.format("[%s] %s.%s", resp.getHeaders().get("GW_RSLT_CD").get(0), resp.getHeaders().get("GW_RSLT_MSG").get(0), resp.getBody()); - throw new RuntimeException("API 응답 오류 입니다."); - } - SymmetrickeyRespDTO resultInfo = gson.fromJson(resp.getBody(), SymmetrickeyRespDTO.class); - log.info("[응답데이터]: " + resultInfo.toString()); - if (!"P000".equals(resultInfo.getRspCd())) { - errCd = ErrCd.ERR601; - errMsg = String.format("[%s]%s", resultInfo.getRspCd(), NiceCiApiCd.valueOfEnum(resultInfo.getRspCd()).getCodeNm()); - throw new RuntimeException("응답코드(rsp_cd) 오류."); - } - if (!"0000".equals(resultInfo.getResultCd())) { - errCd = ErrCd.ERR601; - errMsg = String.format("[%s]%s", resultInfo.getResultCd(), NiceCiApiCd.valueOfEnum("DRSLT_" + resultInfo.getResultCd()).getCodeNm()); - throw new RuntimeException("상세결과코드(result_cd) 오류."); - } - - - return ResponseVO.builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build(); - } catch (Exception e) { - return ResponseVO.builder().errCode(errCd).errMsg(e.getMessage() + " " + errMsg).build(); - } - } - - - //@Override - public ResponseVO getCi(String clientId, String clientSecret, String symkeyVersion, String encData, String integrityValue) { - ErrCd errCd = ErrCd.ERR999; - String errMsg = ErrCd.ERR999.getCodeNm(); - try { - /* ============================================================================== - * 유효성 확인 - ============================================================================== */ - if (StringUtils.isEmpty(clientId)) { - errCd = ErrCd.ERR401; - errMsg = "클라이언트ID는 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(clientSecret)) { - errCd = ErrCd.ERR401; - errMsg = "클라이언트비밀번호는 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(symkeyVersion)) { - errCd = ErrCd.ERR401; - errMsg = "대칭키 버전 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(encData)) { - errCd = ErrCd.ERR401; - errMsg = "JSON암호화 값은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - if (StringUtils.isEmpty(integrityValue)) { - errCd = ErrCd.ERR401; - errMsg = "무결성 체크 값은 필수 입력값 입니다."; - throw new RuntimeException("유효성 검증 실패."); - } - - - /* ============================================================================== - * HEADER 설정 - ============================================================================== */ -// String authorizationToken = Base64.getEncoder().encodeToString(String.format("%s:%s", clientId, clientSecret).getBytes(StandardCharsets.UTF_8)); - String authorizationToken = Base64Util.encode(String.format("%s:%s", clientId, clientSecret)); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8"))); - headers.set("Authorization", String.format("Basic %s", authorizationToken)); - headers.set("productID", PRODUCT_ID); - headers.set("CNTY_CD", "ko"); //이용언어: ko, en, cn... -// headers.set("TRAN_ID", ); //API통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호 - - - /* ============================================================================== - * URL 설정 - ============================================================================== */ - StringBuilder url = new StringBuilder(); - url.append(this.HOST) - .append(API_CI); - Map mParam = new HashMap<>(); - mParam.put("symkey_version", symkeyVersion); - mParam.put("enc_data", encData); - mParam.put("integrity_value", integrityValue); - String jsonStr = gson.toJson(mParam); - - - /* ============================================================================== - * API 호출 - ============================================================================== */ - ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), jsonStr, headers); - log.info("=================================================================================="); - log.info("==== CI조회 ===="); - log.info("[Headers]: " + resp.getHeaders().toString()); - log.info("[Body]: " + resp.getBody()); - log.info("=================================================================================="); - - - - /* ============================================================================== - * 결과 확인 - ============================================================================== */ - if (!"1200".equals(resp.getHeaders().get("GW_RSLT_CD").get(0))) { - errCd = ErrCd.ERR620; - errMsg = String.format("[%s] %s.%s", resp.getHeaders().get("GW_RSLT_CD").get(0), resp.getHeaders().get("GW_RSLT_MSG").get(0), resp.getBody()); - throw new RuntimeException("응답 오류(GW_RSLT_CD) 입니다."); - } - CiRespDTO resultInfo = gson.fromJson(resp.getBody(), CiRespDTO.class); - log.info("[응답데이터]: " + resultInfo.toString()); - - - return ResponseVO.builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build(); - } catch (Exception e) { - e.printStackTrace(); - return ResponseVO.builder().errCode(errCd).errMsg(e.getMessage() + " " + errMsg).build(); - } - - - } - - - /** - *
메소드 설명: API 호출
-     * 
- * - * @param method - * @param url - * @param body - * @param headers - * @return ResponseEntity 요청처리 후 응답객체 - * @author: 박민규 - */ - private ResponseEntity callApi(HttpMethod method, String url, String body, HttpHeaders headers) { - log.debug("param ======================="); - log.debug(body); - - - ResponseEntity responseEntity = null; - try { - //uri 및 entity(param) 설정 - HttpEntity entity = null; - UriComponents uri = null; - switch (method) { - case GET: - entity = new HttpEntity<>(headers); - uri = UriComponentsBuilder - .fromHttpUrl(String.format("%s?%s", url, body == null ? "" : body)) -// .encode(StandardCharsets.UTF_8) //"%"기호가 "%25"로 인코딩 발생하여 주석처리 함. - .build(false); - break; - case POST: - entity = new HttpEntity<>(body, headers); - uri = UriComponentsBuilder - .fromHttpUrl(url) - .encode(StandardCharsets.UTF_8) - .build(); - break; - - default: - break; - } - - - //api 호출 - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); - factory.setConnectTimeout(3000); //커넥션타임아웃 설정 3초 - factory.setReadTimeout(3000);//타임아웃 설정 3초 - RestTemplate restTemplate = new RestTemplate(factory); - System.out.println(" url => " + uri.toString()); - System.out.println(" method => " + method); - System.out.println(" headers => " + entity.getHeaders().toString()); - System.out.println(" body => " + entity.getBody()); - responseEntity = restTemplate.exchange(URI.create(uri.toString()), method, entity, String.class); //이 한줄의 코드로 API를 호출해 String타입으로 전달 받는다. - - /* - * HttpStatus 정보 확인 방법 - * -.코드: responseEntity.getStatusCodeValue() - * -.메시지: responseEntity.getStatusCode() - */ - - } catch (HttpServerErrorException e) { - responseEntity = new ResponseEntity(e.getResponseBodyAsString(), e.getStatusCode()); - log.error(String.format("call API 서버오류[url =>%s param => %s error => %s]", url, body, e.getMessage())); - } catch (HttpClientErrorException e) { - responseEntity = new ResponseEntity(e.getResponseBodyAsString(), e.getStatusCode()); - log.error(String.format("call API 클라이언트오류[url =>%s param => %s error => %s]", url, body, e.getMessage())); - } catch (RestClientException e) { //timeout 발생 또는 기타 오류... - responseEntity = new ResponseEntity(e.getMessage(), HttpStatus.REQUEST_TIMEOUT); - log.error(String.format("RestAPI 호출 오류[url =>%s param => %s error => %s]", url, body, e.getMessage())); - } catch (Exception e) { - responseEntity = new ResponseEntity(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); - log.error(String.format("call API 기타오류[url =>%s param => %s error => %s]", url, body, e.getMessage())); - } - - //결과 응답 - return responseEntity; - } - -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApiExecutor.java b/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApiExecutor.java new file mode 100644 index 0000000..cdb991b --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApiExecutor.java @@ -0,0 +1,115 @@ +package cokr.xit.ci.api.service.support.rest; + +import cokr.xit.ci.api.service.support.rest.api.CiRequest; +import cokr.xit.ci.api.service.support.rest.api.PubkeyRequest; +import cokr.xit.ci.api.service.support.rest.api.SymkeyRegist; +import cokr.xit.ci.api.service.support.rest.api.TokenGenerate; +import cokr.xit.ci.api.service.support.rest.code.NiceCiApiCd; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespVO; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyCiResp; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyGenerateTokenResp; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyPubkeyResp; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodySymkeyResp; +import cokr.xit.ci.api.service.support.rest.utils.PublicKey; +import cokr.xit.ci.api.service.support.rest.utils.SymmetricKey; +import cokr.xit.ci.api.service.support.rest.utils.Token; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +@Builder +public class NiceCiApiExecutor { + private final String HOST; + private final String CLIENT_ID; + private final String CLIENT_SECRET; + private final String API_GENERATE_TOKEN; + private final String API_REVOKE_TOKEN; + private final String API_PUBLICKEY; + private final String API_SYMMETRICKEY; + private final String API_CI; + + + + public NiceCiRespVO token() throws Exception { + NiceCiRespVO result = null; + if (Token.isValidStat()) { + result = NiceCiRespVO.okBuilder() + .resultInfo(Token.getInstance().getData()) + .build(); + } else { + result = TokenGenerate.builder() + .HOST(this.HOST) + .API_URL_GENERATE_TOKEN(this.API_GENERATE_TOKEN) + .API_URL_REVOKE_TOKEN(this.API_REVOKE_TOKEN) + .build() + .execute(CLIENT_ID, CLIENT_SECRET); + // 토큰정보 싱글톤 초기화 + if (NiceCiApiCd.OK.equals(result.getErrCode())) + Token.getInstance(result.getResultInfo()); + } + + + return result; + } + + + + public NiceCiRespVO pubkey() throws Exception { + NiceCiRespVO result = null; + if (PublicKey.isValidStat()) { + result = NiceCiRespVO.okBuilder() + .resultInfo(PublicKey.getInstance().getData()) + .build(); + } else { + result = PubkeyRequest.builder() + .HOST(this.HOST) + .API_URL(this.API_PUBLICKEY) + .build() + .execute(this.CLIENT_ID); + // 공개키 싱글톤 초기화 + if (NiceCiApiCd.OK.equals(result.getErrCode())) + PublicKey.getInstance(result.getResultInfo()); + } + + return result; + } + + + + public NiceCiRespVO symkey() throws Exception { + NiceCiRespVO result = null; + if(SymmetricKey.isValidStat()){ + result = NiceCiRespVO.okBuilder() + .resultInfo(SymmetricKey.getInstance().getData()) + .build(); + }else{ + result = SymkeyRegist.builder() + .HOST(this.HOST) + .API_URL(this.API_SYMMETRICKEY) + .build() + .execute(this.CLIENT_ID); + // 대칭키 싱글톤 초기화 + if(NiceCiApiCd.OK.equals(result.getErrCode())) + SymmetricKey.getInstance(result.getResultInfo()); + } + + + return result; + } + + + public NiceCiRespVO ci(String jid, String clientIp) throws Exception { + + NiceCiRespVO result = CiRequest.builder() + .HOST(this.HOST) + .API_URL(this.API_CI) + .build() + .execute(this.CLIENT_ID, this.CLIENT_SECRET, jid, clientIp); + + return result; + } + + +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApiStruct.java b/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApiStruct.java deleted file mode 100644 index 12d1471..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/NiceCiApiStruct.java +++ /dev/null @@ -1,35 +0,0 @@ -package cokr.xit.ci.api.service.support.rest; - -import cokr.xit.ci.api.model.ResponseVO; - -public interface NiceCiApiStruct { - - /** - * 1. 토큰발급 API - */ - ResponseVO generateToken(); - - /** - * 2. 토큰폐기 API - */ - ResponseVO revokeToken(); - - /** - * 공개키요청 API - * -.대칭키 암호화를 위한 공개키 요청 - */ - ResponseVO generatePublickey(); - - /** - * 대칭키요청 API - * -.데이터 암호화를 위한 대칭키 요청 - */ - ResponseVO generateSymmetrickey(); - - /** - * CI 조회 - */ - ResponseVO getCi(); - - -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/api/CiRequest.java b/src/main/java/cokr/xit/ci/api/service/support/rest/api/CiRequest.java new file mode 100644 index 0000000..32e41b5 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/api/CiRequest.java @@ -0,0 +1,273 @@ +package cokr.xit.ci.api.service.support.rest.api; + +import cokr.xit.ci.api.service.support.rest.code.NiceCiApiCd; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespDTO; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespVO; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyCiResp; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodySymkeyResp; +import cokr.xit.ci.api.service.support.rest.model.conf.EncData; +import cokr.xit.ci.api.service.support.rest.utils.SymmetricKey; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.experimental.SuperBuilder; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Base64Util; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Base64Utils; +import org.springframework.util.StringUtils; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.Charset; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@SuperBuilder +public class CiRequest extends NiceCiApiAbstract { + + protected final String HOST; + protected final String API_URL; + + + public NiceCiRespVO execute(String clientId, String clientSecret, String jumin, String clientIp) throws Exception { + /* ============================================================================== + * 유효성 확인 + ============================================================================== */ + if (StringUtils.isEmpty(clientId)) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(clientSecret)) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트비밀번호는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getKey())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("대칭키는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getSymkeyStatInfo().getCurSymkeyVersion())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("대칭키 버전은 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getSiteCode())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("사이트코드는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getRequestNo())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("신청번호는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getIv())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("IV는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getHmacKey())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("HmacKey는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(jumin)) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("주민번호는 필수 입력값 입니다.").build(); + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + NiceCiRespDTO responseDTO = this.getCi(clientId, clientSecret, SymmetricKey.getInstance().getData(), jumin, clientIp); + + + /* ============================================================================== + * 결과 반환 + ============================================================================== */ + if (!"1200".equals(responseDTO.getDataHeader().getGW_RSLT_CD())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("HEAD_" + responseDTO.getDataHeader().getGW_RSLT_CD())) + .errMsg(String.format("[%s]: %s", responseDTO.getDataHeader().getGW_RSLT_CD(), responseDTO.getDataHeader().getGW_RSLT_MSG())) + .build(); + if (!"P000".equals(responseDTO.getDataBody().getRspCd())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum(responseDTO.getDataBody().getRspCd())) + .errMsg(String.format("[%s]: %s. %s", responseDTO.getDataBody().getRspCd(), NiceCiApiCd.valueOfEnum(responseDTO.getDataBody().getRspCd()).getCodeNm(), responseDTO.getDataBody().getResMsg())) + .build(); + if (!"0000".equals(responseDTO.getDataBody().getResultCd())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("CI_" + responseDTO.getDataBody().getResultCd())) + .errMsg(String.format("[%s]: %s", responseDTO.getDataBody().getResultCd(), NiceCiApiCd.valueOfEnum("CI_" + responseDTO.getDataBody().getResultCd()).getCodeNm())) + .build(); + + + /* ============================================================================== + * 무결성 확인 + ============================================================================== */ + //enc_data와 intergrity_value 비교 + if (!responseDTO.getDataBody().getIntegrityValue().equals(createIntegrityValue(responseDTO.getDataBody().getEncData(), SymmetricKey.getInstance().getData().getHmacKey()))) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.FAIL) + .errMsg("응답데이터 무결성 체크 오류") + .build(); + /* ============================================================================== + * 데이터 복호화 + ============================================================================== */ + String decEncDataStr = this.decEncData(responseDTO.getDataBody().getEncData(), SymmetricKey.getInstance().getData().getKey(), SymmetricKey.getInstance().getData().getIv()); + responseDTO.getDataBody().setDecEncData(mapper.readValue(decEncDataStr, EncData.class)); + + + return NiceCiRespVO.okBuilder().resultInfo(responseDTO.getDataBody()).build(); + + } + + + protected NiceCiRespDTO getCi(String clientId, String clientSecret, DataBodySymkeyResp symkeyResp, String jumin, String clientIp) throws Exception { + try { + + + /* ============================================================================== + * HEADER 설정 + ============================================================================== */ +// String authorizationToken = Base64.getEncoder().encodeToString(String.format("%s:%s", clientId, clientSecret).getBytes(StandardCharsets.UTF_8)); + String authorizationToken = Base64Util.encode(String.format("%s:%s", clientId, clientSecret)); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8"))); + headers.set("Authorization", String.format("Basic %s", authorizationToken)); + headers.set("productID", PRODUCT_ID); + + + /* ============================================================================== + * URL 설정 + ============================================================================== */ + StringBuilder url = new StringBuilder(); + url.append(this.HOST) + .append(this.API_URL); + final String strEncData = this.createEncData(symkeyResp.getSiteCode(), jumin, symkeyResp.getRequestNo(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")), clientIp); + final String encData = this.encEncData(strEncData, symkeyResp.getKey(), symkeyResp.getIv()); + final String integrityValue = this.createIntegrityValue(encData, symkeyResp.getHmacKey()); + final String jsonStr = this.createMessage(symkeyResp.getSymkeyStatInfo().getCurSymkeyVersion(), encData, integrityValue); + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), jsonStr, headers); + log.info("=================================================================================="); + log.info("==== 아이핀 CI 요청 Info... ===="); + log.info("[Body]: " + jsonStr); + log.info("[encData]: " + strEncData); + log.info("==== 아이핀 CI 요청 Result Info... ===="); + log.info("[Headers]: " + resp.getHeaders().toString()); + log.info("[Body]: " + resp.getBody()); + log.info("=================================================================================="); + + + + /* ============================================================================== + * 결과 확인 + ============================================================================== */ + String strRespBody = resp.getBody().replace("\"\"", "null"); + NiceCiRespDTO respDTO = mapper.readValue(strRespBody, new TypeReference>(){}); + + + return respDTO; + } catch (Exception e) { + e.printStackTrace(); + throw new Exception("아이핀 CI 요청 실패." + e.getMessage()); + } + + + } + + + /** + * JSON 포맷의 요청메시지 생성 + * + * @param symkeyVersion + * @param encData + * @param integrityValue + * @return + * @throws JsonProcessingException + */ + protected String createMessage(String symkeyVersion, String encData, String integrityValue) throws JsonProcessingException { + Map dataHeader = new HashMap<>(); + dataHeader.put("CNTY_CD", "ko"); //[필수]이용언어: ko, en, cn ... + dataHeader.put("TRAN_ID", null); //[선택]API 통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호 + + Map dataBody = new HashMap<>(); + dataBody.put("symkey_version", symkeyVersion); //[선택]대칭키 버전 + dataBody.put("enc_data", encData); //[필수]JSON암호화 값 + dataBody.put("integrity_value", integrityValue); //[선택]무결성체크를 위해 enc_data를 HMAC처리 후, Base64 인코딩한 값 + + Map m = new HashMap<>(); + m.put("dataHeader", dataHeader); + m.put("dataBody", dataBody); + + return mapper.writeValueAsString(m); + } + + private String createEncData(String siteCode, String jid, String reqNo, String reqDtim, String clientIp) throws JsonProcessingException { + Map m = new HashMap<>(); + m.put("site_code", siteCode); //[필수]사이트코드(공개키요청시 수신한 사이트코드) + m.put("info_req_type", "1"); //[필수]정보요청유형(1: CI제공) + m.put("jumin_id", jid.replaceAll("[^0-9]", "")); //[필수]주민등록번호(13자리) + m.put("req_no", reqNo); //[필수]이용기관에서 서비스에 대한 요청거래를 확인하기 위한 고유값 + m.put("req_dtim", reqDtim); //[필수]거래요청시간(YYYYYMMDDHH24MISS) + m.put("client_ip", clientIp); //[선택]서비스 이용 사용자 IP + + return mapper.writeValueAsString(m); + + + } + + private String encEncData(String encData, String symkey, String iv) throws IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException { + + // 암호화 + SecretKey secureKey = new SecretKeySpec(symkey.getBytes(), "AES"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secureKey, new IvParameterSpec(iv.getBytes())); + byte[] encrypted = cipher.doFinal(encData.trim().getBytes()); + + // Base64 인코딩 + String reqDataEnc = Base64Utils.encodeToString(encrypted); + + return reqDataEnc; + } + + + private String decEncData(String encData, String symkey, String iv) throws IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException { + + // Base64 디코딩 + byte[] respDataEnc = Base64Utils.decode(encData.getBytes()); + + // 복호화 + SecretKey secureKey = new SecretKeySpec(symkey.getBytes(), "AES"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secureKey, new IvParameterSpec(iv.getBytes())); + byte[] decrypted = cipher.doFinal(respDataEnc); + String strDecrypted = new String(decrypted); + + return strDecrypted; + } + + + /** + * Hmac 무결성체크값(integrity_value) 생성 + * + * @param encData + * @param hmacKey + * @return + */ + private String createIntegrityValue(String encData, String hmacKey) { + byte[] hmacSha256 = hmac256(hmacKey.getBytes(), encData.getBytes()); + String integrityValue = Base64.getEncoder().encodeToString(hmacSha256); + + return integrityValue; + } + + private byte[] hmac256(byte[] secretKey, byte[] message) { + byte[] hmac256 = null; + try { + Mac mac = Mac.getInstance("HmacSHA256"); + SecretKeySpec sks = new SecretKeySpec(secretKey, "HmacSHA256"); + mac.init(sks); + hmac256 = mac.doFinal(message); + return hmac256; + } catch (Exception e) { + throw new RuntimeException("Failed to generate HMACSHA256 encrypt"); + } + } + + +} + diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/api/NiceCiApiAbstract.java b/src/main/java/cokr/xit/ci/api/service/support/rest/api/NiceCiApiAbstract.java new file mode 100644 index 0000000..c4b3f9c --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/api/NiceCiApiAbstract.java @@ -0,0 +1,120 @@ +package cokr.xit.ci.api.service.support.rest.api; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.experimental.SuperBuilder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Random; + +@Slf4j +@SuperBuilder +public class NiceCiApiAbstract { + + protected final String PRODUCT_ID = "2101466024"; + + protected final ObjectMapper mapper = new ObjectMapper() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + + + + + /** + *
메소드 설명: API 호출
+     * 
+ * + * @param method + * @param url + * @param body + * @param headers + * @return ResponseEntity 요청처리 후 응답객체 + * @author: 박민규 + */ + protected final ResponseEntity callApi(HttpMethod method, String url, Object body, HttpHeaders headers) { + + ResponseEntity responseEntity = null; + try { + //uri 및 entity(param) 설정 + HttpEntity entity = null; + UriComponents uri = null; + switch (method) { + case GET: + entity = new HttpEntity<>(headers); + uri = UriComponentsBuilder + .fromHttpUrl(String.format("%s?%s", url, body == null ? "" : body)) +// .encode(StandardCharsets.UTF_8) //"%"기호가 "%25"로 인코딩 발생하여 주석처리 함. + .build(false); + break; + case POST: + entity = new HttpEntity<>(body, headers); + uri = UriComponentsBuilder + .fromHttpUrl(url) + .encode(StandardCharsets.UTF_8) + .build(); + break; + + default: + break; + } + + + //api 호출 + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); + factory.setConnectTimeout(3000); //커넥션타임아웃 설정 3초 + factory.setReadTimeout(3000);//타임아웃 설정 3초 + RestTemplate restTemplate = new RestTemplate(factory); + log.info(" url => " + uri.toString()); + log.info(" method => " + method); + log.info(" headers => " + entity.getHeaders().toString()); + log.info(" body => " + entity.getBody()); + responseEntity = restTemplate.exchange(URI.create(uri.toString()), method, entity, String.class); //이 한줄의 코드로 API를 호출해 String타입으로 전달 받는다. + + /* + * HttpStatus 정보 확인 방법 + * -.코드: responseEntity.getStatusCodeValue() + * -.메시지: responseEntity.getStatusCode() + */ + + } catch (HttpServerErrorException e) { + responseEntity = new ResponseEntity(e.getResponseBodyAsString(), e.getStatusCode()); + log.error(String.format("call API 서버오류[url =>%s param => %s error => %s]", url, body, e.getMessage())); + } catch (HttpClientErrorException e) { + responseEntity = new ResponseEntity(e.getResponseBodyAsString(), e.getStatusCode()); + log.error(String.format("call API 클라이언트오류[url =>%s param => %s error => %s]", url, body, e.getMessage())); + } catch (RestClientException e) { //timeout 발생 또는 기타 오류... + responseEntity = new ResponseEntity(e.getMessage(), HttpStatus.REQUEST_TIMEOUT); + log.error(String.format("RestAPI 호출 오류[url =>%s param => %s error => %s]", url, body, e.getMessage())); + } catch (Exception e) { + responseEntity = new ResponseEntity(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + log.error(String.format("call API 기타오류[url =>%s param => %s error => %s]", url, body, e.getMessage())); + } + + //결과 응답 + return responseEntity; + } + + + protected final String randomAlphaWord(int wordLength) { + Random r = new Random(); + + StringBuilder sb = new StringBuilder(wordLength); + for (int i = 0; i < wordLength; i++) { + char tmp = (char) ('a' + r.nextInt('z' - 'a')); + sb.append(tmp); + + } + return sb.toString(); + + } +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/api/PubkeyRequest.java b/src/main/java/cokr/xit/ci/api/service/support/rest/api/PubkeyRequest.java new file mode 100644 index 0000000..6f69197 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/api/PubkeyRequest.java @@ -0,0 +1,145 @@ +package cokr.xit.ci.api.service.support.rest.api; + +import cokr.xit.ci.api.service.support.rest.code.NiceCiApiCd; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespDTO; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespVO; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyPubkeyResp; +import cokr.xit.ci.api.service.support.rest.utils.Token; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.experimental.SuperBuilder; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Base64Util; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; + +import java.nio.charset.Charset; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@SuperBuilder +public class PubkeyRequest extends NiceCiApiAbstract { + + protected final String HOST; + protected final String API_URL; + + + public NiceCiRespVO execute(String clientId) throws Exception { + /* ============================================================================== + * 유효성 확인 + ============================================================================== */ + if (StringUtils.isEmpty(Token.getInstance().getData().getAccessToken())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("엑세스토큰은 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(clientId)) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build(); + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + NiceCiRespDTO responseDTO = this.generatePublickey(Token.getInstance().getData().getAccessToken(), clientId); + + + /* ============================================================================== + * 결과 반환 + ============================================================================== */ + if (!"1200".equals(responseDTO.getDataHeader().getGW_RSLT_CD())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("HEAD_" + responseDTO.getDataHeader().getGW_RSLT_CD())) + .errMsg(String.format("[%s]: %s", responseDTO.getDataHeader().getGW_RSLT_CD(), responseDTO.getDataHeader().getGW_RSLT_MSG())) + .build(); + if (!"P000".equals(responseDTO.getDataBody().getRspCd())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum(responseDTO.getDataBody().getRspCd())) + .errMsg(String.format("[%s]: %s. %s", responseDTO.getDataBody().getRspCd(), NiceCiApiCd.valueOfEnum(responseDTO.getDataBody().getRspCd()).getCodeNm(), responseDTO.getDataBody().getResMsg())) + .build(); + if (!"0000".equals(responseDTO.getDataBody().getResultCd())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("PUBKEY_" + responseDTO.getDataBody().getResultCd())) + .errMsg(String.format("[%s]: %s", responseDTO.getDataBody().getResultCd(), NiceCiApiCd.valueOfEnum("PUBKEY_" + responseDTO.getDataBody().getResultCd()).getCodeNm())) + .build(); + + + return NiceCiRespVO.okBuilder().resultInfo(responseDTO.getDataBody()).build(); + } + + + protected NiceCiRespDTO generatePublickey(String accessToken, String clientId) throws Exception { + try { + /* ============================================================================== + * HEADER 설정 + ============================================================================== */ + String bearerToken = Base64Util.encode(String.format("%s:%s:%s", accessToken, (new Date().getTime() / 1000), clientId)); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8"))); + headers.set("Authorization", String.format("bearer %s", bearerToken)); + headers.set("client_id", clientId); + headers.set("productID", PRODUCT_ID); + + + /* ============================================================================== + * URL 설정 + ============================================================================== */ + StringBuilder url = new StringBuilder(); + url.append(this.HOST) + .append(this.API_URL); + String jsonStr = this.createMessage(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), jsonStr, headers); + log.info("=================================================================================="); + log.info("==== 공개키 요청 Result Info... ===="); + log.info("[Headers]: " + resp.getHeaders().toString()); + log.info("[Body]: " + resp.getBody()); + log.info("=================================================================================="); + + + + /* ============================================================================== + * 결과 확인 + ============================================================================== */ +// NiceCiRespDTO respDTO = mapper.readValue(resp.getBody(), NiceCiRespDTO.class); + String strRespBody = resp.getBody().replace("\"\"", "null"); + NiceCiRespDTO respDTO = mapper.readValue(strRespBody, new TypeReference>(){}); + + + return respDTO; + } catch (Exception e) { + e.printStackTrace(); + throw new Exception("공개키 요청 실패." + e.getMessage()); + } + } + + + /** + * JSON 포맷의 요청메시지 생성 + * + * @param reqDtim + * @return + * @throws JsonProcessingException + */ + protected String createMessage(String reqDtim) throws JsonProcessingException { + Map dataHeader = new HashMap<>(); + dataHeader.put("CNTY_CD", "ko"); //[필수]이용언어: ko, en, cn ... + dataHeader.put("TRAN_ID", null); //[선택]API 통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호 + + Map dataBody = new HashMap<>(); + dataBody.put("req_dtim", reqDtim); //[필수]공개키 요청일시(YYYYMMDDHH24MISS) + + Map m = new HashMap<>(); + m.put("dataHeader", dataHeader); + m.put("dataBody", dataBody); + + return mapper.writeValueAsString(m); + } +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/api/SymkeyRegist.java b/src/main/java/cokr/xit/ci/api/service/support/rest/api/SymkeyRegist.java new file mode 100644 index 0000000..460f669 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/api/SymkeyRegist.java @@ -0,0 +1,278 @@ +package cokr.xit.ci.api.service.support.rest.api; + +import cokr.xit.ci.api.service.support.rest.code.NiceCiApiCd; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespDTO; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespVO; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodySymkeyResp; +import cokr.xit.ci.api.service.support.rest.utils.PublicKey; +import cokr.xit.ci.api.service.support.rest.utils.Token; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.experimental.SuperBuilder; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Base64Util; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Base64Utils; +import org.springframework.util.StringUtils; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.Charset; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@SuperBuilder +public class SymkeyRegist extends NiceCiApiAbstract { + + protected final String HOST; + protected final String API_URL; + + + public NiceCiRespVO execute(String clientId) throws Exception { + /* ============================================================================== + * 유효성 확인 + ============================================================================== */ + if (StringUtils.isEmpty(Token.getInstance().getData().getAccessToken())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("엑세스토큰은 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(clientId)) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(PublicKey.getInstance().getData().getSiteCode())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("사이트코드는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(PublicKey.getInstance().getData().getPublicKey())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("공개키는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(PublicKey.getInstance().getData().getKeyVersion())) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("공개키버전은 필수 입력값 입니다.").build(); + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + NiceCiRespDTO responseDTO = this.generateSymmetrickey(Token.getInstance().getData().getAccessToken(), clientId, PublicKey.getInstance().getData().getSiteCode(), PublicKey.getInstance().getData().getPublicKey(), PublicKey.getInstance().getData().getKeyVersion()); + + + /* ============================================================================== + * 결과 반환 + ============================================================================== */ + if (!"1200".equals(responseDTO.getDataHeader().getGW_RSLT_CD())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("HEAD_" + responseDTO.getDataHeader().getGW_RSLT_CD())) + .errMsg(String.format("[%s]: %s", responseDTO.getDataHeader().getGW_RSLT_CD(), responseDTO.getDataHeader().getGW_RSLT_MSG())) + .build(); + if (!"P000".equals(responseDTO.getDataBody().getRspCd())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum(responseDTO.getDataBody().getRspCd())) + .errMsg(String.format("[%s]: %s. %s", responseDTO.getDataBody().getRspCd(), NiceCiApiCd.valueOfEnum(responseDTO.getDataBody().getRspCd()).getCodeNm(), responseDTO.getDataBody().getResMsg())) + .build(); + if (!"0000".equals(responseDTO.getDataBody().getResultCd())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("SYMKEY_" + responseDTO.getDataBody().getResultCd())) + .errMsg(String.format("[%s]: %s", responseDTO.getDataBody().getResultCd(), NiceCiApiCd.valueOfEnum("SYMKEY_" + responseDTO.getDataBody().getResultCd()).getCodeNm())) + .build(); + + + return NiceCiRespVO.okBuilder().resultInfo(responseDTO.getDataBody()).build(); + + } + + + protected NiceCiRespDTO generateSymmetrickey(String accessToken, String clientId, String siteCode, String publicKey, String pubkeyVersion) throws Exception { + try { + + + /* ============================================================================== + * HEADER 설정 + ============================================================================== */ + String bearerToken = Base64Util.encode(String.format("%s:%s:%s", accessToken, (new Date().getTime() / 1000), clientId)); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, Charset.forName("utf-8"))); + headers.set("Authorization", String.format("bearer %s", bearerToken)); + headers.set("client_id", clientId); + headers.set("productID", PRODUCT_ID); + + + /* ============================================================================== + * URL 설정 + ============================================================================== */ + StringBuilder url = new StringBuilder(); + url.append(this.HOST) + .append(this.API_URL); + final String requestNo = createRequestNo(); + final String key = this.createKey(); + final String iv = this.createIv(); + final String hmacKey = this.createHmacKey(); + final String strSymkeyRegInfo = this.createSymkeyRegInfo(siteCode, requestNo, key, iv, hmacKey); + final String jsonStr = this.createMessage(pubkeyVersion, encSymkeyRegInfo(strSymkeyRegInfo, publicKey)); + + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), jsonStr, headers); + log.info("=================================================================================="); + log.info("==== 대칭키 등록 요청 Info... ===="); + log.info("[Body]: " + jsonStr); + log.info("[symkeyRegInfo]: " + strSymkeyRegInfo); + log.info("==== 대칭키 등록 요청 Result Info... ===="); + log.info("[Headers]: " + resp.getHeaders().toString()); + log.info("[Body]: " + resp.getBody()); + log.info("=================================================================================="); + + + + /* ============================================================================== + * 결과 확인 + ============================================================================== */ +// NiceCiRespDTO respDTO = mapper.readValue(resp.getBody(), NiceCiRespDTO.class); + String strRespBody = resp.getBody().replace("\"\"", "null"); + strRespBody = strRespBody.replace("\\", "").replace("\"{", "{").replace("}\"", "}"); //symkey_stat_info 데이터의 쌍따옴표를 제거하여 Json데이터로 인식하도록 replace 적용 + NiceCiRespDTO respDTO = mapper.readValue(strRespBody, new TypeReference>(){}); + if (!(respDTO.getDataBody() == null || "".equals(respDTO.getDataBody()))) { + respDTO.getDataBody().setSiteCode(siteCode); + respDTO.getDataBody().setRequestNo(requestNo); + respDTO.getDataBody().setKey(key); + respDTO.getDataBody().setIv(iv); + respDTO.getDataBody().setHmacKey(hmacKey); + } + + return respDTO; + } catch (Exception e) { + e.printStackTrace(); + throw new Exception("대칭키 등록 요청 실패." + e.getMessage()); + } + } + + + private String createSymkeyRegInfo(String siteCode, String requestNo, String key, String iv, String hmacKey) throws JsonProcessingException { + Map m = new HashMap<>(); + m.put("site_code", siteCode); //[필수]사이트코드(공개키요청시 수신한 사이트코드) + m.put("request_no", requestNo); //[필수]요청고유번호(이용기관에서 임의 생성한 값) + m.put("key", key); //[필수]회원사에서 사용할 암호화 KEY 세팅(32byte AES256 bits, 16byte AES128 bits). NICE에 Key 등록 후 최대 6개월 내 갱신 필요 + m.put("iv", iv); //[필수]데이터를 암호화할 Initial Vector. 회원사에서 생성(16 byte 딱 맞게 생성) + m.put("hmac_key", hmacKey); //[필수]무결성체크값에 사용할 Hmac Key. 회원사에서 생성(32 byte 딱 맞게 생성) + + return mapper.writeValueAsString(m); + } + + + /** + * 대칭키 생성 + * -. KEY를 NICE에 등록 후 6개월 내 갱신이 필요 함 + * -. 6개월 내 현재 등록키, 이전 등록키 사용 가능하므로 대칭키 등록 후 AP에서 암호화키 변경 적용 필요 + * -. 6개월 내 키등록이 없으면 암복호화 오류 발생 + *

+ * [예시] + * 1) 이용기관에서 대칭키 드록 후, 암복호화를 진행 + * 2) 적절한 시점에 신규 대칭키를 등록 + * 3) API에서도 신규 대칭키로 암호화(enc_data) 키 변경 진행 + * => 직전 등록된 대칭키는 유효기간 내 사용가능하므로 대칭키 등록과 API의 암호화 키변경은 이용기관 일정에 따라 진행) + * + * @return + */ + private String createKey() { + return randomAlphaWord(32); + } + + /** + * Initail Vector 생성 + * -. 데이터를 암호화할 Initial Vector + * -. 16 byte 딱 맞게 생성 해야 함 + * + * @return + */ + private String createIv() { + return randomAlphaWord(16); + } + + /** + * 요청고유번호 + * -.이용기관에서 임의 생성한 값 + * + * @return + */ + private String createRequestNo() { + return randomAlphaWord(30); + } + + /** + * hamc_key 생성 + * -. 무결성체크값에 사용할 Hmac Key + * -. 32 byte 딱 맞게 생성 해야 함 + * + * @return + */ + private String createHmacKey() { + return randomAlphaWord(32); + } + + + /** + * 공개키로 암호화를 수행 후 반환 한다. + * + * @param symkeyRegInfo + * @param sPublicKey + * @return + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + protected final String encSymkeyRegInfo(String symkeyRegInfo, String sPublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + //공개키 변환 + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); +// byte[] cipherEnc = Base64Utils.decode(sPublicKey.getBytes()); + byte[] cipherEnc = Base64.getDecoder().decode(sPublicKey); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(cipherEnc); + java.security.PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + //암호화 + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] bytePlain = cipher.doFinal(symkeyRegInfo.getBytes()); + + // Base64 인코딩 + String keyInfoEnc = Base64Utils.encodeToString(bytePlain); + + return keyInfoEnc; + } + + + /** + * JSON 포맷의 요청메시지 생성 + * + * @param pubkeyVersion + * @param symkeyRegInfo + * @return + * @throws JsonProcessingException + */ + protected String createMessage(String pubkeyVersion, String symkeyRegInfo) throws JsonProcessingException { + Map dataHeader = new HashMap<>(); + dataHeader.put("CNTY_CD", "ko"); //[필수]이용언어: ko, en, cn ... + dataHeader.put("TRAN_ID", null); //[선택]API 통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호 + + Map dataBody = new HashMap<>(); + dataBody.put("pubkey_version", pubkeyVersion); //[필수]공개키 버전 + dataBody.put("symkey_reg_info", symkeyRegInfo); //[필수]JSON암호화 값 + + Map m = new HashMap<>(); + m.put("dataHeader", dataHeader); + m.put("dataBody", dataBody); + + return mapper.writeValueAsString(m); + } +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/api/TokenGenerate.java b/src/main/java/cokr/xit/ci/api/service/support/rest/api/TokenGenerate.java new file mode 100644 index 0000000..fe27f24 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/api/TokenGenerate.java @@ -0,0 +1,225 @@ +package cokr.xit.ci.api.service.support.rest.api; + +import cokr.xit.ci.api.service.support.rest.code.NiceCiApiCd; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespDTO; +import cokr.xit.ci.api.service.support.rest.model.NiceCiRespVO; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyGenerateTokenResp; +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyRevokeTokenResp; +import cokr.xit.ci.core.utils.DateUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.experimental.SuperBuilder; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Base64Util; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +import java.nio.charset.Charset; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +@Slf4j +@SuperBuilder +public class TokenGenerate extends NiceCiApiAbstract { + + protected final String HOST; + protected final String API_URL_GENERATE_TOKEN; + protected final String API_URL_REVOKE_TOKEN; + + + public NiceCiRespVO execute(String clientId, String clientSecret) throws Exception { + /* ============================================================================== + * 유효성 확인 + ============================================================================== */ + if (StringUtils.isEmpty(clientId)) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build(); + if (StringUtils.isEmpty(clientSecret)) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트비밀번호는 필수 입력값 입니다.").build(); + + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + NiceCiRespDTO generateResponseDTO = this.generateToken(clientId, clientSecret); + + + /* ============================================================================== + * 결과 반환 + * [토큰 재발급] + * -.토큰만료or폐기 상태에서 API 요청하면 GW_RSLT_CD가 1800 이 리턴되며 이때는 "token 요청" + * -.만료 30일 이전이면 토큰 재발급(폐기&요청) + ============================================================================== */ + if ("1800".equals(generateResponseDTO.getDataHeader().getGW_RSLT_CD())) //결과코드가 "1800" 이면... + generateResponseDTO = this.generateToken(clientId, clientSecret); + if (!"1200".equals(generateResponseDTO.getDataHeader().getGW_RSLT_CD())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("HEAD_" + generateResponseDTO.getDataHeader().getGW_RSLT_CD())) + .errMsg(String.format("[%s]: %s", generateResponseDTO.getDataHeader().getGW_RSLT_CD(), generateResponseDTO.getDataHeader().getGW_RSLT_MSG())) + .build(); + if (DateUtil.secToDays(generateResponseDTO.getDataBody().getExpiresIn()) < 30) { //토큰만료일이 30일 미만이면.. + //토큰폐기 + NiceCiRespDTO revokeResponseDTO = this.revokeToken(generateResponseDTO.getDataBody().getAccessToken(), clientId); + if (!"1200".equals(revokeResponseDTO.getDataHeader().getGW_RSLT_CD())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("HEAD_" + generateResponseDTO.getDataHeader().getGW_RSLT_CD())) + .errMsg(String.format("[%s]: %s", generateResponseDTO.getDataHeader().getGW_RSLT_CD(), generateResponseDTO.getDataHeader().getGW_RSLT_MSG())) + .build(); + if(!revokeResponseDTO.getDataBody().getResult()) + return NiceCiRespVO.errBuilder().errCode(NiceCiApiCd.FAIL).errMsg(String.format("토큰(%s) 폐기에 실패 했습니다.", generateResponseDTO.getDataBody().getAccessToken())).build(); + //토큰요청 + generateResponseDTO = this.generateToken(clientId, clientSecret); + if (!"1200".equals(generateResponseDTO.getDataHeader().getGW_RSLT_CD())) + return NiceCiRespVO.errBuilder() + .errCode(NiceCiApiCd.valueOfEnum("HEAD_" + generateResponseDTO.getDataHeader().getGW_RSLT_CD())) + .errMsg(String.format("[%s]: %s", generateResponseDTO.getDataHeader().getGW_RSLT_CD(), generateResponseDTO.getDataHeader().getGW_RSLT_MSG())) + .build(); + } + + + + return NiceCiRespVO.okBuilder().resultInfo(generateResponseDTO.getDataBody()).build(); + } + + + protected NiceCiRespDTO generateToken(String clientId, String clientSecret) throws Exception { + try { + + + /* ============================================================================== + * HEADER 설정 + ============================================================================== */ + String authorizationToken = Base64Util.encode(String.format("%s:%s", clientId, clientSecret)); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType(MediaType.APPLICATION_FORM_URLENCODED, Charset.forName("utf-8"))); + headers.set("Authorization", String.format("Basic %s", authorizationToken)); + + + /* ============================================================================== + * URL 설정 + ============================================================================== */ + StringBuilder url = new StringBuilder(); + url.append(this.HOST) + .append(API_URL_GENERATE_TOKEN); + MultiValueMap body = this.createMessage(); + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), body, headers); + log.info("=================================================================================="); + log.info("==== 토큰발급 요청 Result Info... ===="); + log.info("[Headers]: " + resp.getHeaders().toString()); + log.info("[Body]: " + resp.getBody()); + log.info("=================================================================================="); + + + /* ============================================================================== + * 결과 확인 + * 응답데이터 예시 => {"dataHeader":{"GW_RSLT_CD":"1200","GW_RSLT_MSG":"오류 없음"},"dataBody":{"access_token":"8c680964-eec7-485c-ad58-6534e90cc653","token_type":"bearer","expires_in":1576914752,"scope":"default"}} + ============================================================================== */ +// NiceCiRespDTO respDTO = mapper.readValue(resp.getBody(), NiceCiRespDTO.class); + String strRespBody = resp.getBody().replace("\"\"", "null"); + NiceCiRespDTO respDTO = mapper.readValue(strRespBody, new TypeReference>(){}); + if(!(respDTO.getDataBody()==null||"".equals(respDTO.getDataBody()))) + respDTO.getDataBody().setExpiredDt(this.addSec(respDTO.getDataBody().getExpiresIn(), "yyyyMMddHHmmss")); + + + return respDTO; + } catch (Exception e) { + e.printStackTrace(); + throw new Exception("토큰발급 요청 실패." + e.getMessage()); + } + } + + + protected NiceCiRespDTO revokeToken(String accessToken, String clientId) throws Exception { + try { + /* ============================================================================== + * HEADER 설정 + ============================================================================== */ + String bearerToken = Base64Util.encode(String.format("%s:%s:%s", accessToken, (new Date().getTime() / 1000), clientId)); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType(MediaType.APPLICATION_FORM_URLENCODED, Charset.forName("utf-8"))); + headers.set("Authorization", String.format("Basic %s", bearerToken)); + + + /* ============================================================================== + * URL 설정 + ============================================================================== */ + StringBuilder url = new StringBuilder(); + url.append(this.HOST) + .append(API_URL_REVOKE_TOKEN); + + + /* ============================================================================== + * API 호출 + ============================================================================== */ + ResponseEntity resp = this.callApi(HttpMethod.POST, url.toString(), null, headers); + log.info("=================================================================================="); + log.info("==== 토큰폐기 요청 Result Info... ===="); + log.info("[Headers]: " + resp.getHeaders().toString()); + log.info("[Body]: " + resp.getBody()); + log.info("=================================================================================="); + + + /* ============================================================================== + * 결과 확인 + ============================================================================== */ +// NiceCiRespDTO respDTO = mapper.readValue(resp.getBody(), NiceCiRespDTO.class); + String strRespBody = resp.getBody().replace("\"\"", "null"); + NiceCiRespDTO respDTO = mapper.readValue(strRespBody, new TypeReference>(){}); + + + return respDTO; + } catch (Exception e) { + e.printStackTrace(); + throw new Exception("토큰폐기 요청 실패." + e.getMessage()); + } + } + + + /** + * JSON 포맷의 요청메시지 생성 + * + * @return + * @throws JsonProcessingException + */ + protected MultiValueMap createMessage() throws JsonProcessingException { + MultiValueMap m = new LinkedMultiValueMap<>(); + m.add("grant_type", "client_credentials"); + m.add("scope", "default"); + + return m; + } + + + + /** + * 현재날짜에 초(sec)를 더한 날짜를 반환 한다. + * @param sec + * @param pattern + * @return + */ + private String addSec(Integer sec, String pattern){ + if(sec==null) + return null; + + if("".equals(pattern)||pattern==null) + pattern = "yyyyMMdd"; + + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis((sec*1000L) + (new Date().getTime())); + Date date = calendar.getTime(); + + return new SimpleDateFormat(pattern).format(date); + } + +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/code/NiceCiApiCd.java b/src/main/java/cokr/xit/ci/api/service/support/rest/code/NiceCiApiCd.java index aaf965f..4f945ec 100644 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/code/NiceCiApiCd.java +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/code/NiceCiApiCd.java @@ -3,23 +3,26 @@ package cokr.xit.ci.api.service.support.rest.code; public enum NiceCiApiCd { UNKNOWN("알수없음") - /* ======================================================================= - * HTTP 응답코드 - * -. HTTP의 코드값을 열거형 상수로 선언하기 위해 prefix "HTTP_"를 추가 함 - ======================================================================= */ - , HTTP_200("No Error") - , HTTP_400("Bad Request or Inavalid Token") - , HTTP_401("Authorized required") - , HTTP_402("unAuthorized") - , HTTP_403("service Disabled") - , HTTP_404("Service Not Found") - , HTTP_500("Internal Server Error") - , HTTP_501("Access Denied by Protected Service") - , HTTP_502("Bad Response from Protected Service") - , HTTP_503("Service Temporarily Unavailable") + , OK("정상") + , FAIL("실패") +// /* ======================================================================= +// * HTTP 응답코드 +// * -. HTTP의 코드값을 열거형 상수로 선언하기 위해 prefix "HTTP_"를 추가 함 +// ======================================================================= */ +// , HTTP_200("No Error") +// , HTTP_400("Bad Request or Inavalid Token") +// , HTTP_401("Authorized required") +// , HTTP_402("unAuthorized") +// , HTTP_403("service Disabled") +// , HTTP_404("Service Not Found") +// , HTTP_500("Internal Server Error") +// , HTTP_501("Access Denied by Protected Service") +// , HTTP_502("Bad Response from Protected Service") +// , HTTP_503("Service Temporarily Unavailable") /* ======================================================================= - * APIM 결과코드 (dataHeader) + * APIM 결과코드 (dataHeader.GW_RSLT_CD) + * -. DataHeader부의 GW_RSLT_CD가 "1200"일 경우, dataBody 부가 유효함 * -. Data Header의 코드값을 열거형 상수로 선언하기 위해 prefix "HEAD_"를 추가 함 ======================================================================= */ , HEAD_1200("오류 없음 (정상)") @@ -60,7 +63,8 @@ public enum NiceCiApiCd { /* ======================================================================= - * 응답코드(rsp_cd) + * 응답코드(dataBody.rsp_cd) + * -.dataBody부의 rsp_cd가 P000일 때, result_cd값이 유효함 ======================================================================= */ , P000("정상응답") , S603("내부 DB 오류") @@ -71,7 +75,8 @@ public enum NiceCiApiCd { /* ======================================================================= - * APIM 결과코드 (dataBody) + * APIM 결과코드 (dataBody.???) + * -.dataBody부의 result_cd값이 "0000"일 경우 응답데이터가 유효함 ======================================================================= */ , EAPI2500("맵핑정보 없음 - {0}") , EAPI2510("요청맵핑 데이터가 없습니다.") @@ -97,13 +102,49 @@ public enum NiceCiApiCd { /* ======================================================================= - * 상세 결과코드 - * -. 상세결과 코드값을 열거형 상수로 선언하기 위해 prefix "DRSLT_"를 추가 함 + * 상세 결과코드(result_cd) - 공개키등록 + * -. rsp_cd가 "P000"일 때 상세결과코드 + * -. 상세결과 코드값을 열거형 상수로 선언하기 위해 prefix "PUBKEY_"를 추가 함 + ======================================================================= */ + , PUBKEY_0000("공개키 발급") + , PUBKEY_0001("필수입력값 오류") + , PUBKEY_0003("공개키 발급 대상 회원사 아님") + , PUBKEY_0099("기타오류") + + /* ======================================================================= + * 상세 결과코드(result_cd) - 대칭키등록 + * -. rsp_cd가 "P000"일 때 상세결과코드 + * -. 상세결과 코드값을 열거형 상수로 선언하기 위해 prefix "SYMKEY_"를 추가 함 + ======================================================================= */ + , SYMKEY_0000("대칭키 발급") + , SYMKEY_0001("공개키 기간 만료") + , SYMKEY_0002("공개키를 찾을 수 없음") + , SYMKEY_0003("공개키를 발급한 회원사 아님") + , SYMKEY_0004("복호화 오류") + , SYMKEY_0005("필수입력값 오류 (key_version, key_info 필수값 확인)") + , SYMKEY_0006("대칭키 등록 가능 회원사 아님") + , SYMKEY_0007("key 중복 오류 (현재 및 직전에 사용한 Key 사용 불가)") + , SYMKEY_0008("요청사이트코드와 공개키발급 사이트코드 다름") + , SYMKEY_0099("기타오류") + + /* ======================================================================= + * 상세 결과코드(result_cd) - 아이핀CI요청 + * -. rsp_cd가 "P000"일 때 상세결과코드 + * -. 상세결과 코드값을 열거형 상수로 선언하기 위해 prefix "CI_"를 추가 함 ======================================================================= */ - , DRSLT_0000("공개키 발급") - , DRSLT_0001("필수입력값 오류") - , DRSLT_0003("공개키 발급 대상 회원사 아님") - , DRSLT_0099("기타오류") + , CI_0000("처리완료") + , CI_0001("대칭키 기간 만료") + , CI_0002("대칭키를 찾을 수 없음") + , CI_0003("대칭키를 발급한 회원사 아님") + , CI_0004("복호화 오류") + , CI_0005("필수입력값 오류 (integrity_value, enc_data 내 필수값 확인)") + , CI_0006("데이터 무결성 오류 (hmac값 불일치)") + , CI_0007("정보요청유형 입력값 오류 (info_req_type이 1 아님)") + , CI_0008("주민번호 유효성 오류 (생년월일 유효성 및 숫자 아님)") + , CI_0009("거래요청시간 포멧오류 (req_dtim 자릿수 및 숫자 아님)") + , CI_0099("기타오류") + + ; diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/CiRespDTO.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/CiRespDTO.java deleted file mode 100644 index 8efb7a2..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/model/CiRespDTO.java +++ /dev/null @@ -1,26 +0,0 @@ -package cokr.xit.ci.api.service.support.rest.model; - -import cokr.xit.ci.api.service.support.rest.model.conf.EncData; -import com.fasterxml.jackson.annotation.JsonAlias; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -@Builder -public class CiRespDTO implements TransDTO { - - @JsonAlias({"rsp_cd"}) - private String rspCd; //dataBody 정상처리여부 (P000 성공, 이외 모두 오류) - - @JsonAlias({"res_msg"}) - private String resMsg; //rsp_cd가 "EAPI"로 시작될 경우 오류 메시지 세팅 - - @JsonAlias({"enc_data"}) - private EncData encData; //JSON암호화값(rsp_cd가 P000일 때 나감) - 응답정보를 회원사에서 요청시 전달한 대칭키로 암호화한 값 - - @JsonAlias({"integrity_value"}) - private String integrityValue; //무결성체크를 위해 enc_data를 HMAC처리 후, BASE64 인코딩한 값 - -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/GenerateTokenRespDTO.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/GenerateTokenRespDTO.java deleted file mode 100644 index 561a17e..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/model/GenerateTokenRespDTO.java +++ /dev/null @@ -1,28 +0,0 @@ -package cokr.xit.ci.api.service.support.rest.model; - -import com.fasterxml.jackson.annotation.JsonAlias; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -@Getter -@ToString -@Builder -public class GenerateTokenRespDTO implements TransDTO { - - @JsonAlias({"access_token"}) - private String accessToken; //사용자 엑세스 토큰 값(모든 API 요청시 헤더에 access_token을 포함하여 전송) - - @JsonAlias({"expires_in"}) - private Long expiresIn; //access token 만료까지 남은시간(초) - - @JsonAlias({"token_type"}) - private String tokenType; //bearer로 고정 - - @JsonAlias({"scope"}) - private String scope; //요청한 scope값(기본 default) - - @Setter - private String expiredDt; //access token 만료시간(yyyyMmddHHmmss) -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/NiceCiRespDTO.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/NiceCiRespDTO.java new file mode 100644 index 0000000..1218eaa --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/NiceCiRespDTO.java @@ -0,0 +1,22 @@ +package cokr.xit.ci.api.service.support.rest.model; + +import cokr.xit.ci.api.service.support.rest.model.conf.DataHeader; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class NiceCiRespDTO { + + + private DataHeader dataHeader; + + private T dataBody; + +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/NiceCiRespVO.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/NiceCiRespVO.java new file mode 100644 index 0000000..b1538c7 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/NiceCiRespVO.java @@ -0,0 +1,34 @@ +package cokr.xit.ci.api.service.support.rest.model; + +import cokr.xit.ci.api.service.support.rest.code.NiceCiApiCd; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@ToString +public class NiceCiRespVO { + + private NiceCiApiCd errCode; + private String errMsg; + + + private T resultInfo; + + + @Builder(builderClassName = "okBuilder" ,builderMethodName = "okBuilder") + NiceCiRespVO(T resultInfo) { + this.errCode = NiceCiApiCd.OK; + this.errMsg = NiceCiApiCd.OK.getCodeNm(); + this.resultInfo = resultInfo; + } + + @Builder(builderClassName = "errBuilder" ,builderMethodName = "errBuilder") + NiceCiRespVO(NiceCiApiCd errCode, String errMsg, T resultInfo) { + this.errCode = errCode; + this.errMsg = errMsg; + this.resultInfo = resultInfo; + } +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/PublickeyRespDTO.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/PublickeyRespDTO.java deleted file mode 100644 index 1cc3b71..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/model/PublickeyRespDTO.java +++ /dev/null @@ -1,37 +0,0 @@ -package cokr.xit.ci.api.service.support.rest.model; - -import com.fasterxml.jackson.annotation.JsonAlias; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -@Builder -public class PublickeyRespDTO implements TransDTO { - - @JsonAlias({"rsp_cd"}) - private String rspCd; //dataBody 정상처리여부 (P000 성공, 이외 모두 오류) - - @JsonAlias({"res_msg"}) - private String resMsg; //rsp_cd가 "EAPI"로 시작될 경우 오류 메시지 세팅 - - @JsonAlias({"result_cd"}) - private String resultCd; //rsp_cd가 P000일 때 상세결과코드(0000:공개키발급, 0001:필수입력값 오류, 0003:공개키 발급 대상 회원사 아님, 0099: 기타오류) - - @JsonAlias({"site_code"}) - private String siteCode; //사이트코드 - - @JsonAlias({"key_version"}) - private String keyVersion; //공개키 버전 - - @JsonAlias({"public_key"}) - private String publicKey; //공개키 - - @JsonAlias({"valid_dtim"}) - private String validDtim; //공개키 만료일시(YYYYMMDDHH24MISS) - - - - -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/RevokeTokenRespDTO.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/RevokeTokenRespDTO.java deleted file mode 100644 index cf97c5a..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/model/RevokeTokenRespDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cokr.xit.ci.api.service.support.rest.model; - -import com.fasterxml.jackson.annotation.JsonAlias; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -@Builder -public class RevokeTokenRespDTO implements TransDTO { - - @JsonAlias({"result"}) - private Boolean result; //폐기여부(true: 폐기 성공, false: 폐기 실패) - -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/SymmetrickeyRespDTO.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/SymmetrickeyRespDTO.java deleted file mode 100644 index d08ec98..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/model/SymmetrickeyRespDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package cokr.xit.ci.api.service.support.rest.model; - -import cokr.xit.ci.api.service.support.rest.model.conf.SymkeyStatInfo; -import com.fasterxml.jackson.annotation.JsonAlias; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -@Builder -public class SymmetrickeyRespDTO implements TransDTO { - - @JsonAlias({"rsp_cd"}) - private String rspCd; //dataBody 정상처리여부 (P000 성공, 이외 모두 오류) - - @JsonAlias({"res_msg"}) - private String resMsg; //rsp_cd가 "EAPI"로 시작될 경우 오류 메시지 세팅 - - @JsonAlias({"result_cd"}) - private String resultCd; //rsp_cd가 P000일 때 상세결과코드(0000:대칭키발급, 0001:공개키기간만료, 0002:공개키를 찾을 수 없음, 0003:공개키를 발급한 회원사 아님, 0004:복호화 오류, 0005:필수입력값 오류(key_version, key_info 필수값 확인), 0006:대칭키 등록 가능 회원사 아님, 0007:key 중복 오류(현재 및 직전에 사용한 Key 사용 불가), 0008:요청사이트코드와 공개키발급 사이트코드 다름, 0099: 기타오류) - - @JsonAlias({"symkey_stat_info"}) - private SymkeyStatInfo symkeyStatInfo; //JSON값(회원사에 생성되어 있는 대칭키 버전별 유효기간(result_cd 가 0000, 0007일 경우에 나감) - - -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/TransDTO.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/TransDTO.java deleted file mode 100644 index efdb248..0000000 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/model/TransDTO.java +++ /dev/null @@ -1,5 +0,0 @@ -package cokr.xit.ci.api.service.support.rest.model; - -public interface TransDTO -{ -} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyCiResp.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyCiResp.java new file mode 100644 index 0000000..b875099 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyCiResp.java @@ -0,0 +1,41 @@ +package cokr.xit.ci.api.service.support.rest.model.conf; + +import com.fasterxml.jackson.annotation.JsonAlias; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Getter +@ToString +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "DataBodyCiResp", description = "아이핀 CI 요청 응답DataBody") +public class DataBodyCiResp { + + @JsonAlias({"rsp_cd"}) + @Schema(required = true, title = "dataBody 응답 코드", example = "P000", description = "dataBody 정상처리여부 (P000 성공, 이외 모두 오류)") + private String rspCd; + + @JsonAlias({"res_msg"}) + @Schema(required = false, title = "dataBody 응답 메시지", example = " ", description = "rsp_cd가 \"EAP\"로 시작될 경우 오류 메시지 세팅됨") + private String resMsg; + + @JsonAlias({"result_cd"}) + @Schema(required = false, title = "상세결과코드", example = " ", description = "rsp_cd가 P000일때 상세결과코드(0000:처리완료, 0001:대칭키 기간 만료, 0002:대칭키를 찾을 수 없음, 0003:대칭키를 발급한 회원사 아님, 0004:복호화 오류, 0005:필수입력값 오류(integrity_value, enc_data 내 필수값 확인), 0006:데이터 무결성 오류(hmac값 불일치), 0007:정보요청유형 입력값 오류(info_req_type이 1 아님), 0008:주민번호 유효성 오류(생년월일 유효성 및 숫자 아님), 0009:거래요청시간 포멧오류(req_dtim 자릿수 및 숫자 아님), 0099:기타오류)") + private String resultCd; + + @JsonAlias({"enc_data"}) + @Schema(required = false, title = "JSON 암호화값", example = " ", description = "P000일때 나감. 응답정보를 회원사에서 요청 시 전달한 대칭키로 암호화한 값") + private String encData; + + @JsonAlias({"integrity_value"}) + @Schema(required = false, title = "base64 인코딩 값", example = " ", description = "무결성체크를 위해 enc_data를 HMAC처리 후, BASE64 인코딩한 값") + private String integrityValue; + + + @Schema(required = false, title = "JSON 디코딩 값", example = " ", description = "encData 를 디코딩한 값") + @Setter + private EncData decEncData; + +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyGenerateTokenResp.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyGenerateTokenResp.java new file mode 100644 index 0000000..e1b93d1 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyGenerateTokenResp.java @@ -0,0 +1,35 @@ +package cokr.xit.ci.api.service.support.rest.model.conf; + +import com.fasterxml.jackson.annotation.JsonAlias; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Getter +@ToString +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "DataBodyGenerateTokenResp", description = "토큰발급 API 응답DataBody") +public class DataBodyGenerateTokenResp { + + @JsonAlias({"access_token"}) + @Schema(required = true, title = "엑세스토큰", example = " ", description = "사용자 엑세스 토큰 값(모든 API 요청시 헤더에 access_token을 포함하여 전송)") + private String accessToken; + + @JsonAlias({"expires_in"}) + @Schema(required = true, title = "엑세스토큰 만료 절대시간(sec)", example = " ", description = "access token 만료까지 남은시간(초)") + private Integer expiresIn; + + @JsonAlias({"token_type"}) + @Schema(required = true, title = "토큰타입", example = " ", description = "bearer로 고정") + private String tokenType; + + @JsonAlias({"scope"}) + @Schema(required = true, title = "요청한 scope값", example = " ", description = "요청한 scope값(기본 default)") + private String scope; + + @Setter + @Schema(required = true, title = "access token 만료시간(yyyyMmddHHmmss)", example = " ", description = "사용자 응답을 위해 절대시간(expires_in)을 yyyyMMddHHmmss 포맷으로 변경한 값") + private String expiredDt; +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyPubkeyResp.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyPubkeyResp.java new file mode 100644 index 0000000..f2536b4 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyPubkeyResp.java @@ -0,0 +1,50 @@ +package cokr.xit.ci.api.service.support.rest.model.conf; + +import com.fasterxml.jackson.annotation.JsonAlias; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Getter +@ToString +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "DataBodyPubkeyResp", description = "공개키 요청 API 응답DataBody") +public class DataBodyPubkeyResp { + + @JsonAlias({"rsp_cd"}) + @Schema(required = true, title = "dataBody 응답코드", example = " ", description = "dataBody 정상처리여부 (P000 성공, 이외 모두 오류)") + private String rspCd; + + @JsonAlias({"res_msg"}) + @Schema(required = false, title = "dataBody 응답메시지", example = " ", description = "rsp_cd가 \"EAPI\"로 시작될 경우 오류 메시지 세팅") + private String resMsg; + + @JsonAlias({"result_cd"}) + @Schema(required = false, title = "상세결과코드", example = " ", description = "rsp_cd가 P000일 때 상세결과코드(0000:공개키발급, 0001:필수입력값 오류, 0003:공개키 발급 대상 회원사 아님, 0099: 기타오류)") + private String resultCd; + + @JsonAlias({"site_code"}) + @Schema(required = false, title = "사이트코드", example = " ", description = "사이트코드") + private String siteCode; + + @JsonAlias({"key_version"}) + @Schema(required = false, title = "공개키 버전", example = " ", description = "공개키 버전") + private String keyVersion; + + @JsonAlias({"public_key"}) + @Schema(required = false, title = "공개키", example = " ", description = "공개키") + private String publicKey; + + @JsonAlias({"valid_dtim"}) + @Schema(required = false, title = "공개키 만료일시", example = " ", description = "공개키 만료일시(YYYYMMDDHH24MISS)") + private String validDtim; + + + + +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyRevokeTokenResp.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyRevokeTokenResp.java new file mode 100644 index 0000000..fed75fd --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodyRevokeTokenResp.java @@ -0,0 +1,23 @@ +package cokr.xit.ci.api.service.support.rest.model.conf; + +import com.fasterxml.jackson.annotation.JsonAlias; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Getter +@ToString +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "DataBodyRevokeTokenResp", description = "토큰폐기 API 응답DataBody") +public class DataBodyRevokeTokenResp { + + @JsonAlias({"result"}) + @Schema(required = true, title = "토큰폐기 여부", example = " ", description = "폐기여부(true: 폐기 성공, false: 폐기 실패)") + private Boolean result; + +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodySymkeyResp.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodySymkeyResp.java new file mode 100644 index 0000000..ec0b6b2 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataBodySymkeyResp.java @@ -0,0 +1,48 @@ +package cokr.xit.ci.api.service.support.rest.model.conf; + +import com.fasterxml.jackson.annotation.JsonAlias; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Getter +@ToString +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "DataBodySymkeyResp", description = "대칭키 요청 API 응답DataBody") +public class DataBodySymkeyResp { + + @JsonAlias({"rsp_cd"}) + @Schema(required = true, title = "dataBody 응답코드", example = " ", description = "dataBody 정상처리여부 (P000 성공, 이외 모두 오류)") + private String rspCd; + + @JsonAlias({"res_msg"}) + @Schema(required = false, title = "dataBody 응답메시지", example = " ", description = "rsp_cd가 \"EAPI\"로 시작될 경우 오류 메시지 세팅") + private String resMsg; + + @JsonAlias({"result_cd"}) + @Schema(required = false, title = "상세결과코드", example = " ", description = "rsp_cd가 P000일 때 상세결과코드(0000:대칭키발급, 0001:공개키기간만료, 0002:공개키를 찾을 수 없음, 0003:공개키를 발급한 회원사 아님, 0004:복호화 오류, 0005:필수입력값 오류(key_version, key_info 필수값 확인), 0006:대칭키 등록 가능 회원사 아님, 0007:key 중복 오류(현재 및 직전에 사용한 Key 사용 불가), 0008:요청사이트코드와 공개키발급 사이트코드 다름, 0099: 기타오류)") + private String resultCd; + + @JsonAlias({"symkey_stat_info"}) + @Schema(required = false, title = "JSON값", example = " ", description = "JSON값(회원사에 생성되어 있는 대칭키 버전별 유효기간(result_cd 가 0000, 0007일 경우에 나감)") + private SymkeyStatInfo symkeyStatInfo; + + + + /* =========================================================================== + * 아이핀CI API 요청 시 필요한 값 + =========================================================================== */ + @Setter + private String siteCode; //[필수]사이트코드 + @Setter + private String requestNo; //[필수]요청고유번호(이용기관에서 임의 생성한 값) + @Setter + private String key; //[필수]회원사에서 사용할 암호화 KEY + @Setter + private String iv; //[필수]Initial Vector값 + @Setter + private String hmacKey; //[필수]회원사에서 사용한 HMAC KEY + +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataHeader.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataHeader.java new file mode 100644 index 0000000..51b2540 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/DataHeader.java @@ -0,0 +1,30 @@ +package cokr.xit.ci.api.service.support.rest.model.conf; + +import com.fasterxml.jackson.annotation.JsonAlias; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class DataHeader { + @NotEmpty(message = "응답코드 값이 없습니다.") + @Schema(required = true, title = "응답코드", example = "1200", description = "정상:1200 그외는 오류") + @JsonAlias({"GW_RSLT_CD"}) + private String GW_RSLT_CD; + + @NotEmpty(message = "응답메시지 값이 없습니다.") + @Schema(required = true, title = "응답메시지", example = " ", description = "한글 또는 영문") + @JsonAlias({"GW_RSLT_MSG"}) + private String GW_RSLT_MSG; + + @Schema(required = false, title = "교환ID", example = " ", description = "API 호출 시 요청한 값 그대로 리턴(적용대상: 공개키/대칭키/아이핀CI 요청API)") + @JsonAlias({"TRAN_ID"}) + private String TRAN_ID; +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/EncData.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/EncData.java index a34834e..e21c546 100644 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/EncData.java +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/EncData.java @@ -1,29 +1,32 @@ package cokr.xit.ci.api.service.support.rest.model.conf; -import cokr.xit.ci.api.service.support.rest.model.TransDTO; + import com.fasterxml.jackson.annotation.JsonAlias; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; @Getter @ToString @Builder -public class EncData implements TransDTO { - - @JsonAlias({"result_cd"}) - private String resultCd; //rsp_cd가 P000일때 상세결과코드(0000:처리완료, 0001:대칭키 기간 만료, 0002:대칭키를 찾을 수 없음, 0003:대칭키를 발급한 회원사 아님, 0004:복호화 오류, 0005:필수입력값 오류(integrity_value, enc_data 내 필수값 확인), 0006:데이터 무결성 오류(hmac값 불일치), 0007:정보요청유형 입력값 오류(info_req_type이 1 아님), 0008:주민번호 유효성 오류(생년월일 유효성 및 숫자 아님), 0009:거래요청시간 포멧오류(req_dtim 자릿수 및 숫자 아님), 0099:기타오류) +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "EncData", description = "아이핀 CI 요청 응답DTO의 JSON 데이터") +public class EncData { @JsonAlias({"ci1"}) - private String ci1; //연계정보1(Connection Info로 다른 웹사이트간 고객확인용으로 사용) + @Schema(required = false, title = "연계정보1", example = " ", description = "Connection Info로 다른 웹사이트간 고객확인용으로 사용") + private String ci1; @JsonAlias({"ci2"}) - private String ci2; //연계정보2(연계정보1의 Key 유출에 대비한 예비값) + @Schema(required = false, title = "연계정보2", example = " ", description = "연계정보1의 Key 유출에 대비한 예비값") + private String ci2; @JsonAlias({"updt_cnt"}) - private String updtCnt; //갱신횟수(연계정보 Key 유출될 경우 갱신 횟수(초기값 1세팅)) + @Schema(required = false, title = "갱신횟수", example = " ", description = "연계정보 Key 유출될 경우 갱신 횟수(초기값 1세팅)") + private String updtCnt; @JsonAlias({"tx_unique_no"}) - private String txUniqueNo; //거래고유번호(result_cd가 0000일 경우 NICE에서 제공하는 거래일련번호) + @Schema(required = false, title = "거래고유번호", example = " ", description = "result_cd가 0000일 경우 NICE에서 제공하는 거래일련번호") + private String txUniqueNo; } diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/SymkeyStatInfo.java b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/SymkeyStatInfo.java index 61963ea..356b864 100644 --- a/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/SymkeyStatInfo.java +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/model/conf/SymkeyStatInfo.java @@ -1,27 +1,33 @@ package cokr.xit.ci.api.service.support.rest.model.conf; -import cokr.xit.ci.api.service.support.rest.model.TransDTO; + import com.fasterxml.jackson.annotation.JsonAlias; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; @Getter @ToString @Builder -public class SymkeyStatInfo implements TransDTO { +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "SymkeyStatInfo", description = "대칭키 요청 API 응답DTO의 JSON 데이터") +public class SymkeyStatInfo { @JsonAlias({"cur_symkey_version"}) - private String curSymkeyVersion; //현재 등록요청한 대칭키 버전 + @Schema(required = false, title = "현재 대칭키 버전", example = " ", description = "현재 등록요청한 대칭키 버전") + private String curSymkeyVersion; @JsonAlias({"cur_valid_dtim"}) - private String curValidDtim; //현재 등록된 대칭키 만료일시(YYYYMMDDHH24MISS) + @Schema(required = false, title = "현재 대칭키 만료일시", example = " ", description = "현재 등록된 대칭키 만료일시(YYYYMMDDHH24MISS)") + private String curValidDtim; @JsonAlias({"bef_symkey_version"}) - private String befSymkeyVersion; //이전 등록된 대칭키 버전 + @Schema(required = false, title = "이전 대칭키 버전", example = " ", description = "이전 등록된 대칭키 버전") + private String befSymkeyVersion; @JsonAlias({"bef_valid_dtim"}) - private String befValidDtim; //이전 등록된 대칭키 만료일시(YYYYMMDDHH24MISS) + @Schema(required = false, title = "이전 대칭키 만료일시", example = " ", description = "이전 등록된 대칭키 만료일시(YYYYMMDDHH24MISS)") + private String befValidDtim; } diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/utils/PublicKey.java b/src/main/java/cokr/xit/ci/api/service/support/rest/utils/PublicKey.java new file mode 100644 index 0000000..5800989 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/utils/PublicKey.java @@ -0,0 +1,62 @@ +package cokr.xit.ci.api.service.support.rest.utils; + +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyPubkeyResp; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class PublicKey { + + + private volatile static PublicKey instance; + private static DataBodyPubkeyResp data; + +// private PubkeyInfo() { +// +// } +// +// public static PubkeyInfo getInstance() { +// if (instance == null) +// synchronized (PubkeyInfo.class) { +// if (instance == null) +// instance = new PubkeyInfo(); +// } +// +// return instance; +// } + + private PublicKey(DataBodyPubkeyResp data) { + this.data = data; + } + + public static PublicKey getInstance() { + return instance; + } + public static PublicKey getInstance(DataBodyPubkeyResp data) { + if(!isValidStat()) + synchronized (PublicKey.class) { + if(!isValidStat()) + instance = new PublicKey(data); + } + + return instance; + } + + + public static boolean isValidStat() { + if (instance == null) + return false; + if (data == null) + return false; + if (Long.parseLong(data.getValidDtim()) < Long.parseLong(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))) + return false; + + return true; + } + + + public DataBodyPubkeyResp getData() { + return data; + } + +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/utils/SymmetricKey.java b/src/main/java/cokr/xit/ci/api/service/support/rest/utils/SymmetricKey.java new file mode 100644 index 0000000..97e338c --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/utils/SymmetricKey.java @@ -0,0 +1,71 @@ +package cokr.xit.ci.api.service.support.rest.utils; + +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodySymkeyResp; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class SymmetricKey { + + + private volatile static SymmetricKey instance; + private static DataBodySymkeyResp data; + // private static String key; +// private static String version; + private static Long expireDt; + +// private SymkeyInfo() { +// +// } +// +// public static SymkeyInfo getInstance() { +// if (instance == null) +// synchronized (SymkeyInfo.class) { +// if (instance == null) +// instance = new SymkeyInfo(); +// } +// +// return instance; +// } + + private SymmetricKey(DataBodySymkeyResp data) { + this.data = data; +// this.expireDt = Long.parseLong(LocalDateTime.now().plusMonths(5L).format(DateTimeFormatter.ofPattern("yyyyMMdd"))); + this.expireDt = Long.parseLong(LocalDateTime.now().plusDays(1L).format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); + } + + public static SymmetricKey getInstance() { + + return instance; + } + public static SymmetricKey getInstance(DataBodySymkeyResp data) { + if(!isValidStat()) + synchronized (SymmetricKey.class) { + if(!isValidStat()) + instance = new SymmetricKey(data); + } + + return instance; + } + + + public static boolean isValidStat() { + if (instance == null) + return false; + if (data == null) + return false; + if (expireDt < Long.parseLong(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))) + return false; + + return true; + } + + + public DataBodySymkeyResp getData() { + return data; + } + + public Long getExpireDe(){ + return expireDt; + } +} diff --git a/src/main/java/cokr/xit/ci/api/service/support/rest/utils/Token.java b/src/main/java/cokr/xit/ci/api/service/support/rest/utils/Token.java new file mode 100644 index 0000000..79589a7 --- /dev/null +++ b/src/main/java/cokr/xit/ci/api/service/support/rest/utils/Token.java @@ -0,0 +1,62 @@ +package cokr.xit.ci.api.service.support.rest.utils; + +import cokr.xit.ci.api.service.support.rest.model.conf.DataBodyGenerateTokenResp; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class Token { + + + private volatile static Token instance; + private static DataBodyGenerateTokenResp data; + +// private PubkeyInfo() { +// +// } +// +// public static PubkeyInfo getInstance() { +// if (instance == null) +// synchronized (PubkeyInfo.class) { +// if (instance == null) +// instance = new PubkeyInfo(); +// } +// +// return instance; +// } + + private Token(DataBodyGenerateTokenResp data) { + this.data = data; + } + + public static Token getInstance() { + return instance; + } + public static Token getInstance(DataBodyGenerateTokenResp data) { + if(!isValidStat()) + synchronized (Token.class) { + if(!isValidStat()) + instance = new Token(data); + } + + return instance; + } + + + public static boolean isValidStat() { + if (instance == null) + return false; + if (data == null) + return false; + if (Long.parseLong(data.getExpiredDt()) < Long.parseLong(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))) + return false; + + return true; + } + + + public DataBodyGenerateTokenResp getData() { + return data; + } + +} diff --git a/src/main/java/cokr/xit/ci/core/config/JpaConfig.java b/src/main/java/cokr/xit/ci/core/config/JpaConfig.java new file mode 100644 index 0000000..512d7d3 --- /dev/null +++ b/src/main/java/cokr/xit/ci/core/config/JpaConfig.java @@ -0,0 +1,8 @@ +package cokr.xit.ci.core.config; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@EnableJpaRepositories(basePackages = "cokr.xit.ci") +public class JpaConfig { +} diff --git a/src/main/java/cokr/xit/ci/core/utils/DateUtil.java b/src/main/java/cokr/xit/ci/core/utils/DateUtil.java index 9a9469b..b458f3b 100644 --- a/src/main/java/cokr/xit/ci/core/utils/DateUtil.java +++ b/src/main/java/cokr/xit/ci/core/utils/DateUtil.java @@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j; import java.text.SimpleDateFormat; import java.time.LocalDateTime; +import java.util.Calendar; import java.util.Date; import java.util.Optional; @@ -180,4 +181,66 @@ public class DateUtil { ); } + + /** + * 날짜(date) 사이의 간격을 계산하여 일자(day)로 반환 한다. + * @param fromDate + * @param toDate + * @return + */ + public static int daysByFromBetweenTo(Date fromDate, Date toDate){ + return daysByFromBetweenTo(fromDate.getTime(), toDate.getTime()); + } + /** + * 초(sec) 사이의 간격을 계산하여 일자(day)로 반환 한다. + * @param fromSec + * @param toSec + * @return + */ + public static int daysByFromBetweenTo(int fromSec, int toSec){ + return daysByFromBetweenTo(fromSec*1000L, toSec*1000L); + } + /** + * 밀리초(ms) 사이의 간격을 계산하여 일자(day)로 반환 한다. + * @param fromMs + * @param toMs + * @return + */ + public static int daysByFromBetweenTo(long fromMs, long toMs){ + return (int) ((fromMs - toMs)/(24*60*60*1000)); + } + + + /** + * 초(sec)를 일수(day)로 반환 한다. + * @return + */ + public static int secToDays(int sec){ + return msToDays(sec*1000L); + } + /** + * 밀리초(ms)를 일수(day)로 반환 한다. + * @return + */ + public static int msToDays(long ms){ + return (int) (ms/(24*60*60*1000)); + } + + + /** + * 현재날짜에 초(sec)를 더한 날짜를 반환 한다. + * @param sec + * @param pattern + * @return + */ + public static String addSec(int sec, String pattern){ + if("".equals(pattern)||pattern==null) + pattern = "yyyyMMdd"; + + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis((sec*1000L) + (new Date().getTime())); + Date date = calendar.getTime(); + + return new SimpleDateFormat(pattern).format(date); + } } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 406df70..89b8d46 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -12,20 +12,3 @@ spring: config: activate: on-profile: local - - -# ===================================================== -# NICE api 계약정보 -# ===================================================== -nice: - ci: - socket: - site-code: GI72 - site-pw: "00000000" - api: - host: https://svc.niceapi.co.kr:22001 - generate-token: /digital/niceid/oauth/oauth/token - revoke-token: /digital/niceid/oauth/oauth/revokeById - publickey: /digital/niceid/api/v1.0/common/crypto/publickey - symmetrickey: /digital/niceid/api/v1.0/common/crypto/symmetrickey - ci: /digital/niceid/cert/v1.0/ipin/addinfo/ci diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 51e058f..90f6c12 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -13,21 +13,3 @@ spring: activate: on-profile: prod - -# ===================================================== -# NICE api 계약정보 -# ===================================================== -nice: - ci: - socket: - site-code: GI72 - site-pw: "00000000" - api: - host: https://svc.niceapi.co.kr:22001 - generate-token: /digital/niceid/oauth/oauth/token - revoke-token: /digital/niceid/oauth/oauth/revokeById - publickey: /digital/niceid/api/v1.0/common/crypto/publickey - symmetrickey: /digital/niceid/api/v1.0/common/crypto/symmetrickey - ci: /digital/niceid/cert/v1.0/ipin/addinfo/ci - - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 709a98b..0b95194 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,6 +14,39 @@ spring: pid: file: app-ci.pid + # =================================================================================================================================== + # Database 설정 + # =================================================================================================================================== + h2: + console: + enabled: true + path: /app-ci-db + sql: + init: + # DB 초기화(always / never). Embeded 인메모리DB는 기본적으로 초기화를 수행한다. + mode: always + # Database 벤더(h2,oracle,mysql,postgresql,...) + platform: h2 + datasource: +# url: "jdbc:h2:tcp://localhost:19092/~/ens" +# url: jdbc:h2:mem:xitdb?rewriteBatchedStatements=true&profileSQL=true&logger=Slf4JLogger&maxQuerySizeToLog=999999 + url: jdbc:h2:file:c:/data/app-ci-db;AUTO_SERVER=TRUE + driver-class-name: org.h2.Driver + username: sa + password: + hikari: + driver-class-name: ${spring.datasource.driver-class-name} + jdbc-url: ${spring.datasource.url} + username: ${spring.datasource.username} + password: ${spring.datasource.password} + jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + # create / create-drop / update / validate / none + ddl-auto: update + open-in-view: true + + # ===================================================== # Logging 설정 # ===================================================== @@ -30,3 +63,25 @@ logging: root: info '[org.hibernate.sql]': info + +# ===================================================== +# NICE api 계약정보 +# ===================================================== +contract: + nice: + ci: + type: rest + socket: + site-code: GI72 + site-pw: "00000000" + rest: + host: https://svc.niceapi.co.kr:22001 + client-id: "6c3eb1ff-530d-458a-9a6e-e02e3346f679" + client-secret: "960f204ec45bb312b7ad2d6b54b984d9c353b8" + api: + generate-token: /digital/niceid/oauth/oauth/token + revoke-token: /digital/niceid/oauth/oauth/token/revokeById + publickey: /digital/niceid/api/v1.0/common/crypto/publickey + symmetrickey: /digital/niceid/api/v1.0/common/crypto/symmetrickey + ci: /digital/niceid/cert/v1.0/ipin/addinfo/ci +