반응형

웹 개발을 하다보면 서버에서도 유효성 검증을 필수로 하지만 프론트에서 1차적으로 필터링을 해주면 아무래도 서버에 부하도 적어지고 데이터 타입등 유효하지 않은 데이터를 걸러낼수 있어서, 자바스크립트를 통해 필수적으로 ajax나 submit 처리 전에 데이터 검사를 하게 됩니다.

 

 

문자열 검사하기

문자열을 검사하고 싶으면 생각보다 굉장히 간단하게 검사할 수 있습니다.

기존에는 undefined, null, 공백값등을 체크하는 조건문을 길게 쓰기 싫어서 함수를 만들어서 적용하는 방식으로 사용하거나 조건문에 길게 나열하곤 했었는데, 그 모습은 아래와 같습니다.

 

let text;
if(text === undefined || text === null || text === ''){
	alert("문자열이 비었습니다.");
}

하지만 실상 빈값에 대한 유효성 검증은 변수에 !(not 연산자) 하나만 붙여주면 됩니다.

 

 

 

NOT연산자 하나를 통한 유효성 검증

let text = '';
if(!text){
	alert("문자열이 비었습니다.");
}

!undefined //true
!null      //true
!''        //true
!0         //true
!NaN       //true

 

반응형
반응형

잘 납품되었던 프로젝트 뼈대를 가져와서 다른 프로젝트를 또 만들고 만들고... 이렇게 하다보니 이전에 배웠던 스프링 개념도 많이 흐릿해지고 헷갈리게 되는것 같습니다.

시간이 지나면서 기존 개념도 많이 잊어먹었고, 요구사항에 맞게 스프링 설정을 하면서 어려움을 겪는 느낌이 들어 다시한번 공부를 하고 정리를 해보고자 합니다.(기초지만 역시 주기적으로 다시 봐주고 정리할 필요가 있는 것 같습니다.😎)

 

구동순서

1. was(톰캣 등)를 통해 프로젝트를 구동을 하게되면 해당 프로젝트 내부의 web.xml을 로드합니다.

web.xml은 서블릿 배포 기술자로 DD(Deployment Descriptor)라고도 불립니다. WEB-INF 디렉토리에 존재하며 웹 어플리케이션의 설정을 구성하고 있습니다.

webapp-WEB-INF에 위치합니다.

 

 

2. web.xml 파일을 로드하면서 Servlet Container가 구동됩니다.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:egovframework/spring/context-*.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/egovframework/springmvc/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

<welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

 

3. Servlet Container가 구동되면서 ContextLoaderListener클래스가 자동으로 메모리에 생성됩니다. 

ContextLoaderListener는 서블릿 이전에 서블릿을 초기화 시켜주며 contextConfigLocation을 설정파일들의 위치를 지정시켜줍니다.

 

4. contextConfigLocation는 공통으로 사용할 의존성 설정파일들을 지정하며, <param-value>부분에 파일 경로를 입력해주면 됩니다.

저는 전자정부프레임워크를 사용하면서 기본 위치가 'egovframework/springmvc/'으로 되어있고, dispatcher-servlet.xml읽도록 설정했습니다.

 

5. dispatcher-servlet.xml을 읽어서 새로운 Spring Container를 생성하고, Controller 객체를 메모리에 올립니다.

 

6. dispatcher-servlet.xml(servlet-context.xml) 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:task="http://www.springframework.org/schema/task"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
                http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                http://www.springframework.org/schema/task
                http://www.springframework.org/schema/task/spring-task-3.0.xsd">
                
    <task:annotation-driven></task:annotation-driven>

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <context:component-scan base-package="egovframework"/>

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/jsp/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
</beans>

- annotation-driven: xml이 길고 복잡해지면서 대안으로 나타났습니다. 클래스, 메소드 등에 @를 달아 기능을 수행할 수 있도록 동작하기 위해 설정합니다. (xml Bean 등록이 더 우선순위가 높습니다.)

- mvc:annotation-driven: @Controller @RequestMapping 등 컨트롤러에서 해당하는 기능을 설정할 수 있게 해줍니다. 

