마스킹 표준화 적용 (eapim-admin/portal/JS)
- admin: elink-online-core 표준 위임(e메일/이름/전화/IP)으로 규칙 일원화 - portal/JS: 표준 로직 직접 구현 + 기존 owner 비마스킹 유지 - 호출처 무수정/인라인 중복 제거/테스트 통과
This commit is contained in:
@@ -1,10 +1,24 @@
|
|||||||
package com.eactive.apim.portal.common.util;
|
package com.eactive.apim.portal.common.util;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 문자열 마스킹 처리를 위한 유틸리티 클래스
|
* 문자열 마스킹 처리를 위한 유틸리티 클래스
|
||||||
|
*
|
||||||
|
* <p>보안아키텍처 표준 마스킹 규칙(elink-online-core {@code MaskingUtils})과 동일한 알고리즘을
|
||||||
|
* 구현한다. eapim-portal은 elink-online-core에 의존하지 않으므로 로직을 직접 포함한다.
|
||||||
|
* 소유자 본인이 자신의 정보를 조회하는 경우 마스킹하지 않는 owner 인지 동작은 유지한다.</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 이름 2자: 뒤 1자리(가나→가*) / 3자 이상: 앞뒤 제외 가운데 전체(가나다라→가**라)
|
||||||
|
* 이메일 아이디 앞2·뒤2 마스킹, 4자 이하 전체(test→****, test123→**st1**)
|
||||||
|
* 전화 2·3번째 세그먼트 각각 뒤 2자리(010-1234-1234→010-12**-12**)
|
||||||
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public class StringMaskingUtil {
|
public class StringMaskingUtil {
|
||||||
|
|
||||||
|
private static final char MASK = '*';
|
||||||
|
|
||||||
// ID 비교
|
// ID 비교
|
||||||
private static boolean isOwner(String ownerId, String currentUserId) {
|
private static boolean isOwner(String ownerId, String currentUserId) {
|
||||||
return currentUserId != null && ownerId != null && currentUserId.equals(ownerId);
|
return currentUserId != null && ownerId != null && currentUserId.equals(ownerId);
|
||||||
@@ -21,8 +35,15 @@ public class StringMaskingUtil {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return name.length() > 1 ?
|
int len = name.length();
|
||||||
name.substring(0, name.length() - 1) + "*" : name;
|
if (len == 1) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
if (len == 2) {
|
||||||
|
return name.charAt(0) + String.valueOf(MASK);
|
||||||
|
}
|
||||||
|
// 3자 이상: 첫 자 + 가운데 전체 마스킹 + 마지막 자
|
||||||
|
return name.charAt(0) + stars(len - 2) + name.charAt(len - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 이메일 마스킹 처리
|
// 이메일 마스킹 처리
|
||||||
@@ -31,32 +52,32 @@ public class StringMaskingUtil {
|
|||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] parts = email.split("@", 2);
|
int atIdx = email.indexOf('@');
|
||||||
if (parts.length != 2) {
|
String local = email.substring(0, atIdx);
|
||||||
return email;
|
String domain = email.substring(atIdx);
|
||||||
|
|
||||||
|
if (local.length() <= 4) {
|
||||||
|
return stars(local.length()) + domain;
|
||||||
}
|
}
|
||||||
|
return stars(2) + local.substring(2, local.length() - 2) + stars(2) + domain;
|
||||||
String localPart = parts[0];
|
|
||||||
String maskedLocal;
|
|
||||||
|
|
||||||
if (localPart.length() <= 2) {
|
|
||||||
maskedLocal = "***";
|
|
||||||
} else {
|
|
||||||
maskedLocal = localPart.substring(0, localPart.length() - 3) + "***";
|
|
||||||
}
|
|
||||||
|
|
||||||
return maskedLocal + "@" + parts[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 휴대폰 번호 마스킹 처리
|
// 휴대폰/전화/FAX 번호 마스킹 처리
|
||||||
public static String maskMobileNumber(String number, String ownerId, String currentUserId) {
|
public static String maskMobileNumber(String number, String ownerId, String currentUserId) {
|
||||||
if (!isValidString(number) || isOwner(ownerId, currentUserId)) {
|
if (!isValidString(number) || isOwner(ownerId, currentUserId)) {
|
||||||
return number;
|
return number;
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] parts = number.split("-", 3);
|
boolean hasHyphen = number.contains("-");
|
||||||
return parts.length == 3 ?
|
String normalized = hasHyphen ? number : normalizePhoneNumber(number);
|
||||||
parts[0] + "-***-" + parts[2] : number;
|
String[] parts = normalized.split("-");
|
||||||
|
if (parts.length == 3) {
|
||||||
|
String m0 = parts[0];
|
||||||
|
String m1 = maskSegmentTail(parts[1], 2);
|
||||||
|
String m2 = maskSegmentTail(parts[2], 2);
|
||||||
|
return hasHyphen ? m0 + "-" + m1 + "-" + m2 : m0 + m1 + m2;
|
||||||
|
}
|
||||||
|
return number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 기존 메서드 오버로딩 (하위 호환성)
|
// 기존 메서드 오버로딩 (하위 호환성)
|
||||||
@@ -71,4 +92,41 @@ public class StringMaskingUtil {
|
|||||||
public static String maskMobileNumber(String number) {
|
public static String maskMobileNumber(String number) {
|
||||||
return maskMobileNumber(number, null, null);
|
return maskMobileNumber(number, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// private helpers
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private static String stars(int count) {
|
||||||
|
if (count <= 0) return "";
|
||||||
|
char[] arr = new char[count];
|
||||||
|
Arrays.fill(arr, MASK);
|
||||||
|
return new String(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String maskSegmentTail(String segment, int count) {
|
||||||
|
if (segment.length() <= count) return stars(segment.length());
|
||||||
|
return segment.substring(0, segment.length() - count) + stars(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 한국 전화번호(숫자만) → XXX-XXXX-XXXX 형식으로 정규화
|
||||||
|
* <pre>
|
||||||
|
* 02 + 7자리 → 02-XXX-XXXX
|
||||||
|
* 02 + 8자리 → 02-XXXX-XXXX
|
||||||
|
* 0XX + 7자리 → 0XX-XXX-XXXX
|
||||||
|
* 0XX + 8자리 → 0XX-XXXX-XXXX
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
private static String normalizePhoneNumber(String digits) {
|
||||||
|
int len = digits.length();
|
||||||
|
if (digits.startsWith("02")) {
|
||||||
|
if (len == 9) return digits.substring(0, 2) + "-" + digits.substring(2, 5) + "-" + digits.substring(5);
|
||||||
|
if (len == 10) return digits.substring(0, 2) + "-" + digits.substring(2, 6) + "-" + digits.substring(6);
|
||||||
|
} else if (digits.startsWith("0")) {
|
||||||
|
if (len == 10) return digits.substring(0, 3) + "-" + digits.substring(3, 6) + "-" + digits.substring(6);
|
||||||
|
if (len == 11) return digits.substring(0, 3) + "-" + digits.substring(3, 7) + "-" + digits.substring(7);
|
||||||
|
}
|
||||||
|
return digits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.eactive.apim.portal.common.util;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* eapim-portal {@link StringMaskingUtil}가 보안아키텍처 표준 규칙으로 마스킹하며,
|
||||||
|
* 소유자 본인 조회 시에는 마스킹하지 않는 동작을 검증한다.
|
||||||
|
*/
|
||||||
|
class StringMaskingUtilTest {
|
||||||
|
|
||||||
|
// --- 이름 ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 2자: 뒤 1자리")
|
||||||
|
void maskName_2chars() {
|
||||||
|
assertEquals("가*", StringMaskingUtil.maskName("가나"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 3자: 가운데 1자리")
|
||||||
|
void maskName_3chars() {
|
||||||
|
assertEquals("가*다", StringMaskingUtil.maskName("가나다"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 4자: 앞뒤 제외 가운데 전체")
|
||||||
|
void maskName_4chars() {
|
||||||
|
assertEquals("가**라", StringMaskingUtil.maskName("가나다라"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 5자: 앞뒤 제외 가운데 전체")
|
||||||
|
void maskName_5chars() {
|
||||||
|
assertEquals("가***마", StringMaskingUtil.maskName("가나다라마"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이름 — 1자/null/빈문자 방어")
|
||||||
|
void maskName_oneAndNull() {
|
||||||
|
assertEquals("가", StringMaskingUtil.maskName("가"));
|
||||||
|
assertNull(StringMaskingUtil.maskName(null));
|
||||||
|
assertEquals("", StringMaskingUtil.maskName(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 이메일 ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이메일 — 아이디 앞2·뒤2 마스킹")
|
||||||
|
void maskEmail_normal() {
|
||||||
|
assertEquals("**st1**@test.com", StringMaskingUtil.maskEmail("test123@test.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이메일 — 아이디 4자: 전체 마스킹")
|
||||||
|
void maskEmail_fourChars() {
|
||||||
|
assertEquals("****@test.com", StringMaskingUtil.maskEmail("test@test.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이메일 — 아이디 1자: 전체 마스킹")
|
||||||
|
void maskEmail_oneChar() {
|
||||||
|
assertEquals("*@123.com", StringMaskingUtil.maskEmail("1@123.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("이메일 — @ 없으면 원본 / null·빈문자 방어")
|
||||||
|
void maskEmail_invalidAndNull() {
|
||||||
|
assertEquals("notanemail", StringMaskingUtil.maskEmail("notanemail"));
|
||||||
|
assertNull(StringMaskingUtil.maskEmail(null));
|
||||||
|
assertEquals("", StringMaskingUtil.maskEmail(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 휴대폰/전화 ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("휴대폰 — 2·3번째 세그먼트 각 뒤 2자리")
|
||||||
|
void maskMobileNumber_normal() {
|
||||||
|
assertEquals("010-12**-12**", StringMaskingUtil.maskMobileNumber("010-1234-1234"));
|
||||||
|
assertEquals("010-12**-56**", StringMaskingUtil.maskMobileNumber("010-1234-5678"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("휴대폰 — 하이픈 없는 11자리")
|
||||||
|
void maskMobileNumber_noHyphen() {
|
||||||
|
assertEquals("01012**56**", StringMaskingUtil.maskMobileNumber("01012345678"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("휴대폰 — null/빈문자 방어")
|
||||||
|
void maskMobileNumber_null() {
|
||||||
|
assertNull(StringMaskingUtil.maskMobileNumber(null));
|
||||||
|
assertEquals("", StringMaskingUtil.maskMobileNumber(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- owner 인지 (본인 조회 시 비마스킹) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("owner — 본인 조회 시 마스킹하지 않음")
|
||||||
|
void ownerAware_noMaskWhenOwner() {
|
||||||
|
assertEquals("가나다", StringMaskingUtil.maskName("가나다", "user1", "user1"));
|
||||||
|
assertEquals("test123@test.com", StringMaskingUtil.maskEmail("test123@test.com", "user1", "user1"));
|
||||||
|
assertEquals("010-1234-1234", StringMaskingUtil.maskMobileNumber("010-1234-1234", "user1", "user1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("owner — 타인 조회 시 마스킹")
|
||||||
|
void ownerAware_maskWhenNotOwner() {
|
||||||
|
assertEquals("가*다", StringMaskingUtil.maskName("가나다", "user1", "user2"));
|
||||||
|
assertEquals("**st1**@test.com", StringMaskingUtil.maskEmail("test123@test.com", "user1", "user2"));
|
||||||
|
assertEquals("010-12**-12**", StringMaskingUtil.maskMobileNumber("010-1234-1234", "user1", "user2"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user