Masking Name with Regex, in java

2022. 5. 29. 17:28Spring/Java

반응형

정규식을 이용해 이름에 마스킹처리를 하는 것이 해당 포스팅의 목표입니다.

 

 

보안상 이름 전체가 노출되면 안되는 경우가 있습니다.

하지만 본인의 이름을 구별할 필요는 있기 때문에 제약사항을 가지고 이름을 노출시키곤 하는데요.

부분별로 가려 이름을 마스킹 처리하는 기능을 구현하고자 합니다.

 

Condition

먼저 적용하고자 하는 규칙은 아래와 같습니다.

아래는 실제로 요구받은 마스킹 처리 조건입니다.

 

한글이름 성명 중 이름의 첫번째 글자 정*성 / 현*
영문이름 첫번째 자리를 제외한 이름의 4자리 이상 B****** Spears / S**** Jobs

 

하지만 위의 조건은 부족한 점이 있는데요.

예를 들어 한 글자이거나, 영문이름이지만 한 단어인 경우와 같은 흔치않은 예외 상황이 있습니다.

이런 조건들을 고려하며 개발한 내용을 살펴보겠습니다. 

 

 

 

Masking

필자는 스프링을 주로 하기 때문에 자바 정규식을 이용해서 기능을 구현합니다.

TC를 통해 구현해보았는데, 코드는 아래와 같습니다.

 

public class MaskingTest {
    List<String> testSet = List.of("홍길동", "서 도윤", "동욱", "현", "제갈지유", "Hugh Grant", "Julia Fiona Roberts", "Robert");

    String NAME_MASKING_KO = "(?<=.{1})(?<masking>.*)(?=.$)";
    String NAME_MASKING_EN = "(?<=.{1})(?<masking>\\w*)(?=\\s)";
    String MASKING_STAR = "*";

    @Test
    void masking() {
        testSet.stream().map(name -> {
            System.out.printf("%s → ", name);
            
            if (name.length() < 3) {
                return name.substring(0, name.length() - 1) + MASKING_STAR;
            }

            if (name.charAt(0) >= 65 && name.charAt(0) <= 122) {
                Pattern pattern = Pattern.compile(NAME_MASKING_EN);
                Matcher m = pattern.matcher(name);

                if (m.find()) {
                    return name.replaceFirst(
                    	NAME_MASKING_EN, 
                        MASKING_STAR.repeat(m.group().length())
                    );
                }
            }            
            return name.replaceFirst(NAME_MASKING_KO,
                MASKING_STAR.repeat(name.length() - 2));

        }).forEach(System.out::println);
    }
}

 

Output

홍길동 → 홍*동
서 도윤 → 서**윤
동욱 → 동*
현 → *
제갈지유 → 제**유
Hugh Grant → H*** Grant
Julia Fiona Roberts → J**** Fiona Roberts
Robert → R****t

 

위와 같은 결과로 출력됩니다.

제작을 하고 나니, 미들네임 처리는 어떻게 할지도 고려해볼 필요가 있더라구요.

간단히 정규식을 수정해서 해결했습니다.

 

// before
String NAME_MASKING_EN = "(?<=.{1})(?<masking>\\w*)(?=\\s)";

//after
String NAME_MASKING_EN_FULL = "(?<=.{1})(?<masking>.*)(?=\\s)";

 

변경 후에 코드는 동일합니다.

실행 시 아래와 같이 변경된 것을 확인할 수 있습니다.

 

 

Output

홍길동 → 홍*동
서 도윤 → 서**윤
동욱 → 동*
현 → *
제갈지유 → 제**유
Hugh Grant → H*** Grant
Julia Fiona Roberts → J********** Roberts
Robert → R****t

 

 

이제 코드를 간단히 설명하고 이번 포스팅을 마치도록 하겠습니다.

 

 

 

Explain

replaceX method

가장 먼저 자바의 replaceX 메서드를 사용합니다.

replaceX 메서드는 String 객체에 정의된 기본 기능입니다.

 

package java.lang;

public final class String implements ... {

    public String replaceFirst(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
    }

