โ–  ๊ฐœ์ธ์ •๋ณด์˜ ๊ธฐ์ˆ ์ ๊ด€๋ฆฌ์  ๋ณดํ˜ธ์กฐ์น˜ ๊ธฐ์ค€ ์ œ10์กฐ
์ •๋ณดํ†ต์‹ ์„œ๋น„์Šค ์ œ๊ณต์ž๋“ฑ์€ ๊ฐœ์ธ์ •๋ณด ์—…๋ฌด์ฒ˜๋ฆฌ๋ฅผ ๋ชฉ์ ์œผ๋กœ ๊ฐœ์ธ์ •๋ณด์˜ ์กฐํšŒ, ์ถœ๋ ฅ ๋“ฑ์˜ ์—…๋ฌด๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ณผ์ •์—์„œ ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฅผ ์œ„ํ•˜์—ฌ ๊ฐœ์ธ์ •๋ณด๋ฅผ ๋งˆ์Šคํ‚นํ•˜์—ฌ ํ‘œ์‹œ์ œํ•œ ์กฐ์น˜๋ฅผ ์ทจํ•  ์ˆ˜ ์žˆ๋‹ค.

์œ„์™€ ๊ฐ™์ด ๊ฐœ์ธ์ •๋ณด ๋˜๋Š” ๋ณด์•ˆ ์ด์Šˆ๋กœ ์ธํ•˜์—ฌ ์ผ๋ถ€ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ ํ•ญ๋ชฉ์„ ์ „์ฒด๊ฐ€ ์•„๋‹Œ ์ผ๋ถ€๋งŒ์„ ํ‘œ์‹œํ•ด์•ผํ•  ์š”๊ตฌ์‚ฌํ•ญ์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. Jackson AnnotationIntrospector ์ด์Šˆ๋ฅผ ๊ฒฝํ—˜ํ•œ ๊น€์— AnnotationIntrospector๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ REST API์—์„œ ์‘๋‹ต๋˜๋Š” ์ผ๋ถ€ ํ•„๋“œ๋ฅผ ๋งˆ์Šคํ‚นํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ดํ•ดํ•ด๋ณด๋„๋ก ํ•˜์ž.

Annotation ๊ธฐ๋ฐ˜ ๋งˆ์Šคํ‚น ์ฒ˜๋ฆฌ

๊ธฐ๋ณธ์ ์œผ๋กœ ๋ณ„๋„์˜ Getter ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด์„œ ๋งˆ์Šคํ‚นํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•„๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋„ ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ๋งˆ์Šคํ‚น์„ ์œ„ํ•œ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋“ค๊ณ  AnnotationIntrospector๋ฅผ ํ™•์žฅํ•ด์„œ ์–ด๋…ธํ…Œ์ด์…˜์ด ์„ ์–ธ๋œ ํ•„๋“œ์— ๋Œ€ํ•ด์„œ ๋งˆ์Šคํ‚น๋œ ๊ฒฐ๊ณผ๋กœ ์ง๋ ฌํ™”(Serialize)๋ฅผ ์ˆ˜ํ–‰ํ•˜๋„๋ก ์ž‘์„ฑํ•˜๋ฉด ๋งˆ์Šคํ‚น ๋˜์–ด์•ผํ•˜๋Š” ํ•ญ๋ชฉ์— ๋”ฐ๋ผ์„œ ๋‹ค์–‘ํ•œ ๋งˆ์Šคํ‚น ํŒจํ„ด์„ ์ „๋žต์ ์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

