diff --git a/pom.xml b/pom.xml index 50f7ffb8..2d5ef13a 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,13 @@ 2.6.0 + + + org.apache.commons + commons-lang3 + 3.16.0 + + diff --git a/src/main/java/com/interplug/qcast/config/json/HtmlCharacterEscapes.java b/src/main/java/com/interplug/qcast/config/json/HtmlCharacterEscapes.java new file mode 100644 index 00000000..3e3ac620 --- /dev/null +++ b/src/main/java/com/interplug/qcast/config/json/HtmlCharacterEscapes.java @@ -0,0 +1,59 @@ +package com.interplug.qcast.config.json; + +import com.fasterxml.jackson.core.SerializableString; +import com.fasterxml.jackson.core.io.CharacterEscapes; +import com.fasterxml.jackson.core.io.SerializedString; +import org.apache.commons.lang3.StringEscapeUtils; + +/** + *
+ * xss 방어
+ *
+ * 
+ * + * @author KimYoungHyun (youngh.kim@kt.com) + */ +public class HtmlCharacterEscapes extends CharacterEscapes { + + private static final long serialVersionUID = 2432838078852295950L; + private final int[] asciiEscapes; + + /** + * xss 방지를 위해 escape 처리 + */ + public HtmlCharacterEscapes() { + asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON(); + asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['\"'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['('] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes[')'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['#'] = CharacterEscapes.ESCAPE_CUSTOM; + } + + @Override + public int[] getEscapeCodesForAscii() { + return asciiEscapes; + } + + @Override + public SerializableString getEscapeSequence(int ch) { + SerializedString serializedString = null; + char charAt = (char) ch; + + // emoji(Emoticons) character + if (Character.isHighSurrogate(charAt) || Character.isLowSurrogate(charAt)) { + StringBuilder sb = new StringBuilder(); + sb.append("\\u"); + sb.append(String.format("%04x", ch)); + serializedString = new SerializedString(sb.toString()); + } else { + serializedString = new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString(charAt))); + } + + return serializedString; + } + +} diff --git a/src/main/java/com/interplug/qcast/config/security/WebConfig.java b/src/main/java/com/interplug/qcast/config/security/WebConfig.java new file mode 100644 index 00000000..4d2ad7d4 --- /dev/null +++ b/src/main/java/com/interplug/qcast/config/security/WebConfig.java @@ -0,0 +1,46 @@ +package com.interplug.qcast.config.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.interplug.qcast.config.json.HtmlCharacterEscapes; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + *
+ * Web Config
+ * 
+ * + * @author jaeyoung_lee (kkang090@gmail.com) + */ +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final ObjectMapper objectMapper; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOriginPatterns("*").allowCredentials(true).exposedHeaders("Authorization") + .allowedMethods("GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS"); + } + + /** + *
+	 * XSS(Cross Site Scripting) converter
+	 * 
+ * + * @author KimYoungHyun (youngh.kim@kt.com) + * @return + */ + @Bean + public MappingJackson2HttpMessageConverter jsonEscapeConverter() { + ObjectMapper copy = objectMapper.copy(); + copy.getFactory().setCharacterEscapes(new HtmlCharacterEscapes()); + return new MappingJackson2HttpMessageConverter(copy); + } + +}