나이스CI REST API 개발완료
parent
a1e4bac731
commit
ecdd3f83b4
@ -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
|
||||
@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
|
@ -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;
|
||||
|
||||
|
||||
|
||||
}
|
@ -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<NiceCiSymkeyMng, Long> {
|
||||
|
||||
Optional<NiceCiSymkeyMng> findByPubkey(String pubkey);
|
||||
}
|
@ -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<String> 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<DataBodyGenerateTokenResp> tokenResponseVO = executor.token();
|
||||
if (!NiceCiApiCd.OK.equals(tokenResponseVO.getErrCode()))
|
||||
return ResponseVO.<String>builder().errCode(ErrCd.ERR600).errMsg(tokenResponseVO.getErrCode().getCode() + " " + tokenResponseVO.getErrMsg()).build();
|
||||
|
||||
/* ==========================================================================
|
||||
* 2. 공개키 요청
|
||||
========================================================================== */
|
||||
NiceCiRespVO<DataBodyPubkeyResp> pubkeyResponseVO = executor.pubkey();
|
||||
if (!NiceCiApiCd.OK.equals(pubkeyResponseVO.getErrCode()))
|
||||
return ResponseVO.<String>builder().errCode(ErrCd.ERR600).errMsg(pubkeyResponseVO.getErrCode().getCode() + " " + pubkeyResponseVO.getErrMsg()).build();
|
||||
|
||||
/* ==========================================================================
|
||||
* 3. 대칭키 등록 요청
|
||||
* -. 대칭키는 1일 1회만 등록이 가능하며, 1일 2회 이상 등록요청 시 "0099 기타오류" 가 발생 하므로
|
||||
* -. 등록요청에 성공한 대칭키는 DB에 저장하여, 서버 재기동 시에도 휘발되지 않도록 한다.
|
||||
========================================================================== */
|
||||
// NiceCiRespVO<DataBodySymkeyResp> symkeyResponseVO = executor.symkey();
|
||||
// if (!NiceCiApiCd.OK.equals(symkeyResponseVO.getErrCode()))
|
||||
// return ResponseVO.<String>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> niceCiSymkeyMng = niceCiSymkeyMngRepository.findByPubkey(pubkeyResponseVO.getResultInfo().getPublicKey());
|
||||
|
||||
if (niceCiSymkeyMng.isPresent()) {
|
||||
dataBodySymkeyResp = mapper.readValue(niceCiSymkeyMng.get().getRespJsonData(), DataBodySymkeyResp.class);
|
||||
|
||||
//대칭키 싱글톤 객체 초기화
|
||||
SymmetricKey.getInstance(dataBodySymkeyResp);
|
||||
|
||||
} else {
|
||||
//3. 대칭키 등록 요청
|
||||
NiceCiRespVO<DataBodySymkeyResp> symkeyResponseVO = executor.symkey();
|
||||
if (!NiceCiApiCd.OK.equals(symkeyResponseVO.getErrCode()))
|
||||
return ResponseVO.<String>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<DataBodyCiResp> ciResponseVO = executor.ci(jid, clientIp);
|
||||
if (!NiceCiApiCd.OK.equals(pubkeyResponseVO.getErrCode()))
|
||||
return ResponseVO.<String>builder().errCode(ErrCd.ERR600).errMsg(ciResponseVO.getErrCode().getCode() + " " + ciResponseVO.getErrMsg()).build();
|
||||
|
||||
|
||||
return ResponseVO.<String>builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(ciResponseVO.getResultInfo().getDecEncData().getCi1()).build();
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.<String>builder().errCode(ErrCd.ERR699).errMsg(e.getMessage()).build();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Map<String, Object>> {
|
||||
|
||||
@Override @SuppressWarnings("unchecked")
|
||||
public Map<String, Object> deserialize(JsonElement json, Type typeOfT,
|
||||
JsonDeserializationContext context) throws JsonParseException {
|
||||
return (Map<String, Object>) read(json);
|
||||
}
|
||||
|
||||
public Object read(JsonElement in) {
|
||||
if(in.isJsonArray()){ //JsonArray 이면...
|
||||
//JsonArray인 경우
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
JsonArray arr = in.getAsJsonArray();
|
||||
for (JsonElement anArr : arr) {
|
||||
//JsonPrimitive 나올 떄까지 for문
|
||||
list.add(read(anArr));
|
||||
}
|
||||
return list;
|
||||
}else if(in.isJsonObject()){ //JsonObject 이면...
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
JsonObject obj = in.getAsJsonObject();
|
||||
Set<Map.Entry<String, JsonElement>> entitySet = obj.entrySet();
|
||||
for(Map.Entry<String, JsonElement> 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;
|
||||
}
|
||||
}
|
@ -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<GenerateTokenRespDTO> 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<String, String> mParam = new HashMap<>();
|
||||
mParam.put("grant_type", "client_credentials");
|
||||
mParam.put("scope", "default");
|
||||
String jsonStr = gson.toJson(mParam);
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* API 호출
|
||||
============================================================================== */
|
||||
ResponseEntity<String> 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<String, Object> 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.<GenerateTokenRespDTO>builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build();
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.<GenerateTokenRespDTO>builder().errCode(errCd).errMsg(errMsg + " " + errMsg).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//@Override
|
||||
public ResponseVO<RevokeTokenRespDTO> 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<String> 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.<RevokeTokenRespDTO>builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build();
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.<RevokeTokenRespDTO>builder().errCode(errCd).errMsg(e.getMessage() + " " + errMsg).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//@Override
|
||||
public ResponseVO<PublickeyRespDTO> 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<String, String> mParam = new HashMap<>();
|
||||
mParam.put("req_dtim", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
|
||||
String jsonStr = gson.toJson(mParam);
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* API 호출
|
||||
============================================================================== */
|
||||
ResponseEntity<String> 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.<PublickeyRespDTO>builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build();
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.<PublickeyRespDTO>builder().errCode(errCd).errMsg(e.getMessage() + " " + errMsg).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//@Override
|
||||
public ResponseVO<SymmetrickeyRespDTO> 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<String, String> mParam = new HashMap<>();
|
||||
mParam.put("pubkey_version", pubkeyVersion);
|
||||
mParam.put("symkey_reg_info", symkeyRegInfo);
|
||||
String jsonStr = gson.toJson(mParam);
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* API 호출
|
||||
============================================================================== */
|
||||
ResponseEntity<String> 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.<SymmetrickeyRespDTO>builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build();
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.<SymmetrickeyRespDTO>builder().errCode(errCd).errMsg(e.getMessage() + " " + errMsg).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//@Override
|
||||
public ResponseVO<CiRespDTO> 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<String, String> 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<String> 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.<CiRespDTO>builder().errCode(ErrCd.OK).errMsg(ErrCd.OK.getCodeNm()).resultInfo(resultInfo).build();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResponseVO.<CiRespDTO>builder().errCode(errCd).errMsg(e.getMessage() + " " + errMsg).build();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <pre>메소드 설명: API 호출
|
||||
* </pre>
|
||||
*
|
||||
* @param method
|
||||
* @param url
|
||||
* @param body
|
||||
* @param headers
|
||||
* @return ResponseEntity 요청처리 후 응답객체
|
||||
* @author: 박민규
|
||||
*/
|
||||
private ResponseEntity<String> callApi(HttpMethod method, String url, String body, HttpHeaders headers) {
|
||||
log.debug("param =======================");
|
||||
log.debug(body);
|
||||
|
||||
|
||||
ResponseEntity<String> 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<String>(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<String>(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<String>(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<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
log.error(String.format("call API 기타오류[url =>%s param => %s error => %s]", url, body, e.getMessage()));
|
||||
}
|
||||
|
||||
//결과 응답
|
||||
return responseEntity;
|
||||
}
|
||||
|
||||
}
|
@ -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<DataBodyGenerateTokenResp> token() throws Exception {
|
||||
NiceCiRespVO<DataBodyGenerateTokenResp> result = null;
|
||||
if (Token.isValidStat()) {
|
||||
result = NiceCiRespVO.<DataBodyGenerateTokenResp>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<DataBodyPubkeyResp> pubkey() throws Exception {
|
||||
NiceCiRespVO<DataBodyPubkeyResp> result = null;
|
||||
if (PublicKey.isValidStat()) {
|
||||
result = NiceCiRespVO.<DataBodyPubkeyResp>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<DataBodySymkeyResp> symkey() throws Exception {
|
||||
NiceCiRespVO<DataBodySymkeyResp> result = null;
|
||||
if(SymmetricKey.isValidStat()){
|
||||
result = NiceCiRespVO.<DataBodySymkeyResp>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<DataBodyCiResp> ci(String jid, String clientIp) throws Exception {
|
||||
|
||||
NiceCiRespVO<DataBodyCiResp> result = CiRequest.builder()
|
||||
.HOST(this.HOST)
|
||||
.API_URL(this.API_CI)
|
||||
.build()
|
||||
.execute(this.CLIENT_ID, this.CLIENT_SECRET, jid, clientIp);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
|
||||
}
|
@ -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<DataBodyCiResp> execute(String clientId, String clientSecret, String jumin, String clientIp) throws Exception {
|
||||
/* ==============================================================================
|
||||
* 유효성 확인
|
||||
============================================================================== */
|
||||
if (StringUtils.isEmpty(clientId))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(clientSecret))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트비밀번호는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getKey()))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("대칭키는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getSymkeyStatInfo().getCurSymkeyVersion()))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("대칭키 버전은 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getSiteCode()))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("사이트코드는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getRequestNo()))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("신청번호는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getIv()))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("IV는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(SymmetricKey.getInstance().getData().getHmacKey()))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("HmacKey는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(jumin))
|
||||
return NiceCiRespVO.<DataBodyCiResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("주민번호는 필수 입력값 입니다.").build();
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* API 호출
|
||||
============================================================================== */
|
||||
NiceCiRespDTO<DataBodyCiResp> responseDTO = this.getCi(clientId, clientSecret, SymmetricKey.getInstance().getData(), jumin, clientIp);
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* 결과 반환
|
||||
============================================================================== */
|
||||
if (!"1200".equals(responseDTO.getDataHeader().getGW_RSLT_CD()))
|
||||
return NiceCiRespVO.<DataBodyCiResp>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.<DataBodyCiResp>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.<DataBodyCiResp>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.<DataBodyCiResp>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.<DataBodyCiResp>okBuilder().resultInfo(responseDTO.getDataBody()).build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected NiceCiRespDTO<DataBodyCiResp> 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<String> 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<DataBodyCiResp> respDTO = mapper.readValue(strRespBody, new TypeReference<NiceCiRespDTO<DataBodyCiResp>>(){});
|
||||
|
||||
|
||||
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<String, String> dataHeader = new HashMap<>();
|
||||
dataHeader.put("CNTY_CD", "ko"); //[필수]이용언어: ko, en, cn ...
|
||||
dataHeader.put("TRAN_ID", null); //[선택]API 통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호
|
||||
|
||||
Map<String, String> dataBody = new HashMap<>();
|
||||
dataBody.put("symkey_version", symkeyVersion); //[선택]대칭키 버전
|
||||
dataBody.put("enc_data", encData); //[필수]JSON암호화 값
|
||||
dataBody.put("integrity_value", integrityValue); //[선택]무결성체크를 위해 enc_data를 HMAC처리 후, Base64 인코딩한 값
|
||||
|
||||
Map<String, Object> 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<String, String> 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* <pre>메소드 설명: API 호출
|
||||
* </pre>
|
||||
*
|
||||
* @param method
|
||||
* @param url
|
||||
* @param body
|
||||
* @param headers
|
||||
* @return ResponseEntity 요청처리 후 응답객체
|
||||
* @author: 박민규
|
||||
*/
|
||||
protected final ResponseEntity<String> callApi(HttpMethod method, String url, Object body, HttpHeaders headers) {
|
||||
|
||||
ResponseEntity<String> 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<String>(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<String>(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<String>(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<String>(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();
|
||||
|
||||
}
|
||||
}
|
@ -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<DataBodyPubkeyResp> execute(String clientId) throws Exception {
|
||||
/* ==============================================================================
|
||||
* 유효성 확인
|
||||
============================================================================== */
|
||||
if (StringUtils.isEmpty(Token.getInstance().getData().getAccessToken()))
|
||||
return NiceCiRespVO.<DataBodyPubkeyResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("엑세스토큰은 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(clientId))
|
||||
return NiceCiRespVO.<DataBodyPubkeyResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build();
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* API 호출
|
||||
============================================================================== */
|
||||
NiceCiRespDTO<DataBodyPubkeyResp> responseDTO = this.generatePublickey(Token.getInstance().getData().getAccessToken(), clientId);
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* 결과 반환
|
||||
============================================================================== */
|
||||
if (!"1200".equals(responseDTO.getDataHeader().getGW_RSLT_CD()))
|
||||
return NiceCiRespVO.<DataBodyPubkeyResp>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.<DataBodyPubkeyResp>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.<DataBodyPubkeyResp>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.<DataBodyPubkeyResp>okBuilder().resultInfo(responseDTO.getDataBody()).build();
|
||||
}
|
||||
|
||||
|
||||
protected NiceCiRespDTO<DataBodyPubkeyResp> 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<String> 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<DataBodyPubkeyResp> respDTO = mapper.readValue(resp.getBody(), NiceCiRespDTO.class);
|
||||
String strRespBody = resp.getBody().replace("\"\"", "null");
|
||||
NiceCiRespDTO<DataBodyPubkeyResp> respDTO = mapper.readValue(strRespBody, new TypeReference<NiceCiRespDTO<DataBodyPubkeyResp>>(){});
|
||||
|
||||
|
||||
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<String, String> dataHeader = new HashMap<>();
|
||||
dataHeader.put("CNTY_CD", "ko"); //[필수]이용언어: ko, en, cn ...
|
||||
dataHeader.put("TRAN_ID", null); //[선택]API 통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호
|
||||
|
||||
Map<String, String> dataBody = new HashMap<>();
|
||||
dataBody.put("req_dtim", reqDtim); //[필수]공개키 요청일시(YYYYMMDDHH24MISS)
|
||||
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("dataHeader", dataHeader);
|
||||
m.put("dataBody", dataBody);
|
||||
|
||||
return mapper.writeValueAsString(m);
|
||||
}
|
||||
}
|
@ -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<DataBodySymkeyResp> execute(String clientId) throws Exception {
|
||||
/* ==============================================================================
|
||||
* 유효성 확인
|
||||
============================================================================== */
|
||||
if (StringUtils.isEmpty(Token.getInstance().getData().getAccessToken()))
|
||||
return NiceCiRespVO.<DataBodySymkeyResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("엑세스토큰은 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(clientId))
|
||||
return NiceCiRespVO.<DataBodySymkeyResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(PublicKey.getInstance().getData().getSiteCode()))
|
||||
return NiceCiRespVO.<DataBodySymkeyResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("사이트코드는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(PublicKey.getInstance().getData().getPublicKey()))
|
||||
return NiceCiRespVO.<DataBodySymkeyResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("공개키는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(PublicKey.getInstance().getData().getKeyVersion()))
|
||||
return NiceCiRespVO.<DataBodySymkeyResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("공개키버전은 필수 입력값 입니다.").build();
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* API 호출
|
||||
============================================================================== */
|
||||
NiceCiRespDTO<DataBodySymkeyResp> 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.<DataBodySymkeyResp>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.<DataBodySymkeyResp>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.<DataBodySymkeyResp>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.<DataBodySymkeyResp>okBuilder().resultInfo(responseDTO.getDataBody()).build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected NiceCiRespDTO<DataBodySymkeyResp> 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<String> 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<DataBodySymkeyResp> respDTO = mapper.readValue(resp.getBody(), NiceCiRespDTO.class);
|
||||
String strRespBody = resp.getBody().replace("\"\"", "null");
|
||||
strRespBody = strRespBody.replace("\\", "").replace("\"{", "{").replace("}\"", "}"); //symkey_stat_info 데이터의 쌍따옴표를 제거하여 Json데이터로 인식하도록 replace 적용
|
||||
NiceCiRespDTO<DataBodySymkeyResp> respDTO = mapper.readValue(strRespBody, new TypeReference<NiceCiRespDTO<DataBodySymkeyResp>>(){});
|
||||
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<String, String> 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개월 내 키등록이 없으면 암복호화 오류 발생
|
||||
* <p>
|
||||
* [예시]
|
||||
* 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<String, String> dataHeader = new HashMap<>();
|
||||
dataHeader.put("CNTY_CD", "ko"); //[필수]이용언어: ko, en, cn ...
|
||||
dataHeader.put("TRAN_ID", null); //[선택]API 통신구간에서 요청에 대한 응답을 확인하기 위한 고유번호
|
||||
|
||||
Map<String, String> dataBody = new HashMap<>();
|
||||
dataBody.put("pubkey_version", pubkeyVersion); //[필수]공개키 버전
|
||||
dataBody.put("symkey_reg_info", symkeyRegInfo); //[필수]JSON암호화 값
|
||||
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("dataHeader", dataHeader);
|
||||
m.put("dataBody", dataBody);
|
||||
|
||||
return mapper.writeValueAsString(m);
|
||||
}
|
||||
}
|
@ -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<DataBodyGenerateTokenResp> execute(String clientId, String clientSecret) throws Exception {
|
||||
/* ==============================================================================
|
||||
* 유효성 확인
|
||||
============================================================================== */
|
||||
if (StringUtils.isEmpty(clientId))
|
||||
return NiceCiRespVO.<DataBodyGenerateTokenResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트ID는 필수 입력값 입니다.").build();
|
||||
if (StringUtils.isEmpty(clientSecret))
|
||||
return NiceCiRespVO.<DataBodyGenerateTokenResp>errBuilder().errCode(NiceCiApiCd.FAIL).errMsg("클라이언트비밀번호는 필수 입력값 입니다.").build();
|
||||
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* API 호출
|
||||
============================================================================== */
|
||||
NiceCiRespDTO<DataBodyGenerateTokenResp> 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.<DataBodyGenerateTokenResp>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<DataBodyRevokeTokenResp> revokeResponseDTO = this.revokeToken(generateResponseDTO.getDataBody().getAccessToken(), clientId);
|
||||
if (!"1200".equals(revokeResponseDTO.getDataHeader().getGW_RSLT_CD()))
|
||||
return NiceCiRespVO.<DataBodyGenerateTokenResp>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.<DataBodyGenerateTokenResp>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.<DataBodyGenerateTokenResp>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.<DataBodyGenerateTokenResp>okBuilder().resultInfo(generateResponseDTO.getDataBody()).build();
|
||||
}
|
||||
|
||||
|
||||
protected NiceCiRespDTO<DataBodyGenerateTokenResp> 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<String, String> body = this.createMessage();
|
||||
|
||||
|
||||
/* ==============================================================================
|
||||
* API 호출
|
||||
============================================================================== */
|
||||
ResponseEntity<String> 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<DataBodyGenerateTokenResp> respDTO = mapper.readValue(resp.getBody(), NiceCiRespDTO.class);
|
||||
String strRespBody = resp.getBody().replace("\"\"", "null");
|
||||
NiceCiRespDTO<DataBodyGenerateTokenResp> respDTO = mapper.readValue(strRespBody, new TypeReference<NiceCiRespDTO<DataBodyGenerateTokenResp>>(){});
|
||||
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<DataBodyRevokeTokenResp> 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<String> 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<DataBodyRevokeTokenResp> respDTO = mapper.readValue(resp.getBody(), NiceCiRespDTO.class);
|
||||
String strRespBody = resp.getBody().replace("\"\"", "null");
|
||||
NiceCiRespDTO<DataBodyRevokeTokenResp> respDTO = mapper.readValue(strRespBody, new TypeReference<NiceCiRespDTO<DataBodyRevokeTokenResp>>(){});
|
||||
|
||||
|
||||
return respDTO;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new Exception("토큰폐기 요청 실패." + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JSON 포맷의 요청메시지 생성
|
||||
*
|
||||
* @return
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
protected MultiValueMap<String, String> createMessage() throws JsonProcessingException {
|
||||
MultiValueMap<String, String> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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 인코딩한 값
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
@ -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<T> {
|
||||
|
||||
|
||||
private DataHeader dataHeader;
|
||||
|
||||
private T dataBody;
|
||||
|
||||
}
|
@ -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<T> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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: 폐기 실패)
|
||||
|
||||
}
|
@ -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일 경우에 나감)
|
||||
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package cokr.xit.ci.api.service.support.rest.model;
|
||||
|
||||
public interface TransDTO
|
||||
{
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
Loading…
Reference in New Issue