    public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }


    /* 참고
    public String replace(CharSequence target, CharSequence replacement) {
        // ...
    }
    */
}

 

replaceFirst와 replaceAll은 각각 String regex, String replacement를 받아요.

예상한 대로 regex에 매칭되는 영역을 replacement로 대체한다는 것이죠.

 

단, replace 메소드는 regex를 사용하지 않으니 잘 구별하시길 바랍니다.

사용한 코드를 다시 보면 아래와 같은 형식이에요.

 

name.replaceFirst(
    NAME_MASKING_EN, 
    MASKING_STAR.repeat(m.group().length())
);

 

MASKING_STAR(*)를 캡처한 스트링 길이만큼 반복한 후 대체합니다.

 

replaceFirst나 replaceAll은 매칭된 String에 replacement 하나가 대체되는데요.

이 때 "제갈정은" 라는 명이 있을 때, "제**은"이 아닌 "제*은"이 됩니다.

이 조건은 캡처한 그룹을 가져와 그 스트링 길이를 구하는 것으로 해결했습니다.

아래 Pattern에서 계속해서 살펴볼게요.

 

 

Pattern & Compile

 

자바에서 정규식을 사용하려면 반드시 알아야하는 클래스가 두 개 있습니다.

바로, Pattern ClassMatcher Class 입니다.

해당 내용은 예전에 포스팅한 내용을 링크했으니 참고 바랍니다.

 

위의 코드에서는 스트링을 캡처한 후 그 스트링 길이를 알아내고자 사용했습니다.

 

Pattern pattern = Pattern.compile(NAME_MASKING_EN);
Matcher m = pattern.matcher(name);

if (m.find()) {
	... m.group().length() ...;
}

 

m.group은 m.find()를 먼저 해줘야 사용할 수 있습니다.

 

 

Regex

Regex는 예전에 다뤘던 포스팅을 참고하시길 바랍니다.

정규식, 어렵지 않게 사용하기 - 기본정규식, 제대로 사용하기 - 심화

 

 

✔️ NAME_MASKING_KO

 

(?<=.{1})(?<masking>.*)(?=.$)

 

한글명은 위와 같은 정규식을 사용합니다.

 

(?<=.{1}) : 첫 번째 글자 (.{1}) 이후의 (후방탐색 (?<=ABC)) 

(?<masking>.*) : 다음 조건까지 모든 글자를 (.*) masking 그룹으로 캡처

(?=.$) : 끝의 한 자리(.$)

 

 

 

✔️ NAME_MASKING_EN

 

(?<=.{1})(?<masking>\w*)(?=\s)

 

(?<=.{1}) : 첫 번째 글자 (.{1}) 이후의 (후방탐색 (?<=ABC))

(?<masking>\w*) : 다음 조건까지 모든 글자를 (.\w*) masking 그룹으로 캡처

(?=\s) : 공백(\s) 검색

 

 

 

✔️ NAME_MASKING_EN_FULL

 

(?<=.{1})(?<masking>.*)(?=\s)

 

\w 으로 단어에 제한하지 않고 공백을 비롯한 모든 글자(.*) 포함

 

 

 

 

✔️ Named Capturing Group

masking이라는 그룹명으로 캡처를 해서 아래와 같이 사용할 수 있습니다.

 

m.group()
   ↓
m.group("masking")

 

필자는 개발 시 혹여나 수정되면 오류가 날까봐 일단 명명만 했는데, 

그룹명을 확정한다면, 또 여러개의 그룹명이 필요한 경우에 아래와 같이 사용할 수 있으니 참고바랍니다.

 

 

 

 

그럼 지금까지 이름 마스킹에 대해  다뤘습니다.

오타나 잘못된 내용을 댓글로 남겨주세요!

감사합니다 ☺️ 

반응형

'Spring > Java' 카테고리의 다른 글

Java Time, 제대로 사용하기  (1) 2022.09.14
Bloom Filter, 제대로 이해하기 - Java  (0) 2022.08.31
Java Date & Time, 제대로 사용하기  (2) 2022.05.22
ENUM, Clean Code with Java  (0) 2022.05.11
Matcher, 어렵지 않게 사용하기  (0) 2020.08.24