NICE PASS 인증 API 연동 가이드
🔐 NICE PASS 인증 API 연동 가이드
본 문서는 서버에서 안전하게 암호화 폼 데이터를 핸들링하고 클라이언트와 통신하면서 사용자의 정보를 취득하는 프로세스를 상세히 설명합니다.
🏃 준비하기
(프로젝트가 생성된 상태라고 가정합니다.)
- Nice API에서 SecretKey를 조회합니다.
- 여기서 ClientID, ClientSecret을 얻을 수 있습니다.
- 상세보기에서 구성 상품 섹션으로 이동합니다.
- 여기서 ProductId(상품코드)를 얻을 수 있습니다.
- 다음과 같이 nice.yaml에 저장합니다.
- ⚠️ 추가로, 로컬에서 테스트를 하기 위해서는 APP 세부정보에서 허용할 IP를 명시해야합니다. 배포시에도 마찬가지입니다.
💪 전체 플로우 파악
클라이언트 = 프론트엔드
서버 = 백엔드(우리가 구현할 것) NICE서버 = NICE측 서버
위 플로우를 반드시 숙지해주세요. 각 번호 기준으로 설명합니다.
요청 및 콜백은 직선이고, 요청에 대한 응답은 점선입니다.
1. 기관토큰 신청 & 2. 기관토큰 발급
Nice에서는 기관토큰이 있어야 4, 5, 9, 10번의 요청/응답을 처리할 수 있습니다. 일종의 서버용 인증 토큰이라고 생각하면 됩니다. 50년짜리 반영구 토큰이기 때문에, 한번 발급받아서 환경변수로 사용하는 것이 좋습니다.
앞으로 이 기관토큰(=반영구 토큰)을 AccessToken
이라고 칭하겠습니다.
AccessToken
을 발급받기 위해서는 Nice 측에 요청을 보내야 합니다.
요청 사양은 다음과 같습니다:
요청 메서드: "POST"
요청 URL: "https://svc.niceapi.co.kr:22001/digital/niceid/oauth/oauth/token"
헤더:
- 헤더이름: "Authorization"
접두사: "Basic "
헤더본문: Base64Encoding(${client_id}+":"+${client_secret})
- 헤더이름: "Content-Type"
접두사: ""
헤더본문: "x-www-form-urlencoded"
본문:
데이터 목록:
scope: "default"
grant_type: "client_credentials"
우리에게는 ClientId와 ClientSecret이 있으므로 이제 헤더 본문을 만들어봅시다.
예제로
ClientId : abcd1234
ClientSecret: efgh5678
이라고 가정하고 진행하겠습니다.
echo -n abcd1234:efgh5678 | base64
abcd1234:efgh5678
을 base64 인코딩한 값이
ZmRhODgzNjctNDEwMC00YmQ1LWE4OGYtOGZhOGNhNTEwNjhlOjEwNDliMWRhMTk0NDIxM2NhMmU3MjNiODdkYThjOTE1
이기 때문에, 우리는 다음의 헤더를 추가하면 됩니다.
Authorization: Basic ZmRhODgzNjctNDEwMC00YmQ1LWE4OGYtOGZhOGNhNTEwNjhlOjEwNDliMWRhMTk0NDIxM2NhMmU3MjNiODdkYThjOTE1
-
여기에서 POSTMAN기준 전체 예시를 볼 수 있습니다.
요청을 보내면, 아래와 같이 access_token
을 발급받을 수 있습니다.
발급받은 access_token
을 nice.yaml에 넣어줍니다.
nice:
clientId: abcd1234
clientSecret: efgh5678
productId: 1234567890
accessToken: aaaa1111-aaaa-bbbb-cccc-1234abcd1234 # 반영구 토큰
이로써 1,2 단계가 끝났습니다.
3. 본인인증 요청
3.본인인증 요청으로 클라이언트(프론트엔드)는 7.암호화된 폼 데이터를 받을 수 있습니다.
해당 부분은 프론트쪽 로직이기 때문에 따로 설명은 하지 않겠습니다. 3.에서의 프론트 요청은 단순한 GET 요청으로, 아무런 페이로드가 없습니다.
-
컨트롤러 예시
/** * NICE 본인인증 토큰 발급 API * * 이 메서드는 NICE 본인인증을 위한 암호화 토큰을 발급받는다. * 토큰은 클라이언트가 본인인증 요청 시 사용하며, * 이후 인증 완료 시 해당 토큰을 기반으로 결과를 처리한다. */ @GetMapping("/nice/token") public SuccessResponse<...> niceAuthenticationGetToken( ) { // 인증 요청 일시와 고유 요청 번호를 생성한다 // (구체적인 생성 로직은 service 내부에 구현됨) return SuccessResponse.ok(authenticationService.niceAuthenticationGetToken()); }
해당 요청을 통해 클라이언트는 7.에서
tokenVersionId
encData
integrityValue
세가지 데이터를 얻을 수 있습니다.
4. 암호화 토큰 요청 & 5. 암호화 토큰 발급
이제 우리 서버가 NICE 서버에 암호화 토큰을 요청해야합니다.
이 암호화 토큰은 클라이언트에게 전달되어, 클라이언트가 PASS 인증을 할때 이 암호화 토큰을 함께 NICE 서버로 넘겨 각 개체를 식별하는 역할을 합니다.
암호화 토큰을 요청하기 위해서는 아래의 데이터가 필요합니다.
clientId
: 이미 있음 ✅productId
: 이미 있음 ✅accessToken
: 이미 있음 ✅redirectUrl
: 없음 ⛔️- NICE 에서 유저가 본인인증을 끝맺은 뒤, 유저 정보를 받을 수 있는 서버측 엔드포인트입니다.(콜백)
reqDtim
: 없음 ⛔️- 인증 요청 일시
reqNo
: 없음 ⛔️- 고유 요청 번호로, 서버에서 임의로 생성합니다.
bearerAuth
: 없음 ⛔️clientId
,accessToken
가 있으면 만들 수 있습니다.
이제 하나씩 만들어보겠습니다.
redirectUrl 획득
- redirectUrl은 간단합니다. 우리가 콜백을 받을 우리측 주소를 지정하면 됩니다. 컨트롤러는 나중에 정의하고, 일단 콜백 주소를 지정해줍시다.
- 여기서는 예시로, https://www.example.com/nice/callback 이라고 가정하겠습니다.
reqDtim 획득
-
다음과 같은 방식으로 reqDtim을 얻을 수 있습니다.
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); String reqDtim = LocalDateTime.now().format(dateFormat);
reqNo 획득
-
다음과 같은 방식으로 reqNo를 얻을 수 있습니다.
String reqNo = UUID.randomUUID().toString().replace("-", "").substring(0, 30);
bearerAuth 획득
-
다음과 같은 방식으로 bearerAuth를 얻을 수 있습니다.
long currentTimestamp = System.currentTimeMillis() / 1000; String bearerAuth = "Bearer " + Base64.getEncoder().encodeToString( (NICE_ACCESS_TOKEN + ":" + currentTimestamp + ":" + NICE_CLIENT_ID).getBytes() );
-
네, 맞습니다. currentTimestamp가 들어갑니다. 이 값은 reqDtim과 꼭 같을 필요는 없습니다.
이제 필요한 모든 준비가 끝났으니, Nice 측에 요청을 보내고 암호화 토큰을 받아보겠습니다.
먼저, 토큰 응답 객체를 만들겠습니다. 암호화 토큰 요청에 대한 응답은 다음과 같습니다:
{
"dataHeader": {
"GW_RSLT_CD": "1200",
"GW_RSLT_MSG": "정상 처리되었습니다"
},
"dataBody": {
"siteCode": "EXAMPLE_DATA",
"tokenVersionId": "abc123tokenVersionId",
"tokenVal": "xyz456tokenValue"
}
}
-
저는 이렇게 클래스를 구현했습니다.
@AllArgsConstructor @Getter @Setter @ToString public class NiceAuthenticationTokenInfo { private DataHeader dataHeader; private DataBody dataBody; @AllArgsConstructor @Getter @Setter @ToString public static class DataHeader { @JsonProperty("GW_RSLT_CD") private String gwRsltCd; @JsonProperty("GW_RSLT_MSG") private String gwRsltMsg; } @AllArgsConstructor @Getter @Setter @ToString public static class DataBody { private String siteCode; private String tokenVersionId; private String tokenVal; } }
이제 https://svc.niceapi.co.kr:22001/digital/niceid/api/v1.0/common/crypto/token로 POST 요청을 보내면 됩니다.
// 1. HTTP 요청 생성
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://svc.niceapi.co.kr:22001/digital/niceid/api/v1.0/common/crypto/token"))
.header("Content-Type", "application/json")
.header("Authorization", bearerAuth)
.header("productId", productId)
.POST(HttpRequest.BodyPublishers.ofString(
String.format(
"{\"dataHeader\":{\"CNTY_CD\":\"ko\"},\"dataBody\":{\"req_dtim\":\"%s\",\"req_no\":\"%s\",\"enc_mode\":\"1\"}}",
reqDtim,
reqNo
)
))
.build();
그후 받은 응답을 파싱해서 아까 만든 클래스에 넣어줍니다.
저는 아래와 같이 했습니다.
// 2. 요청 전송 및 응답 수신
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
log.info("### response: {}", response.body());
// 3. 응답 본문 파싱
JSONParser jsonParser = new JSONParser();
JSONObject jsonObj = (JSONObject) jsonParser.parse(response.body());
// 4. dataHeader와 dataBody 추출
JSONObject dataHeaderObj = (JSONObject) jsonObj.get("dataHeader");
Object dataBodyRaw = jsonObj.get("dataBody");
// 5. dataBody가 문자열인 경우 JSON 객체로 다시 파싱
JSONObject dataBodyObj = null;
if (dataBodyRaw instanceof JSONObject) {
dataBodyObj = (JSONObject) dataBodyRaw;
} else if (dataBodyRaw instanceof String && !((String) dataBodyRaw).isBlank()) {
dataBodyObj = (JSONObject) jsonParser.parse((String) dataBodyRaw);
}
// 6. dataHeader 매핑
NiceAuthenticationTokenInfo.DataHeader dataHeader = null;
if (dataHeaderObj != null && !dataHeaderObj.isEmpty()) {
dataHeader = new NiceAuthenticationTokenInfo.DataHeader(
dataHeaderObj.get("GW_RSLT_CD") != null ? dataHeaderObj.get("GW_RSLT_CD").toString() : null,
dataHeaderObj.get("GW_RSLT_MSG") != null ? dataHeaderObj.get("GW_RSLT_MSG").toString() : null
);
}
// 7. dataBody 매핑
NiceAuthenticationTokenInfo.DataBody dataBody = null;
if (dataBodyObj != null && !dataBodyObj.isEmpty()) {
dataBody = new NiceAuthenticationTokenInfo.DataBody(
dataBodyObj.get("site_code") != null ? dataBodyObj.get("site_code").toString() : null,
dataBodyObj.get("token_version_id") != null ? dataBodyObj.get("token_version_id").toString() : null,
dataBodyObj.get("token_val") != null ? dataBodyObj.get("token_val").toString() : null
);
}
이제 응답코드(GW_RSLT_CD)가 정상인지 구분해야합니다. 1200이 아니면 오류가 발생한겁니다.
NiceAuthenticationTokenInfo tokenInfo = ...(생략)
// 응답 코드가 성공(1200)인지 검증한다
if (tokenInfo.getDataHeader() == null || !"1200".equals(tokenInfo.getDataHeader().getGwRsltCd())) {
throw new CustomException(ErrorCode.NICE_CLIENT_ERROR);
}
-
NICE 에서 지정한 GW_RSLT_CD 분류는 다음과 같습니다. 참고하세요.
6. 요청 정보 암호화
이제 여기서부터 어렵습니다.
우리가 받은 토큰 정보를 기반으로 암호화를 해줘야합니다.
다시 세부 단계로 나누어 설명하겠습니다.
[암호화] 1. 키 생성 문자열 생성
NICE에서 규정하는 대칭키 생성 알고리즘은 다음과 같습니다.
![]()
- 요청 일시(
reqDtim
) + 요청 번호(reqNo
) + NICE에서 받은 토큰 값(tokenVal
)을 결합하여 하나의 문자열을 만듭니다.
String result = reqDtim + reqNo + tokenInfo.getDataBody().getTokenVal().trim();
- 그리고 해당 문자열을 SHA-256 해시 함수로 암호화하여
resultVal
을 만듭니다.
public static String encryptSHA256(String result) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(result.getBytes());
byte[] arrHashValue = md.digest();
return Base64.getEncoder().encodeToString(arrHashValue);
}
- 여기서 만든
resultVal
은,reqDtim
,reqNo
,tokenVal
에서 파생된 해시 문자열로, 매시간, 매 요청마다 달라지는 특성이 있습니다. 이것을 이용하여 암호화에 사용할 키를 추출할 것입니다.
[암호화] 2. 암호화 키 추출
resultVal
에서 필요한 만큼 잘라서 각각 다음의 키로 변환합니다.
// AES 암호화에 사용할 키와 IV, 그리고 HMAC 무결성 검증용 키를 추출한다
String key = resultVal.substring(0, 16); // 128비트 AES 키
String iv = resultVal.substring(resultVal.length() - 16); // 128비트 IV (초기화 벡터)
String hmacKey = resultVal.substring(0, 32); // 256비트 HMAC 무결성 키
-
AES는 128비트(16비트), 192비트, 256비트 키 길이를 지원합니다. 우리는 128비트를 사용할것이므로 0 ~ 15 까지의 문자열을 AES키로 사용합니다.
-
IV는 암호화시 동일한 평문이 항상 다른 암호문을 생성하도록 합니다. AES와 같아야하므로 동일하게 16바이트를 사용합니다.
- 마지막 16바이트를 사용하는 이유는 보안상으로 해커에게서 예측 불가능하도록 하기 위해서입니다.
- 동일하게 앞자리 16자리를 사용하면 패턴검출이 쉬워집니다.
- 더 자세한 정보는 아래를 참고하세요:
-
초기화 벡터란?
**초기화 벡터(Initialization Vector, IV)**는 암호화 과정에서 사용되는 난수 또는 유사 난수 값입니다. 주로 블록 암호에서 특정 운용 모드(Mode of Operation)와 함께 사용되며, 암호화의 보안을 강화하는 데 핵심적인 역할을 합니다. 초기화 벡터(IV)란 무엇인가? IV는 암호화 알고리즘의 초기 상태를 설정하는 데 사용되는 입력값입니다. 비밀 키와 함께 암호화 알고리즘에 주입되어 첫 번째 평문 블록의 암호화에 영향을 줍니다. IV는 비밀로 유지될 필요는 없지만, 매번 고유하고 예측 불가능해야 합니다. 왜 사용되는가? IV를 사용하는 주된 이유는 다음과 같습니다. 동일한 평문이 다른 암호문을 생성하도록 보장: IV가 없으면 동일한 평문을 같은 키로 여러 번 암호화할 때 항상 동일한 암호문이 생성됩니다. 이는 공격자가 암호문의 패턴을 분석하여 평문이나 암호화 키에 대한 정보를 유추할 수 있게 합니다. IV를 사용하면 매번 다른 암호문이 생성되어 이러한 **패턴 분석 공격(pattern analysis attacks)**을 방지하고 **확률적 암호화(probabilistic encryption)**를 가능하게 합니다. 보안 강화: IV는 암호화 과정에 추가적인 무작위성을 도입하여 암호의 강도를 높입니다. 특히, Cipher Block Chaining (CBC)와 같은 블록 암호 운용 모드에서는 IV가 이전 암호문 블록과 함께 다음 평문 블록의 암호화에 영향을 미치므로, 암호문 내의 예측 가능한 패턴을 제거하여 사전 공격(dictionary attacks) 등을 어렵게 만듭니다. 암호 해독 동기화: 암호화된 데이터를 복호화할 때, 복호화 과정의 초기 상태를 맞추기 위해 암호화에 사용된 IV가 필요합니다. 따라서 IV는 암호문과 함께 전송되거나 공유되어야 합니다. 요약하자면, 초기화 벡터는 암호화 시스템의 견고성과 보안성을 높이는 데 필수적인 요소입니다. 고유하고 예측 불가능한 IV를 사용함으로써, 동일한 평문이 암호화될 때에도 항상 다른 암호문이 생성되도록 하여 잠재적인 암호 분석 공격으로부터 데이터를 보호합니다.
-
-
HMAC-SHA256해시함수의 출력길이는 항상 32바이트입니다. 그러므로 32바이트를 사용합니다.
[암호화] 3. 평문 준비
이제 암호화할 평문이 필요합니다.
NICE에서 지정한 평문 양식은 다음과 같습니다.
String plain = String.format(
"{\"requestno\":\"%s\",\"returnurl\":\"%s\",\"sitecode\":\"%s\"}",
reqNo, "https://www.example.com/nice/callback", tokenInfo.getDataBody().getSiteCode()
);
[암호화] 4. 평문 AES 암호화
String encryptData = encryptAES(plain, key, iv);
public static String encryptAES(String reqData, String key, String iv) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
SecretKey secureKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, secureKey, new IvParameterSpec(iv.getBytes()));
byte[] encrypted = c.doFinal(reqData.trim().getBytes());
return Base64.getEncoder().encodeToString(encrypted);
}
- 여기서 생성하는 값이 바로 encData 입니다.
[암호화] 5. 암호화된 평문에 대한 해시 생성 및 인코딩
String integrity = Base64.getEncoder().encodeToString(
hmac256(hmacKey.getBytes(), encryptData.getBytes())
);
public static byte[] hmac256(byte[] secretKey,byte[] message)
throws NoSuchAlgorithmException, InvalidKeyException{
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec sks = new SecretKeySpec(secretKey, "HmacSHA256");
mac.init(sks);
return mac.doFinal(message);
}
- hmac256으로 해시를 생성한 결과물을 Base64로 인코딩했습니다.
- 여기서 생성하는 값이 바로 integrityValue 입니다.
이제 암호화가 끝났습니다.
7. 암호화된 폼데이터 전송
- 간단합니다. 아까 요청으로 온 3.에 대해서 다음의 응답을 내보내면 됩니다.
public class NiceAuthenticationGetTokenResponse {
@Schema(description = "나이스 인증창에서 사용할 토큰버전 아이디, 프론트에서 저장 후 최종 회원가입시 같이 주셔야 합니다.")
private String tokenVersionId;
@Schema(description = "나이스 인증창에서 사용할 encData")
private String encData;
@Schema(description = "나이스 인증창에서 사용할 무결성 값")
private String integrityValue;
}
tokenVersionId
: 아까 5번에서 Nice 측으로부터 받았던 값을 사용하면 됩니다.encData
: 6.에서 생성했습니다.integrityValue
: 6.에서 생성했습니다.
8. NICE 인증창 호출
프론트엔드에서 우리가 7.에서 준 값들을 토대로 NICE 인증창을 호출하고, 클라이언트가 인증할 것입니다.
프론트엔드 파트이므로 넘어가겠습니다.
9. 암호화된 인증결과 전송(서버입장에선 인증결과 받기)
거의 끝났습니다.
네, 아까 우리는 https://www.example.com/nice/callback 를 콜백으로 지정했습니다.
따라서 NICE에서 GET/POST(앱이냐 웹이냐에 따라 다름) https://www.example.com/nice/callback 으로 유저의 인증 정보를 전달해줄 것입니다.
먼저 엔드포인트를 정의합니다.
@CustomExceptionDescription(SwaggerResponseDescription.NICE_WEB_HOOK)
@Operation(summary = "나이스측에서 받는 엔드포인트입니다. 사용하지 마세요", description = "유저 인증 완료 후 인증결과를 받는다")
@RequestMapping(value = "/nice/callback", method = {RequestMethod.GET, RequestMethod.POST})
public SuccessResponse<SingleResult<Void>> niceAuthenticationComplete(
@RequestParam("token_version_id") String tokenVersionId,
@RequestParam("enc_data") String encData,
@RequestParam("integrity_value") String integrityValue
) {
// 무결성 검증 및 암호화된 데이터 복호화, 인증 결과 처리 수행
// 내부적으로 Redis 조회, 인증 결과 저장 등의 작업이 포함됨
authenticationUseCase.niceAuthenticationComplete(tokenVersionId, encData, integrityValue);
return ...
}
네 여기서 의문이 드셔야합니다.
콜백이 오긴 왔는데, 이게 아까 어떤 유저의 인증 콜백인지 어떻게 알지요?
그렇습니다. 우리는 아까 6번 이후, 7번 이전에 유저에 대한 암호화 데이터를 db에 저장했어야 합니다.
TTL 기능이 있는 Redis를 추천합니다.
지금와서 기재하는 이유는 이전에 해당 저장로직을 껴서 설명하면 더 복잡해질것 같았기 때문입니다.
6 - 7번 사이에 암호화된 데이터들을 저장하는 로직을 추가로 작성하고 다시 오시기 바랍니다.
다음 내용을 저장해야합니다.
tokenVersionId
: PK로 사용reqNo
key
: AES256iv
hmacKey
예시:
// Redis에 임시 토큰 정보를 저장한다 (후속 처리를 위해 필요)
NiceTokenCache tokenCache = NiceTokenCache.builder()
.tokenVersionId(tokenInfo.getDataBody().getTokenVersionId())
.reqNo(reqNo)
.key(key)
.iv(iv)
.hmacKey(hmacKey)
.build();
authenticationInfra.saveNiceTokenCache(tokenCache);
다시 넘어오셨나요? 이어서 시작하겠습니다.
먼저 아까 저장한 토큰 정보를 불러옵니다.
NiceTokenCache niceTokenCache = authenticationInfra.getNiceTokenCache(tokenVersionId);
String hmacKey = niceTokenCache.getHmacKey();
String key = niceTokenCache.getKey();
String iv = niceTokenCache.getIv();
String reqNo = niceTokenCache.getReqNo();
그리고 아까 암호화했던것과 똑같이 해시 값을 만듭니다.
정상적인 데이터라면, 아까 암호화(해시)했던 값들을 똑같이 해시하면, 같은 값이 나와야겠죠?
byte[] hmacSha256 = hmac256(hmacKey.getBytes(), encData.getBytes());
String integrity = Base64.getEncoder().encodeToString(hmacSha256);
이제 방금만든 integrity와, 아까만들었을(위조가 아니라면) integrity를 비교합니다.
if (!integrity.equals(integrityValue)) {
log.error("무결성 검증 실패");
throw new RuntimeException("무결성 검증 실패");
}
무결성이 보장됐다면, 이제 암호문을 다시 평문으로 바꿉니다.
지금부터 하는 과정은 아까 암호화했던 과정을 다시 되돌리는 과정입니다.
그러니까,
String plain = String.format(
"{\"requestno\":\"%s\",\"returnurl\":\"%s\",\"sitecode\":\"%s\"}",
reqNo, SERVER_URL+NICE_REDIRECT_URL, tokenInfo.getDataBody().getSiteCode()
);
얘를 다시 복원하는 과정이라 보시면 됩니다.
String decData = NiceSecurityUtil.getAesDecDataPKCS5(key.getBytes(), iv.getBytes(), encData);
이제 jsonString으로 복원이 됐으니, 내부 값을 빼내야겠죠?
JSONParser parser = new JSONParser();
JSONObject plainData = (JSONObject) parser.parse(decData);
log.info("### Nice 인증 성공: tokenVersionId={}", tokenVersionId);
// 세션 유효성 검증: 요청 번호가 일치하는지 확인한다
if (!reqNo.equals(getFieldFromJson(plainData, "requestno"))) {
log.error("세션값 불일치");
throw new RuntimeException("세션값 불일치");
}
아까 UUID로 만들어서 db에 저장했던 reqNo와 비교했습니다.
plainData에는, 아까 우리가 암호화한 평문에 추가적인 정보들이 들어가있습니다. 바로 유저정보입니다.
// 복호화된 데이터에서 사용자 정보를 추출한다
String username = URLDecoder.decode(getFieldFromJson(plainData, "utf8_name"), StandardCharsets.UTF_8);
String birthdate = getFieldFromJson(plainData, "birthdate");
String gender = getFieldFromJson(plainData, "gender");
String nationalInfo = getFieldFromJson(plainData, "nationalinfo");
String phoneNumber = getFieldFromJson(plainData, "mobileno");
-
우리가 암호화 했을때:
// niceAuthenticationGetToken()에서 생성 String plain = "{\"requestno\":\"req123\",\"returnurl\":\"...\",\"sitecode\":\"...\"}"; String encryptData = encryptAES(plain, key, iv);
-
인증 완료후 받은 실제 값:
// niceAuthenticationComplete()에서 수신 // 복호화 후 예상되는 데이터 구조: { "requestno": "req123", "returnurl": "...", "sitecode": "...", "name": "홍길동", "birthdate": "19900101", "gender": "M", "phone": "01012345678", // ... 기타 NICE에서 추가된 개인정보 }
내부 데이터가 바뀌었는데 어떻게 decoding이 가능한걸까요?
분명 처음 plain에는 name, birthdate같은 값이 없었습니다.
그 이유는 바로 NICE 서버에서 데이터 수정후 우리가 준 key값으로 서명했기 때문입니다.
같은 key를 사용했기에 복호화도 똑같이 할 수 있습니다.
이제 여기서 유저정보를 취득 후 사용하시면 됩니다. 수고하셨습니다.
✅ Conclusion
본 문서는 NICE PASS 인증 API를 서버에 연동하는 과정을 상세히 안내하였습니다. 클라이언트와 NICE 서버 간의 복잡한 통신 흐름을 이해하고, 각 단계에서 필요한 기관 토큰 발급, 암호화 토큰 요청, 그리고 데이터 암복호화 과정을 면밀히 살펴보았습니다.
특히, 사용자의 민감한 정보를 안전하게 보호하기 위한 보안 메커니즘의 중요성을 강조했습니다. SHA-256 해시, AES 암호화, HMAC-SHA256 무결성 검증과 같은 기술들을 활용하여 데이터가 안전하게 전송되고 처리될 수 있도록 구현 방안을 제시하였습니다. 또한, 콜백 시점에서 요청과 응답 간의 유효성을 검증하기 위해 Redis와 같은 임시 저장소를 활용하는 방법을 추가적으로 설명하며, 실제 서비스 환경에서의 안정성을 확보하는 데 중점을 두었습니다.
이 가이드가 NICE PASS 인증 API를 성공적으로 연동하고, 사용자에게 안전하고 원활한 본인인증 경험을 제공하는 데 실질적인 도움이 되기를 바랍니다.