Jackson 이용한 개인정보 마스킹처리

h블로그·2021년 12월 20일
0

목록 화면에서 개인정보에 해당하는 몇 필드들은 전달받은 규칙에 맞게 마스킹처리를 해야했다.
구글링을 해보니, Jackson을 이용하는 방법이 있었다.

여러 dto 중 일부 개인정보에 해당하는 필드에만 @MaskRequired 라는 어노테이션을 만들어 달아 어떤 필드를 마스킹 해야하는지 정의하도록 한다.
어떤 방식으로 마스킹할지 정의해줄 속성으로 MaskingType enum을 만든다.

어떻게 마스킹을 처리하는지를 맡는 Masking 클래스가 필요한다. 이 클래스에는 이름, 전화번호, 주소등을 마스킹처리하는 구현 method를 가진다.

@MaskRequired Annotation 생성

public enum MaskingType {
    NAME,
    CONTACT,
    DETAIL_ADDRESS
}

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
public @interface MaskRequired {
    MaskingType type();
}
  • @Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
    - 어노테이션 타입, 클래스의 특정 필드에 사용한다고 정의
  • @Retention(RetentionPolicy.RUNTIME) : 런타임시에 어노테이션 사용할 수 있음
  • @JacksonAnnotationsInside : jackson custom annotation 이라고 정의하는 부분

Masking.java

public class Masking {

    public static String mask(MaskingType type, String value) {
        String str = "";
        switch (type) {
            case NAME:
                str = getNameMask(value);
                break;
            case CONTACT:
                str = getContactMask(value);
                break;
            case DETAIL_ADDRESS:
                str = getDetailAddressMask(value);
                break;
            default:
                break;
        }
        return str;
    }
    ... // 마스킹 메소드들은 필요한 형태로 구현하면 됨
}
  • 어노테이션에 속성의 정의한 enum 값에 따라서 마스킹 규칙을 매핑해준다.

처음엔 참고 블로그 내용처럼 interface를 구현하는 XXMasking 클래스를 각각 만들려고 하였으나 MaskingPropertySerializercreateContextual()에서 instance를 넘기려고 했으나, java에서는 그런 방법이 없어서 enum 속성과 Masking 클래스를 분리했다.

Custom Serialize

어노테이션이 붙은 필드는 custom serialize 를 사용하도록 설정해야 한다.


public class MaskingPropertySerializer extends StdSerializer<String> implements ContextualSerializer {
    MaskingType maskingType;

    protected MaskingPropertySerializer() {
        super(String.class);
    }

    protected MaskingPropertySerializer(MaskingType maskingType) {
        super(String.class);
        this.maskingType = maskingType;
    }

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeString(Masking.mask(maskingType, value));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        MaskingType maskingTypeValue = null;
        MaskRequired ann = null;
        if (property != null) {
            ann = property.getAnnotation(MaskRequired.class);
        }
        if (ann != null) {
            maskingTypeValue = ann.type();
        }
        return new MaskingPropertySerializer(maskingTypeValue);
    }
}
  • gen.writeString(Masking.mask(maskingType, value)); : 구현한 mask() 메소드를 타도록 한다.

여기까지 작성하면, dto의 해당하는 필드 위에

@JsonSerialize(using = MaskingPropertySerializer.class)
@MaskRequired(type = MaskType.NAME)
private String name;

이렇게 @JsonSerialize(using = MaskingPropertySerializer.class) 을 반복적으로 달아주어야 한다.

더 편리하게 이용하기 위해서는

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JsonSerialize(using = MaskingPropertySerializer.class)		// 추가
public @interface MaskRequired {
    MaskingType type();
}

MaskRequired 인터페이스에 두 어노테이션을 더 추가한다.

이렇게 하면 마스킹 처리가 필요한 dto의 필드 위에

@MaskRequired(type = MaskType.NAME)
private String name;

이렇게만 작성해주면 된다.

참고
https://rutesun.github.io/development/annotation-driven-masking/

profile
😎🙈🙈🙈🤓

0개의 댓글