MaskedField
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotation public @interface MaskedField { String expression() default "****"; MaskedType type() default MaskedType.COMMON; String[] fields() default {}; // NOTE: If metadata }
MaskedFieldAnnotationIntrospector
public class MaskedFieldAnnotationIntrospector extends NopAnnotationIntrospector { @Override public Object findSerializer(Annotated annotated) { MaskedField annotation = annotated.getAnnotation(MaskedField.class); if (annotation != null) { return MaskedFieldSerializer.class; } return null; } public static class MaskedFieldSerializer extends StdSerializer<Object> implements ContextualSerializer { private final boolean isMask; private final MaskedField annotation; public MaskedFieldSerializer(MaskedField annotation, boolean isMask) { super(Object.class); this.isMask = isMask; this.annotation = annotation; } @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { // NOTE: MaskedType ์— ๋”ฐ๋ฅธ ๋งˆ์Šคํ‚น ํŒจํ„ด ๊ตฌํ˜„์€ ์ƒ๋žต ObjectMapper mapper = (ObjectMapper) gen.getCodec(); String s = mapper.writeValueAsString(value); if (isMask) { JSONParser parser = new JSONParser(DEFAULT_PERMISSIVE_MODE); try { Object o = parser.parse(s); if (o instanceof JSONAwareEx ex) { DocumentContext doc = JsonPath.parse(ex.toJSONString()); Map json = doc.json(); for (String field : annotation.fields()) { if (json.containsKey(field)) { doc.set(field, annotation.expression()); } } gen.writeRawValue(doc.jsonString()); } else { gen.writeString(annotation.expression()); } } catch (ParseException e) { e.printStackTrace(); } } else { gen.writeRawValue(s); } } @Override public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException { MaskedField maskedField = null; if (beanProperty != null) { maskedField = beanProperty.getAnnotation(MaskedField.class); } return new MaskedFieldSerializer(maskedField, true); } } }
AnnotationIntrospector.pair
@Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); AnnotationIntrospector introspector = objectMapper.getSerializationConfig().getAnnotationIntrospector(); AnnotationIntrospector annotationIntrospector = AnnotationIntrospector.pair(introspector, new MaskedFieldAnnotationIntrospector()); objectMapper.setAnnotationIntrospector(annotationIntrospector); return objectMapper; }
  • ๋‹ค์–‘ํ•œ ๋งˆ์Šคํ‚น ํŒจํ„ด ๋Œ€์‘์„ ์œ„ํ•œ MaskedType Enum ๋งŒ๋“ค๊ธฐ
  • NopAnnotationIntrospector ๋ฅผ ํ™•์žฅํ•œ MaskedFieldAnnotationIntrospector ํด๋ž˜์Šค ์ž‘์„ฑํ•˜๊ธฐ
  • objectMapper์— AnntationIntrospectorPair๋กœ MaskedFieldAnnotationIntrospector ๋“ฑ๋กํ•˜๊ธฐ

๋งˆ์Šคํ‚น ์ „๋žต

์ด๋ฆ„๊ณผ ์ฃผ๋ฏผ๋“ฑ๋ก๋ฒˆํ˜ธ ๊ทธ๋ฆฌ๊ณ  ํœด๋Œ€ํฐ ๋ฒˆํ˜ธ์™€ ๊ฐ™์ด ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋งˆ์Šคํ‚น์ด ๊ณ ๋ ค๋˜์–ด์•ผํ•œ๋‹ค. ๋งˆ์Šคํ‚น ์ „๋žต์—๋Š” ๋’ค์—์„œ N๊ฐœ์˜ ๋ฌธ์ž ๋˜๋Š” ๋ฐ์ดํ„ฐ ํ˜•์‹์— ๋”ฐ๋ผ ์ค‘๊ฐ„์˜ N๊ฐœ์˜ ๋ฌธ์ž๋ฅผ ๋ณ„ํ‘œ(asterisk)๋กœ ์น˜ํ™˜ํ•œ๋‹ค. ๊ฒฝ๊ธฐ๋Œ€ํ•™๊ต ์ „์‚ฐ์ •๋ณด์›์˜ ๊ฐœ์ธ์ •๋ณด ๋…ธ์ถœ ์กฐ์น˜ ๋ฐฉ๋ฒ• ์•ˆ๋‚ด์—์„œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์˜ˆ์‹œ๋ฅผ ์ž˜ ๋‚˜ํƒ€๋‚ด๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ๋งˆ์Šคํ‚น ์ฒ˜๋ฆฌ๋ฅผ ๋ฐ˜๋“œ์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„์—์„œ ์ด๋ฃจ์–ด์ ธ์•ผํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์„œ๋กœ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์—์„œ ๊ฐœ์ธ์ •๋ณด๋ฅผ ๊ณต์œ ํ•˜๋Š”๋ฐ ๊ฐ ์‹œ์Šคํ…œ์—์„œ์˜ ๋งˆ์Šคํ‚น ์ „๋žต์ด ๋‹ค๋ฅด๋‹ค๋ฉด ์ด๊ฒƒ๋„ ๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ์™€ ๋ณดํ˜ธ ์กฐ์น˜์— ๋Œ€ํ•œ ๋ฌธ์ œ์˜ ์†Œ์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๊ทธ์— ํฌํ•จ๋  ์ˆ˜ ์žˆ๋Š” ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ์–ด๋–ป๊ฒŒ ๋งˆ์Šคํ‚น ํ•ด์•ผํ•˜์ง€? ๐Ÿค”