Merge branch 'dev' into dev-feat-doc
commit
ae7ddf8d9e
@ -0,0 +1,25 @@
|
||||
package kr.xit.framework.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotation;
|
||||
|
||||
import kr.xit.framework.core.annotation.mask.MaskingType;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* masking 처리
|
||||
* @MaskingRequired(type = MaskingType.NAME) annotaion이 붙은 필드 masking
|
||||
* MaskingType.NAME | RN | CONTACT | MAIL | CARD | ADDRESS
|
||||
* <p/>
|
||||
*/
|
||||
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotation
|
||||
//@JsonSerialize(using = MaskingPropertySerializer.class)
|
||||
public @interface MaskRequired {
|
||||
MaskingType type();
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
package kr.xit.framework.core.annotation.mask;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Masking {
|
||||
final static String MASKING_STAR = "*";
|
||||
final static char CHAR_STAR = '*';
|
||||
|
||||
public static String mask(MaskingType type, String value) {
|
||||
switch (type) {
|
||||
case NAME:
|
||||
return maskingName(value);
|
||||
case RN:
|
||||
return maskingRn(value);
|
||||
case CONTACT:
|
||||
return maskingContact(value);
|
||||
case MAIL:
|
||||
return maskingMail(value);
|
||||
case CARD:
|
||||
return maskingCard(value);
|
||||
case ADDRESS:
|
||||
return maskingAddress(value);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 이름 마스킹
|
||||
* 2글자 이상의 이름은 가운데 글자 마스킹. 외자는 끝자리만 마스킹
|
||||
* <p>
|
||||
* @param name String
|
||||
* @return String
|
||||
* </pre>
|
||||
*/
|
||||
public static String maskingName(String name) {
|
||||
|
||||
String NAME_MASKING_KO = "(?<=.{1})(?<masking>.*)(?=.$)";
|
||||
String NAME_MASKING_EN = "(?<=.{1})(?<masking>.*)(?=\\s)";
|
||||
|
||||
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()));
|
||||
//if(m.find()) return name.replaceFirst(NAME_MASKING_EN, StringUtils.repeat(MASKING_STAR, m.group().length()));
|
||||
}
|
||||
|
||||
// 한글인 경우
|
||||
return name.replaceFirst(NAME_MASKING_KO, MASKING_STAR.repeat(name.length() - 2));
|
||||
//return name.replaceFirst(NAME_MASKING_KO, StringUtils.repeat(MASKING_STAR, name.length() - 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 주민번호 마스킹
|
||||
* @param regNumber
|
||||
* @return
|
||||
*/
|
||||
public static String maskingRn(String regNumber) {
|
||||
|
||||
return regNumber.replaceAll("([0-9]{6})-([1-4]{1})([0-9]{6})", "$1-$2".concat(MASKING_STAR.repeat(6)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴대폰번호 마스킹
|
||||
* 가운데 숫자 4자리 마스킹이고, '-'(하이픈)이 들어오나 안들어오나, 숫자 길이와 형식이 맞으면 마스킹 처리
|
||||
* @param contactNo
|
||||
* @return
|
||||
*/
|
||||
public static String maskingContact(String contactNo) {
|
||||
String regex = "(\\d{2,3})-?(\\d{3,4})-?(\\d{4})$";
|
||||
|
||||
Matcher matcher = Pattern.compile(regex).matcher(contactNo);
|
||||
if(matcher.find()) {
|
||||
String target = matcher.group(2);
|
||||
int length = target.length();
|
||||
char[] c = new char[length];
|
||||
Arrays.fill(c, CHAR_STAR);
|
||||
|
||||
return contactNo.replace(target, String.valueOf(c));
|
||||
}
|
||||
return contactNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이메일 마스킹(앞3자리 이후 '@'전까지 마스킹)
|
||||
* @param email
|
||||
* @return
|
||||
*/
|
||||
public static String maskingMail(String email) {
|
||||
return email.replaceAll("[A-z|0-9|.-]+@", MASKING_STAR.repeat(5).concat("@"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 카드번호 마스킹
|
||||
* 카드번호 가운데 8자리 마스킹
|
||||
* <p>
|
||||
* @param cardNo
|
||||
* @return
|
||||
*/
|
||||
public static String maskingCard(String cardNo) {
|
||||
return cardNo.replaceAll("(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})", "$1-****-****-$4");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 주소 마스킹
|
||||
* 신주소, 구주소, 도로명 주소 숫자만 전부 마스킹
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
public static String maskingAddress(String address) {
|
||||
// 신(구)주소, 도로명 주소
|
||||
String regex = "(([가-힣]+(\\d{1,5}|\\d{1,5}(,|.)\\d{1,5}|)+(읍|면|동|가|리))(^구|)((\\d{1,5}(~|-)\\d{1,5}|\\d{1,5})(가|리|)|))([ ](산(\\d{1,5}(~|-)\\d{1,5}|\\d{1,5}))|)|";
|
||||
String newRegx = "(([가-힣]|(\\d{1,5}(~|-)\\d{1,5})|\\d{1,5})+(로|길))";
|
||||
|
||||
Matcher matcher = Pattern.compile(regex).matcher(address);
|
||||
Matcher newMatcher = Pattern.compile(newRegx).matcher(address);
|
||||
if(matcher.find()) {
|
||||
return address.replaceAll("[0-9]", "*");
|
||||
} else if(newMatcher.find()) {
|
||||
return address.replaceAll("[0-9]", "*");
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 계좌번호 마스킹(뒤 5자리)
|
||||
* @param accountNo
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String accountNoMasking(String accountNo) throws Exception {
|
||||
// 계좌번호는 숫자만 파악하므로
|
||||
String regex = "(^[0-9]+)$";
|
||||
|
||||
Matcher matcher = Pattern.compile(regex).matcher(accountNo);
|
||||
if(matcher.find()) {
|
||||
int length = accountNo.length();
|
||||
if(length > 5) {
|
||||
char[] c = new char[5];
|
||||
Arrays.fill(c, '*');
|
||||
|
||||
return accountNo.replace(accountNo, accountNo.substring(0, length-5) + String.valueOf(c));
|
||||
}
|
||||
}
|
||||
return accountNo;
|
||||
}
|
||||
|
||||
// 생년월일 마스킹(8자리)
|
||||
public static String birthMasking(String birthday) throws Exception {
|
||||
String regex = "^((19|20)\\d\\d)?([-/.])?(0[1-9]|1[012])([-/.])?(0[1-9]|[12][0-9]|3[01])$";
|
||||
|
||||
Matcher matcher = Pattern.compile(regex).matcher(birthday);
|
||||
if(matcher.find()) {
|
||||
return birthday.replace("[0-9]", "*");
|
||||
}
|
||||
return birthday;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package kr.xit.framework.core.annotation.mask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
|
||||
import kr.xit.framework.core.annotation.MaskRequired;
|
||||
public class MaskingPropertySerializer extends StdSerializer<String> implements ContextualSerializer {
|
||||
private MaskingType maskingType;
|
||||
|
||||
protected MaskingPropertySerializer() {
|
||||
super(String.class);
|
||||
}
|
||||
|
||||
public 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) {
|
||||
|
||||
return Optional.ofNullable(property)
|
||||
.map(p -> p.getAnnotation(MaskRequired.class))
|
||||
.map(maskRequired -> new MaskingPropertySerializer(maskRequired.type()))
|
||||
.orElse(null);
|
||||
/*
|
||||
MaskingType maskingTypeValue = null;
|
||||
MaskRequired ann = null;
|
||||
if (property != null) {
|
||||
ann = property.getAnnotation(MaskRequired.class);
|
||||
}
|
||||
if (ann != null) {
|
||||
maskingTypeValue = ann.type();
|
||||
}
|
||||
return new MaskingPropertySerializer(maskingTypeValue);
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package kr.xit.framework.core.annotation.mask;
|
||||
|
||||
public enum MaskingType {
|
||||
NAME,
|
||||
RN,
|
||||
CONTACT,
|
||||
MAIL,
|
||||
CARD,
|
||||
ADDRESS
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package kr.xit.framework.core.annotation.mask;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.AnnotationIntrospector;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
|
||||
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
|
||||
|
||||
import kr.xit.framework.core.annotation.MaskRequired;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class MaskingUtils {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* masking 처리
|
||||
* @MaskingRequired(type = MaskingType.NAME) annotaion이 붙은 필드 masking
|
||||
* MaskingType.NAME | RN | CONTACT | MAIL | CARD | ADDRESS
|
||||
* <p/>
|
||||
* @param src
|
||||
* @return
|
||||
* </pre>
|
||||
*/
|
||||
public static String buildMaskingObject(final Object src) {
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
AnnotationIntrospector sis = mapper.getSerializationConfig().getAnnotationIntrospector();
|
||||
AnnotationIntrospector ais = AnnotationIntrospectorPair.pair(sis, new MaskPropertyAnnotationIntrospector());
|
||||
|
||||
return mapper
|
||||
.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
|
||||
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
|
||||
.setAnnotationIntrospector(ais)
|
||||
|
||||
.writeValueAsString(src);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class MaskPropertyAnnotationIntrospector extends NopAnnotationIntrospector {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Object findSerializer(Annotated am) {
|
||||
MaskRequired annotation = am.getAnnotation(MaskRequired.class);
|
||||
if (annotation != null) {
|
||||
return MaskingPropertySerializer.class;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestDTO dto = TestDTO.builder()
|
||||
.name("황길동")
|
||||
.rn("200102-2039432")
|
||||
.contact("0212345678")
|
||||
.email("dm.m3dk.345@kk.co.kr")
|
||||
.card("1234-5678-9012-3456")
|
||||
.build();
|
||||
|
||||
log.debug(MaskingUtils.buildMaskingObject(dto));
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
static class TestDTO {
|
||||
|
||||
@MaskRequired(type = MaskingType.NAME)
|
||||
String name;
|
||||
|
||||
@MaskRequired(type = MaskingType.RN)
|
||||
String rn;
|
||||
|
||||
@MaskRequired(type = MaskingType.CONTACT)
|
||||
String contact;
|
||||
|
||||
@MaskRequired(type = MaskingType.MAIL)
|
||||
String email;
|
||||
|
||||
@MaskRequired(type = MaskingType.CARD)
|
||||
String card;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package kr.xit.framework.support.freemaker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.tiles.TilesContainer;
|
||||
import org.apache.tiles.evaluator.AttributeEvaluatorFactory;
|
||||
import org.apache.tiles.extras.complete.CompleteAutoloadTilesContainerFactory;
|
||||
import org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer;
|
||||
import org.apache.tiles.factory.AbstractTilesContainerFactory;
|
||||
import org.apache.tiles.request.ApplicationContext;
|
||||
import org.apache.tiles.request.ApplicationResource;
|
||||
import org.apache.tiles.request.freemarker.render.FreemarkerRendererBuilder;
|
||||
import org.apache.tiles.request.render.BasicRendererFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.view.UrlBasedViewResolver;
|
||||
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
|
||||
|
||||
//@Configuration
|
||||
public class FreeMarkerConfig{
|
||||
|
||||
@Bean
|
||||
public TilesConfigurer tilesConfigurer() {
|
||||
|
||||
String tilesDefinitionPath = "classpath*:/WEB-INF/freemarker/layout-definition/tiles-layout.xml";
|
||||
|
||||
TilesConfigurer tilesConfigurer = new TilesConfigurer();
|
||||
tilesConfigurer.setTilesInitializer(new CompleteAutoloadTilesInitializer() {
|
||||
@Override
|
||||
protected AbstractTilesContainerFactory createContainerFactory(ApplicationContext context) {
|
||||
// BasicTilesContainerFactory
|
||||
return new CompleteAutoloadTilesContainerFactory() {
|
||||
@Override
|
||||
protected List<ApplicationResource> getSources(ApplicationContext applicationContext) {
|
||||
List<ApplicationResource> retValue = new ArrayList<ApplicationResource>(1);
|
||||
retValue.add(applicationContext.getResource(tilesDefinitionPath));
|
||||
return retValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerAttributeRenderers(BasicRendererFactory rendererFactory, ApplicationContext applicationContext, TilesContainer container, AttributeEvaluatorFactory attributeEvaluatorFactory) {
|
||||
super.registerAttributeRenderers(rendererFactory, applicationContext, container, attributeEvaluatorFactory);
|
||||
FreemarkerRendererBuilder freemarkerRenderer = FreemarkerRendererBuilder.createInstance();
|
||||
freemarkerRenderer.setApplicationContext(applicationContext);
|
||||
freemarkerRenderer.setParameter("defaultEncoding", "UTF-8");
|
||||
//freemarkerRenderer.setParameter("ClasspathTlds", "/META-INF/tld/tiles-jsp.tld, /META-INF/spring.tld");
|
||||
//freemarkerRenderer.setParameter("autoInclude", "/WEB-INF/freemarker/common/common.ftl");
|
||||
freemarkerRenderer.setParameter("NoCache", "true");
|
||||
rendererFactory.registerRenderer("freemarker", freemarkerRenderer.build());
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
return tilesConfigurer;
|
||||
}
|
||||
|
||||
// @Bean
|
||||
// public UrlBasedViewResolver tilesViewResolver() {
|
||||
// UrlBasedViewResolver urlBasedViewResolver = new UrlBasedViewResolver();
|
||||
// urlBasedViewResolver.setViewClass(TilesView.class);
|
||||
// urlBasedViewResolver.setRequestContextAttribute("rc");
|
||||
// return urlBasedViewResolver;
|
||||
// }
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package kr.xit.framework.support.logback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.classic.PatternLayout;
|
||||
|
||||
/**
|
||||
* logback log masking
|
||||
* <p>
|
||||
* <appender name="mask" class="ch.qos.logback.core.ConsoleAppender">
|
||||
* <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
|
||||
* <layout class="kr.xit.framework.support.logback.LogbackMaskingPatternLayout">
|
||||
* <maskPattern>\"SSN\"\s*:\s*\"(.*?)\"</maskPattern> <!-- SSN JSON pattern -->
|
||||
* <maskPattern>\"address\"\s*:\s*\"(.*?)\"</maskPattern> <!-- Address JSON pattern -->
|
||||
* <maskPattern>(\d+\.\d+\.\d+\.\d+)</maskPattern> <!-- Ip address IPv4 pattern -->
|
||||
* <maskPattern>(\w+@\w+\.\w+)</maskPattern> <!-- Email pattern -->
|
||||
* <pattern>%-5p [%d{ISO8601,UTC}] [%thread] %c: %m%n%rootException</pattern>
|
||||
* </layout>
|
||||
* </encoder>
|
||||
* </appender>
|
||||
*
|
||||
*/
|
||||
public class LogbackMaskingPatternLayout extends PatternLayout {
|
||||
|
||||
private Pattern multilinePattern;
|
||||
private List<String> maskPatterns = new ArrayList<>();
|
||||
|
||||
public void addMaskPattern(String maskPattern) {
|
||||
maskPatterns.add(maskPattern);
|
||||
multilinePattern = Pattern.compile(maskPatterns.stream().collect(Collectors.joining("|")), Pattern.MULTILINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String doLayout(ILoggingEvent event) {
|
||||
return maskMessage(super.doLayout(event));
|
||||
}
|
||||
|
||||
private String maskMessage(String message) {
|
||||
if (multilinePattern == null) {
|
||||
return message;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(message);
|
||||
Matcher matcher = multilinePattern.matcher(sb);
|
||||
while (matcher.find()) {
|
||||
IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> {
|
||||
if (matcher.group(group) != null) {
|
||||
IntStream.range(matcher.start(group), matcher.end(group)).forEach(i -> sb.setCharAt(i, '*'));
|
||||
}
|
||||
});
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
|
||||
|
||||
|
||||
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
|
||||
<property name="templateLoaderPath" value="/WEB-INF/ftl"/>
|
||||
<property name="defaultEncoding" value="UTF-8"/>
|
||||
<property name="DEFAULT_INCOMPATIBLE_IMPROVEMENTS" value="2.3.23"/>
|
||||
<property name="freemarkerSettings">
|
||||
<map>
|
||||
<entry key="template_update_delay" value="60000"/>
|
||||
<entry key="auto_flush" value="false"/>
|
||||
<entry key="default_encoding" value="UTF-8"/>
|
||||
<entry key="whitespace_stripping" value="true"/>
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
|
||||
</beans>
|
@ -0,0 +1,10 @@
|
||||
<#ftl encoding="utf-8"/>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>네이바 프리마카</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>컨트롤러의 메세지: ${message}</h1>
|
||||
</body>
|
||||
</html>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/SETTINGS/1.0.0">
|
||||
<localRepository>C:/repo</localRepository>
|
||||
</settings>
|
Loading…
Reference in New Issue