- task:annotation-driven: @Scheduled 어노테이션을 사용을 위해 처리합니다.(스케줄러 관련)

- org.springframework.web.servlet.view.InternalResourceViewResolver: 컨트롤러가 모델을 리턴하고 DisapatcherServlet이 해당하는 파일을 처리할 때 필요한 정보를 기술하는 태그 접미사가 'WEB-INF/jsp'이면서 '.jsp'로 끝나는 파일들을 찾습니다. 

WEB-INF/jsp/login/login.jsp

- context:component-scan: 특정 패키지의 클래스들을 스캔하여 어노테이션을 확인하고 bean 등록을 한다. @Component @Controller @Service @Repository등이 클래스에 설정해야 등록된다.

 

 

 

 

 

웹 요청 동작순서

1. 사용자가 브라우저를 통해 주소를 입력합니다. - ( http://localhost/login.do )

web.xml에서 설정했던 정보에 맞는 요청일 경우 DispatcherServlet이 해당 요청을 먼저 가로채서 적합한 Controller에 매핑을 해줍니다.

 

2. <servlet-mapping/>부분이 있는데, 설정된 <servlet-name/>에 해당하는 매핑 설정을 할 수 있습니다.

예를들어 <url-pattern/>부분이 '/'으로 처리되어 있다면 모든 요청을 가로챕니다. 하지만 설정부분은 '*.do'로 되어있어서 'login.do'와 같은 형태만 적용이 됩니다.

 

3. 가로챈 매핑정보를 통해 처리할 Controller를 찾은 후, 적용된 코드에 따라 기능이 처리된다.

   3-1. Controller(mapping) -> View

   3-2. Controller(mapping) -> Service -> DAO -> mapper -> DB -> DAO -> Service -> Controller -> Return

 

4. 3-1과 같은 처리가 수행되면 ViewResolver에 의해 해당하는 view를 반환한다.

<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/jsp/" />
    <beans:property name="suffix" value=".jsp" />
</beans:bean>

dispatcher-servlet.xml에 설정된 ViewResolver부분에 맞는 prefix, suffix의 정보를 토대로 view페이지를 제공합니다.

(저는 tiles3를 적용해서 다르게 설정이 되어있습니다.)

@RequestMapping(value = "/login.do")
public ModelAndView login(ModelAndView mv) {
    mv.setViewName("login/login");
    return mv;
}

http://localhost/login.do -> '/WEB-INF/jsp/login/login.jsp'

 

5. 해당하는 viewDispatcherServlet에 보내면 DispatcherServlet은 클라이언트에게 데이터를 전송한다.

이때, 모델의 속성값이 있으면 같이 전송된다. (해당하는 값은 .jsp파일에서 ${변수명}을 통해 사용이 가능하다)

 

 

 

반응형
반응형

웹 개발을 진행하다보면 웹 접근성이라는 말이 나옵니다.

장애인분들이나 고령자분들도 웹 사이트를 사용하는데 불편함이 없도록 차별없게 기능을 제공하는 것을 말하는데, 탭키를 통한 모든 메뉴를 이동하는 기능이라던지 색맹, 색약분들을 위한 디자인 개선이라던지 다양한 방법으로 제공이 되도록 보장하는것을 말합니다.

 

그럼 개발자분들은 이런분들의 입장이 되어 개발을 진행해야하는데, 사실 내가 개발중인 사이트가 색맹, 색약인 분들이 봤을때, 어떤식으로 보일지 가늠이 안되는 경우가 있는데, 이럴때 크롬의 자체 지원 기능으로 간접적으로 경험을 하고 개발을 진행할 수 있습니다.

 

색맹모드 설정방법

1. F12 키등을 통해 관리자 도구를 실행합니다.

2. 상단 더보기(점 3개) - More tools - Rendering을 차례대로 누릅니다.

 

3. 추가된 Rendering 탭을 위로 올립니다.

 

4. 다양한 옵션들 중 Emulate vision deficiencies 찾습니다.(색맹 에뮬레이션)

* 관리자도구 옵션을 한글로 변경하여 보시면 설정이 더 편하게 처리 가능합니다.

변경하는방법은 최하단에 추가로 기재하였습니다.

 

5. 변경하고 싶은 옵션을 선택합니다.

  • Blurred vision : 흐릿하게 변경해줍니다. 근시효과
  • Protanopia : 적색맹
  • Deuteranopia : 녹색맹
  • Tritanopia : 청황색맹
  • Achromatopsia : 색맹

 

 

적용 테스트

기존 화면 색맹 모드(Achromatopsia)

파란계열 주황계열로 차트 구분을 해두었지만 색맹인분들에게는 거의 구분이 되지 않는다.

이를 해결하기 위해 차트에 질감처리를 추가하였다.

 

 

-색 구분을 위한 질감처리 추가

기존 화면(질감처리 추가) 색맹 모드(Achromatopsia)

질감 처리 추가로 2개의 비교 차트가 좀 더 구분되는 모습을 볼 수 있습니다.

 

 

 

크롬 브라우저 한글로 언어 변경하기

1. F12 관리자도구 열기

2. 톱니바퀴(환경설정)

3. 환경설정 - 언어 - 한국어 선택

반응형
반응형

어떤 블로거분 글을 보다가 너무 이쁘게 꾸며져 있어서 이건 뭘까하고 개발자 정신이 발동되어 f12부터 눌렀는데 자꾸 티스토리 페이지로 강제 이동이 되었다...

 

오잉 뭐지?! 하고 또 시도해봤는데.. 2차 팅...

이번엔 컨트롤 쉬프트 c로 강제로 열어봤는데 또 튕긴다...

관리자모드를 열어둔상태로 가봤는데도 튕기고 옵션에 찾아가서 클릭으로 열어도 튕긴다

 

... 우와 신기하다!!

 

어떻게 한건지 찾아보니 이미 감시하는 이벤트를 만들어두신분들이 있었다.

 

출처: https://dev.to/composite/a-simple-way-to-detect-devtools-2ag0

 

A simple way to detect devtools.

After I made this implementation, I found related issue on stack overflow....

dev.to

!function() {
  function detectDevTool(allow) {
    if(isNaN(+allow)) allow = 100;
    var start = +new Date(); // Validation of built-in Object tamper prevention.
    debugger;
    var end = +new Date(); // Validates too.
    if(isNaN(start) || isNaN(end) || end - start > allow) {
      // input your code here when devtools detected.
    }
  }
  if(window.attachEvent) {
    if (document.readyState === "complete" || document.readyState === "interactive") {
        detectDevTool();
      window.attachEvent('onresize', detectDevTool);
      window.attachEvent('onmousemove', detectDevTool);
      window.attachEvent('onfocus', detectDevTool);
      window.attachEvent('onblur', detectDevTool);
    } else {
        setTimeout(argument.callee, 0);
    }
  } else {
    window.addEventListener('load', detectDevTool);
    window.addEventListener('resize', detectDevTool);
    window.addEventListener('mousemove', detectDevTool);
    window.addEventListener('focus', detectDevTool);
    window.addEventListener('blur', detectDevTool);
  }
}();

if문쪽에 처리하고 싶은 로직을 넣으면 된다.

alert, location.href  등등

 

 

2022/05/12 추가----------------

당시 브라우저 설정이나 문제가 있었던거 같습니다. 결국 해당 코드도 디버거 효과로 중간에 강제로 읽게하고 꺼지는 기능인지라... 사실 관리자도구로 찍어볼만한건 다 찍어보고 끌수 있습니다.(즉 별 의미 없다는 소리입니다...ㅜㅠ)

반응형
반응형

자바스크립트 공부를 하다보면 심심치 않게 만나는 호이스팅이라는 개념...😲

들어보기도 하고 어느정도는 알고 있었지만 혼란스러운 부분이 있어서 정리가 필요했습니다!

 

먼저 간단한 자바스크립트 문법부터 보겠습니다.

 

 

함수의 호이스팅 동작

console.log("결과는? >" + test());
function test(){
    return "hi";
}

 

해당 코드의 결과는?! 아래와 같습니다!

함수 test를 호출함으로써 "hi"라는 문자열이 정상적으로 반환되어서 콘솔에 hi가 잘 찍히는걸 볼 수 있습니다.

 

실질적으로 정의된 test함수는 출력하는 콘솔로그보다 아래에 위치하지만 신기하게도 동작하는걸 볼 수 있는데 이러한 동작을 호이스팅이라고 합니다.

변수와 함수간에 동작이 조금 다른데, 이번엔 변수 동작부분을 확인해보겠습니다.

 

 

변수의 호이스팅 동작

var a = "테스트입니다~";
function test(){
    console.log("a1 >",a);
    var a = "테스트 함수에서 a변수를 재정의합니다!";
    console.log("a2 >",a);
}
test();

이번에 작성한 코드의 결과는 어떨까요? 저도 이부분에 처음에 당황을 했습니다.

 

결과는 아래와 같습니다.

예상한 결과와 같나요?

제가 처음 예상했던 답은 "테스트입니다~"가 먼저 출력되고, 재정의되었다는 문자열이 출력 될것이라고 생각했지만, 호이스팅으로 인해 전혀 다른 결과가 나왔습니다.

 

아래는 호이스팅 방식을 기반으로 해석한 코드입니다.

var a = "테스트입니다~";
function test(){
    var a;
    console.log("a1 >",a);  //undefined
    a = "테스트 함수에서 a변수를 재정의합니다!";
    console.log("a2 >",a);  //테스트 함수에서 a변수를 재정의합니다!
}
test();

해석한 코드와 기존 코드의 차이가 보이나요?

호이스팅으로 인해 test함수 최상단에 a라는 변수가 선언되었고, 콘솔로그에서 출력이 되면서 undefined라는 결과물이 나왔고, 그 이후 대입연산자를 통해 재정의라는 문자열이 대입된 것을 볼 수 있습니다.

 

이처럼 블록 구문(함수) 최상단에 변수 또는 함수명인 식별자를 최상단으로 끌어올리는 효과를 보여주는 것을 호이스팅이라고 부릅니다.

 

 

복잡한 호이스팅 동작과 전역 스코프

이번엔 복잡해진 호이스팅을 확인해보겠습니다.

var a = 1;
function outer () {
    function inner () {
        console.log(a);
        var a = 3;
        console.log(a);
        var a;
        console.log(a);
        var a = 5;
        console.log(a);
    }
    inner();
    console.log(a);
}
outer();
console.log(a);

복잡한 결과물

 

복잡하지만 차근차근 호이스팅을 분석해보겠습니다.

var a;
a = 1;
function outer () {
    function inner () {
        var a;
        var a;
        var a;

        console.log(a); //undefined
        a = 3;
        console.log(a); //3
        console.log(a); //3
        a = 5;
        console.log(a); //5
    }
    inner();
    //내부에 a변수가 없으므로 외부 스코프영역에서 a가 존재하는지 찾음
    console.log(a); //1
}
outer();
console.log(a); //1

어떤가요? 분석구문과 실제 결과와 동일한가요?

내부 스코프에 해당 변수의 선언된게 없으면 외부 스코프에서 해당 변수가 선언되었는지 확인을 합니다.

아우터부분의 a에서 그래서 1이 찍히는걸 볼 수 있습니다.

 

 

정리

다만, 변수와 함수는 또 다른 차이점이 있습니다.

- 변수는 변수명만 최상단에 정의 된 후 선언 위치에서 값이 대입되는 모습을 보여줍니다.

- 함수는 정의부 자체와 함께 최상단에 선언되는 것을 볼 수 있습니다.

 

이러한 차이점때문에 함수는 처음에 호출하여도 정의부가 잘 동작 후 출력되는것을 볼 수 있엇고, 변수는 최초 선언만 되었기 때문에, 결과 호출시 undefined와 같은 결과를 만날 수 있습니다.

 

 

다만, 함수 동작의 호이스팅에서 함수 정의 방법에 따라 달라는 결과를 볼 수 있습니다.

 

 

다양한 함수 표현방법의 호이스팅 결과

console.log(test1());
console.log(test2());
console.log(test3());

function test1(){
    return "test1";
}

var test2 = function(){
    return "test2";
}

var test3 = () => {
    return "test3";
}

function 키워드를 통해 함수를 정의하지만 변수에 함수를 대입하여 함수의 동작을 처리할 수도 있고, 화살표 함수를 통해 표현할 수도 있습니다.

다만 호이스팅 처리방식의 변수와 함수 표현식의 차이로 인해 test2, test3는 정상적으로 동작하지 않습니다.

 

아래는 동작 결과입니다.

초점을 동작하는 행위에 두지 않는다면 당연한 결과일수도 있습니다. (변수로 시작했는지, 함수로 시작했는지)

상단에 정의된 내용까지 호이스팅하는 동작은 function키워드로 정의한 부분만 동작하는 것을 볼 수 있습니다.

반응형
반응형

npm을 통해 리액트 프로젝트를 생성하려고 했는데, 아래와 같이 반복적으로 오류가 발생하고 있었다.

 

업데이트도 안되고 npm의 버전조차 나오지 않았다.

(여러 강의나 다른글의 모듈등을 설치하면서 버전간에 문제가 있게되었고 설정이 꼬인거 같았다...😣)

 

해결방법

1. npm 재설정 처리를 위해 다음과 같이 기존 캐시 파일들을 삭제하였다.

C:/Users/계정명/AppData/Roaming/ 이동

npm, npm-cache 디렉토리 2개 삭제

 

 

2. 다음 명령어를 입력한다.

npm cache clean

 

해결!😎

정상적으로 설치버전까지 나온다!

 

 

반응형
반응형

늦은감이 있지만 리액트 스터디를 재시작했습니다.

 

언어를 습득하는데 작은 프로젝트라도 해보는게 머리에 잘 남기에 개인 이력이 담긴 포트폴리오를 작성하고 깃허브에 올리는 프로젝트를 수행 중이였습니다.

 

여러가지 최대한 아는 기능선에서 만들어보고 있었는데, 자기 소개를 하는 페이지를 작성하는 부분의 텍스트를 실시간으로 타이핑하는 효과를 주기 위해 이미 작성 된 데이터를 담아둔 배열을 setInterval을 통해 표현하면 될 거라고 생각하고 작성을 시작했습니다.

 

useEffect를 통해 로드 시 한번만 setInterval이 동작하도록 하였고, 생각보다 많은 시행착오를 겪고 나서 처리까지 완료가 되었습니다.

 

아래는 처음 작성해 본 예제입니다.

Introduce.js

import { useState, useEffect } from "react";
import {MdKeyboardReturn} from  "react-icons/md";
import "./Introduce.scss";
import bg_img from "../../../assets/imgs/monet.jpg";

const Introduce = () => {
    const OPTION = {
        writeArr: [],
        arrIndex: 0,
        index: 0
    };
    const WORD_TYPING_SPEED = 80;
    const msgArr = [
        " 안녕하세요? 웹 개발자 박신우입니다.",
        " 저를 소개하기 위한 페이지를 만들었습니다.",
        " 저의 정보를 확인해주세요.",
    ];

    const [introduce, setIntroduce] = useState([]);

    const onChangeMsg = (o) => {
        const msg = msgArr[o.arrIndex];
        o.writeArr[o.arrIndex] = msg.substring(0, ++o.index);
        if(o.index > msg.length) {
            o.index = 0;
            if(++o.arrIndex >= msgArr.length){
                o.arrIndex = 0;
                o.writeArr.splice(0, o.writeArr.length);
            }
        }
        setIntroduce(() => [...o.writeArr]);
    };

    useEffect(()=>{
        const interval = setInterval(()=>{
            onChangeMsg(OPTION);
        }, WORD_TYPING_SPEED);
        return () => clearInterval(interval);
    }, []);
    
    return (
        <div id="introduce" className="Introduce" style={{background: `url(${bg_img}) no-repeat`}}>
            <h2 className="introduce_msg">
                {
                    introduce.map((m, i) => (
                        msgArr[i].length === m.length ? <p key={i}>{m}</p>
                        : <p key={i}>{m}<MdKeyboardReturn /></p>                    
                    ))
                }
            </h2>
        </div>
    );
}

export default Introduce;

나름 UI를 확인해봤을땐 별 문제 없이 잘 동작하는것으로 보였습니다.

 

하지만, 관리자 모드를 열어서 확인해보니 Hook관련 에러가 계속해서 발생하는 것을 확인했습니다.😣

 

처리 방법

검색을 하다보니 setInterval과 react의 동작 구조 개념에서 발생하는 현상이라는 번역 글을 확인했습니다.

 

아래는 제가 참고한 블로그 글입니다.

https://velog.io/@jakeseo_me/%EB%B2%88%EC%97%AD-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90%EC%84%9C-setInterval-%EC%82%AC%EC%9A%A9-%EC%8B%9C%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%A0%90

 

번역 / 리액트 훅스 컴포넌트에서 setInterval 사용 시의 문제점

Dan abramov의 https://overreacted.io/making-setinterval-declarative-with-react-hooks/ 번역입니다.All copyrights to Dan Abramovtranslated by Jake seoTHE

velog.io

 

setInterval대신 커스텀으로 작성된 컴포넌트 useInterval을 사용하라는 간단한 제안이 되어있습니다.

 

별도로 컴포넌트를 작성후 import하여 해결하였고, 그 결과는 아래 소스입니다.

useInterval.js

import { useEffect, useRef } from 'react';

const useInterval = (callback, delay) => {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

export default useInterval;

 

Introduce.js

import { useState } from "react";
import useInterval from "../../common/useInterval";
import {MdKeyboardReturn} from  "react-icons/md";
import "./Introduce.scss";
import bg_img from "../../../assets/imgs/monet.jpg";


const OPTION = {
    writeArr: [],
    arrIndex: 0,
    index: 0
};
const WORD_TYPING_SPEED = 80;
const msgArr = [
    " 안녕하세요? 웹 개발자 박신우입니다.",
    " 저를 소개하기 위한 페이지를 만들었습니다.",
    " 저의 정보를 확인해주세요.",
];

const Introduce = () => {
    const [introduce, setIntroduce] = useState([]);

    const onChangeMsg = (o) => {
        const msg = msgArr[o.arrIndex];
        o.writeArr[o.arrIndex] = msg.substring(0, ++o.index);
        if(o.index > msg.length) {
            o.index = 0;
            if(++o.arrIndex >= msgArr.length){
                o.arrIndex = 0;
                o.writeArr.splice(0, o.writeArr.length);
            }
        }
        setIntroduce(() => [...o.writeArr]);
    };

    useInterval(() => {
        onChangeMsg(OPTION);
    }, WORD_TYPING_SPEED);

    return (
        <div id="introduce" className="Introduce" style={{background: `url(${bg_img}) no-repeat`}}>
            <h2 className="introduce_msg">
                {
                    introduce.map((m, i) => (
                        msgArr[i].length === m.length ? <p key={i}>{m}</p>
                        : <p key={i}>{m}<MdKeyboardReturn /></p>                    
                    ))
                }
            </h2>
        </div>
    );
}

export default Introduce;

소스도 한결 가벼워지고 더 이상 오류문구도 발생하지 않는걸 볼 수 있습니다.

useInterval에서 useEffect를 통해 setInterval을 처리하기 때문에 useEffect로 감싸지 않는것을 볼 수 있습니다.

반응형
반응형

적용은 차근차근 따라오면 크게 어렵지 않게 적용이 가능합니다. maven을 통해 적용합니다.

 

프로젝트에 lucy적용하기

pom.xml

<!-- https://mvnrepository.com/artifact/com.navercorp.lucy/lucy-xss-servlet -->
<dependency>
    <groupId>com.navercorp.lucy</groupId>
    <artifactId>lucy-xss-servlet</artifactId>
    <version>2.0.1</version>
</dependency>

다음은 필터를 프로젝트에 설정해주어야 합니다.

 

 

 

web.xml

<!-- 멀티파트 필터링을 위한 설정 -->
<filter>
    <filter-name>multipartFilter</filter-name>
    <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>multipartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- lucy 설정 -->
<filter>
    <filter-name>xssEscapeServletFilter</filter-name>
    <filter-class>com.navercorp.lucy.security.xss.servletfilter.XssEscapeServletFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>xssEscapeServletFilter</filter-name>
    <!-- <url-pattern>/*</url-pattern> -->
    <url-pattern>*.do</url-pattern>
</filter-mapping>

멀티파트필터 없이 경우에는 루시필터만 적용하게되면 멀티파트의 행위에 대해서는 감시가 이루어지지 않기 때문에 해당 필터를 적용해주셔야 합니다!

 

 

 

lucy-xss-servlet-filter-rule.xml

- resources/ 하위에 해당 파일을 생성하고 위치시킵니다.

<?xml version="1.0" encoding="UTF-8"?>
 
<config xmlns="http://www.navercorp.com/lucy-xss-servlet">
    <defenders>
        <!-- XssPreventer 등록 -->
        <defender>
            <name>xssPreventerDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssPreventerDefender</class>
        </defender>

        <!-- XssSaxFilter 등록 -->
        <defender>
            <name>xssSaxFilterDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssSaxFilterDefender</class>
            <init-param>
                <param-value>lucy-xss-sax.xml</param-value>   <!-- lucy-xss-filter의 sax용 설정파일 -->
                <param-value>false</param-value>        <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 -->
            </init-param>
        </defender>

        <!-- XssFilter 등록 -->
        <defender>
            <name>xssFilterDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssFilterDefender</class>
            <init-param>
                <param-value>lucy-xss.xml</param-value>    <!-- lucy-xss-filter의 dom용 설정파일 -->
                <param-value>false</param-value>         <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 -->
            </init-param>
        </defender>
    </defenders>

    <!-- default defender 선언, 필터링 시 지정한 defender가 없으면 여기 정의된 default defender를 사용해 필터링 한다. -->
    <default>
        <defender>xssPreventerDefender</defender>
    </default>

    <!-- global 필터링 룰 선언 -->
    <global>
        <!-- 모든 url에서 들어오는 globalParameter 파라메터는 필터링 되지 않으며
                또한 globalPrefixParameter1로 시작하는 파라메터도 필터링 되지 않는다.
                globalPrefixParameter2는 필터링 되며 globalPrefixParameter3은 필터링 되지 않지만
                더 정확한 표현이 가능하므로 globalPrefixParameter2, globalPrefixParameter3과 같은 불분명한 표현은 사용하지 않는 것이 좋다. -->
        <params>
            <param name="globalParameter" useDefender="false" />
            <param name="globalPrefixParameter1" usePrefix="true" useDefender="false" />
            <param name="globalPrefixParameter2" usePrefix="true" />
            <param name="globalPrefixParameter3" usePrefix="false" useDefender="false" />
        </params>
    </global>

    <!-- url 별 필터링 룰 선언 -->
    <url-rule-set>

        <!-- url disable이 true이면 지정한 url 내의 모든 파라메터는 필터링 되지 않는다. -->
        <url-rule>
            <url disable="true">/disableUrl1.do</url>
        </url-rule>

        <!-- url disable이 false인 설정은 기본이기 때문에 불필요하다. 아래와 같은 불필요한 설정은 하지 않는다.-->
        <url-rule>
            <url disable="false">/disableUrl2.do</url>
        </url-rule>

        <!-- url disable이 true이면 지정한 url 내의 모든 파라메터가 필터링 되지 않기 때문에 <params> 로 선언한 설정은 적용되지 않는다. 
               아래와 같은 불필요한 설정은 하지 않는다. -->
        <url-rule>
            <url disable="true">/disableUrl3.do</url>
            <params>
                <param name="query" useDefender="false" />
                <param name="prefix1" usePrefix="true" />
                <param name="prefix2" usePrefix="false" useDefender="false" />
                <param name="prefix3" usePrefix="true" useDefender="true" />
                <param name="prefix4" usePrefix="true" useDefender="false" />
                <param name="prefix5" usePrefix="false" useDefender="true" />
            </params>
        </url-rule>

        <!-- url disable이 false인 설정은 기본이기 때문에 불필요하다. <params> 선언한 설정은 적용이 된다.-->
        <url-rule>
            <url disable="false">/disableUrl4.do</url>
            <params>
                <!-- disableUrl4.do 의 query 파라메터와 prefix4로 시작하는 파라메터들은 필터링 되지 않는다. 
                        usePrefix가 false, useDefender가 true인 설정은 기본이기 때문에 불필요하다. -->
                <param name="query" useDefender="false" />   
                <param name="prefix1" usePrefix="true" />
                <param name="prefix2" usePrefix="false" useDefender="false" />
                <param name="prefix3" usePrefix="true" useDefender="true" />
                <param name="prefix4" usePrefix="true" useDefender="false" />
                <param name="prefix5" usePrefix="false" useDefender="true" />
            </params>
        </url-rule>

        <!-- url1 내의 url1Parameter는 필터링 되지 않으며 또한 url1PrefixParameter로 시작하는 파라메터도 필터링 되지 않는다. -->
        <url-rule>
            <url>/url1.do</url>
            <params>
                <param name="url1Parameter" useDefender="false" />
                <param name="url1PrefixParameter" usePrefix="true" useDefender="false" />
            </params>
        </url-rule>

        <!-- url2 내의 url2Parameter1만 필터링 되지 않으며 url2Parameter2는 xssSaxFilterDefender를 사용해 필터링 한다.  -->
        <url-rule>
            <url>/url2.do</url>
            <params>
                <param name="url2Parameter1" useDefender="false" />
                <param name="url2Parameter2">
                    <defender>xssSaxFilterDefender</defender>
                </param>
            </params>
        </url-rule>
    </url-rule-set>
</config>

네이버 제공 샘플을 참고하여 자기 프로젝트에 맞게 설정을 변경해주세요.

(작성하다보니 XssFilter, XssSaxFilter 부분에 lucy-xss-sax.xml, lucy-xss.xml를 적용하는 부분이 있는데 별도로 생성하지 않아도 문제없이 잘 동작했습니다.)

 

중요한 부분은 필터링을 제외처리할 부분과 특정 파라미터만 제외할 부분인데, 에디터등을 사용한 프로젝트라면 당연히 html코드로 넘어오기 때문에 제외처리를 잘 해주셔야합니다.

lucy필터의 특징은 넘어오는 파라미터를 필터링(치환)을 처리해주는거지 필터에서 컨트롤러접근을 막아주는 필터는 아닌점만 인지하고 적용하시면 될 것 같습니다.

필터링된 모습

 

 

 

* 멀티파트(Multipart)에서도 필터링이 되어야하는분들은 추가 작업이 있습니다

이후 구동하는 톰캣의 config디렉토리의 context.xml을 열어줍니다.

기존 소스가 아래와 같은 형태로 되어 있을텐데,

<Context>
    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
    <CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />
    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
</Context>

 

Context 태그부분에 allowCasualMultipartParsing="true" path="/"를 추가해줍니다.

<Context allowCasualMultipartParsing="true" path="/">
    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
    <CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />
    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->
</Context>

* 이클립스에서는 Package Explorer에 Server라는게 보이는데 구동하고 계신 톰캣의 conf들이 모여있습니다. 여기서 변경하시면 됩니다.

 

 

이것으로 lucy filter 적용 및 설정은 끝났습니다. xss공격으로부터 한층 부담을 덜고 개발을 진행하실 수 있습니다.

반응형