ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JSP] JSP 실무 프로젝트 셋팅과 활용
    frontend/고전 2022. 3. 21. 15:29

    오늘은 JSP의 실무 프로젝트에서 활용에 대해서 적어 보겠습니다. JSP라고 하면 프론트엔드에서는 클래식이라고 볼 수 있을 것 같습니다. React, Vue, Angular, Svelte, Solid 등이 프론트엔드 춘추전국시대를 만들기 전에 아주 많이 사용했었습니다. 물론, 중소 SI쪽에서는 아직도 사용하는 곳이 많은 걸로 알고 있습니다. 혹시나 아직 수요가 있을가 하는 마음에 JSP의 효율적인 실무 셋팅에 대해서 글을 써보겠습니다.

     

    글 마지막에 소스코드 템플릿 만들어서 공유 해두었습니다.

    1. 스프링 설정

    // application.yml
    spring:
      mvc:
        view:
          prefix: /WEB-INF/views/
          suffix: .jsp
    
    // application.properties
    spring.mvc.view.prefix: /WEB-INF/views/
    spring.mvc.view.suffix: .jsp

    가장 먼저, 스프링 부트의 설정 파일인 application.yml / application.properties에 JSP를 호출하기 위한 설정을 해줍니다. 개인적으로는 application.yml을 선호합니다.

     

    2. 디렉토리 구조

        webapp
        ├─ resources
        └─ WEB-INF
            ├─ tags
            └─ views

    디렉토리는 위의 설정 값 처럼 src/main 하위에 webapp이라는 폴더를 생성하고 위와 같은 형식으로 생성합니다. 위의 구조에서 tags에는 공통으로 사용하고자 하는 JSTL tag를 정의하고 views에는 화면들을 코딩합니다.

     

    3. 코드 구조

    프로젝트 구조는 크게 tags(구조)와 view(화면코드)로 나누어 개발하도록 합니다. tags는 공통으로 사용되는 스크립트 및 메타태그 등을 정의 해놓을 수 있어 해당 파일 한번만 변경하여 전체에 영향을 줄 수 있습니다.

     

    layout.tag
    <%@ tag language="java" pageEncoding="UTF-8" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <!DOCTYPE html>
    <html lang="ko">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width"/>
        <title>류호진 JSP 가이드라인</title>
        <link rel="stylesheet" href='<c:url value="/resources/css/guide.css"/>'>
    </head>
    <body>
        <jsp:include page="/WEB-INF/views/common/header.jsp"/>
        <jsp:include page="/WEB-INF/views/common/sidebar.jsp"/>
        <jsp:doBody/>
        <jsp:include page="/WEB-INF/views/common/footer.jsp"/>
        <script src='<c:url value="/resources/js/guide.js"/>'/>
        <jsp:invoke fragment="script"/>
    </body>
    </html>

    위의 코드는 layout.tag 입니다. 커스텀 tag library를 이용하여 위와 같이 작성하여 주세요. css는 <head> 끝에 스크립트는 바디 마지막에 작성하여 줍니다. 자바스크립트를 <head>에 포함할 경우, 자바스크립트 파일을 전부 다운로드 하고, 파싱, 컴파일 할 때까지 페이지 렌더링이 지연되기 때문에 <body>하단에 포함하도록합니다.

     

    위의 코드를 잘게 나눠보도록 하겠습니다.

     

    공통(헤더,사이드바,메뉴 등등..) 부분
    <jsp:include page="/WEB-INF/views/common/header.jsp"/>
    <jsp:include page="/WEB-INF/views/common/sidebar.jsp"/>
    <jsp:include page="/WEB-INF/views/common/footer.jsp"/>
    화면이 노출될 부분
    <jsp:doBody/>

    위와 같이 크게 공통적으로 사용될 JSP부분과 화면을 송출할 JSP부분으로 나뉘어지게 됩니다. 그럼 doBody에 들어갈 부분을 어떻게 작성해야 되는지 알아보겠습니다. (화면전환은 Spring에 PageController로 하는 것을 안다고 생각하고 화면부분만 설명드리겠습니다.)

     

    실제 화면 부분(doBody)의 JSP는 아래와 같이 작성하셔야 됩니다.

     

    sample.jsp(화면) JSP만 존재하는 경우
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
    
    <t:layout>
        //JSP내용
    </t:layout>
    sample.jsp(화면) 화면내 스크립트 존재하는 경우
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
    <jsp:attribute name="script">
        //스크립트 작성
    </jsp:attribute>
    <t:layout>
        //JSP내용
    </t:layout>

    위의 코드에서 <t:layout></t:layout> 내부에 작성된 코드가 doBody부분에 출력이 됩니다. 또한 해당 페이지 내에서 동작할 함수 등을 스크립트 태그 내부에 작성하여 주시면 됩니다.

     

    이렇게 작성된 프로젝트에서 컨트롤러를 통하여 해당 예시(ex.sample.jsp)를 호출하면 layout.tag 파일의 구조처럼 위 아래로 헤더와 푸터가 결합되어 화면에 렌더링 됩니다. 프로젝트 진행 시 위와 같은 방식으로 프론트 부분을 구조화 한다면 좀 더 관리가 편한 프로젝트가 될 수 있다고 생각합니다. JSP가 좀 고전 같은 느낌이 많이 들지만.. 어쩔 수 없이 해야 된다면 가장 느낌있는 방식으로 만들고 싶었습니다.

     

    이런 구조화를 응용한다면 화면 depth를 표현할 때도 유용하게 사용할 수 있습니다. 아래 화면은 tags폴더 하위에 작성된 content-header.tag 코드 입니다.

     

    content-header.tag
    <%@ tag language="java" pageEncoding="UTF-8"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@ attribute name="title" type="java.lang.String"%>
    <%@ attribute name="breadcrumb" type="java.lang.String"%>
    
    <section class="content-header">
    <h2>
        <strong><i class="fa fa-play-circle"></i><c:out value="${title}"/></strong>
    </h2>
    <ol class="breadcrumb">
        <li><a href="<c:url value="/"/>"><i class="fa fa-home"></i> Home</a></li>
    
        <%
            String[] items = breadcrumb.split("\\/");
            for (String item : items) {
                if (title.equals(item)) {
                    out.print("<li class='active'>" + item + "</li>");
                }
                else {
                    out.print("<li>" + item + "</li>");
                }
            }
        %>
    </ol>
    </section>

    위의 코드는 공통적으로 자주 사용되는 DEPTH 표기화면을 쉽게 추가하기 위한 BreadCrumb 입니다. 태그에 attribute를 통하여 데이터를 넘기고 넘겨받은 데이터를 내부 로직을 통해 화면에 적절히 뿌려주기만 한다면 많은 응용이 가능할 것으로 생각됩니다. 모던 프레임워크 들에서 props로 넘기던 것을 attribute로 넘긴다라고 생각하면 편합니다.

     

    sample.jsp(화면) 네의 <t:content-header> 사용 예시
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
    <t:layout>
    <t:content-header title="타이틀명" breadcrumb="대메뉴/중메뉴"/>
        <script>
    
        </script>
    </t:layout>

    실제로 화면에서는 위와 같이 작성하시면 간편하게 사용할 수 있습니다. 이제 구조에 대해서 전체적으로 파악이 끝났다면 공통 함수를 작성해봅시다.

     

    4. 유틸

     

    가장 많이 사용하는 공통 함수 몇가지만 일단 보겠습니다.

     

    JSP를 활용한 프로젝트에서 API통신시 가장 많이 사용되는 Ajax 공통 작업에 대해 먼저 알아 보겠습니다. 우선 webapp/resources/js 하위에 utils.js를 생성해주세요. 

     

    common.js(Ajax 통신)
    //webapp/resources/js/common.js
    
    var Utils = {};
    
    Utils.ajax = function (options, successcb, failcb) {
        $.ajax({
            type: options.type || 'post',
            url: options.url,
            data: options.data,
            beforeSend: function () {
    
            },
            success: function () {
                var data = arguments[0];
    
                successcb(data);
            },
            error: function () {
                var jsonError = arguments[0].responseJSON;
                //에러 처리
                if (failcb) {
                    failcb(arguments);
                }
            },
            complete: function () {
    
            }
        });
    }

    가장 먼저 Utils에 Api통신을 위한 Ajax 공통 함수를 만들어 줍니다. 위에 보시다 싶히 default는 post로 이루어져있고 url과 data를 넘겨줍니다. 또한 전처리과정은 beforeSend에 후처리는 success, error, complete에서 처리합니다.

     

    common.js(폼 데이터 -> Object형태 변환)
    Utils.getFormValue = function (formObj) {
        var arr = formObj.serializeArray();
        if (arr && arr.length > 0) {
            var values = {};
            for (var i in arr) {
                var obj = arr[i];
                values[obj.name] = obj.value
            }
            return values;
        }
        return null;
    }

    Form내의 내용을 배열로 읽어서 그 input들의 name 어트리뷰트를 key값 으로 key-value쌍으로 변환하는 코드입니다.

     

    common.js(오브젝트 -> 폼 데이터)
    Utils.setFormValue = function (formObj, jsonData) {
        Utils.getFormValue(formObj);
        $.each(jsonData, function (key, value) {
            var ctrl = formObj.find('[name=' + key + ']');
            if (ctrl.is('select')) {
                $('option', ctrl).each(function () {
                    if (this.value == value)
                        this.selected = true;
                });
            } else if (ctrl.is('textarea')) {
                ctrl.val(value);
            } else {
                switch (ctrl.attr("type")) {
                    case "text":
                    case "hidden":
                        ctrl.val(value);
                        break;
                    case "checkbox":
                        if (value == 'on') {
                            ctrl.prop('checked', true);
                        } else {
                            ctrl.prop('checked', false);
                        }
                        break;
                }
            }
        });
    }

    통신 후 response 값을 편하게 셋팅하기 위한 오브젝트 -> 폼데이터 변환 유틸입니다. 데이터를 읽어서 맞는 타입에 알아서 셋팅 되게 작성하였습니다.

     

    이렇게 작성되어진 공통 함수들을 실제 화면에서 어떻게 사용해야 되는 지 알아 보도록 하겠습니다.

     

    데이터 통신(AJAX)
    Utils.ajax({
        url: 'api/sample/test',
        type: 'POST',
        data: jsonData
    }, function (data) {
        //성공시 수행
    }, function (data){
        //실패시 수행
    })
    폼 데이터 처리(폼->오브젝트)
    var jsonData = Utils.getFormValue($('#form'));
    폼 데이터 처리(오브젝트 -> 폼)
    Utils.setFormValue($('#form'), data)

     

    기본적인 구조만 알고 API통신에 대한 공통 코드 작성을 마치겠습니다. 추가적으로 필요한 유틸들을 계속 붙여나가시면 됩니다.

     

    4. 보안 취약점

     

    JSP를 사용하시는 분들은 대부분 SI 사업을 수행하는 분들이 많을 것이라고 생각합니다. 프로젝트 하다보면 정적 검사, 부하 검사, 보안 검사 등을 하실텐데 보안 취약점에 걸릴만한 부분들을 사전에 방지해 봅시다.

     

    캐시 컨트롤 (cache-control) 웹 취약점 
    <meta http-equiv="Cache-Control" content="no-store"/>
    <meta http-equiv="Cache-Control" content="no-cache"/>
    <meta http-equiv="Cache-Control" content="private"/>
    <meta http-equiv="Pragma" content="no-cache"/>
    <meta http-equiv="Expires" content="-1"/>

    캐시 컨트롤 취약점은 Spring Boot + JSP 프로젝트시 단골로 나오는 취약점입니다. layout.tag 내에 <head></head>안에 <meta>를 추가하여 줍니다.

     

    CSRF(크로스 사이트 요청 위조)
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}""/>

    Spring Boot + JSP 조합에서 무작정 진행하다보면 무조건 CSRF 취약점이 나오게 됩니다. 이러한 상황을 빠르게 벗어나기 위해서 meta에 위의 코드를 추가합니다.

     

    그 후, Ajax로 통신 할때 위의 메타 내의 토큰값을 읽어서 전처리과정에 아래와 같이 추가하면 해당 문제가 발생하지 않습니다.

    Utils.ajaxTest = function (options, successcb, failcb) {
        var header = $("meta[name='_csrf_header']").attr("content")
        var token = $("meta[name='_csrf']").attr("content")
        $.ajax({
            type: options.type || 'post',
            url: options.url,
            data: options.data,
            beforeSend: function (xhr) {
                xhr.setRequestHeader(header, token)
            },
            success: function () {
            },
            error: function () {
            },
            complete: function () {
            }
        });
    }

    위와 같은 방식으로 데이터를 전송하기 전에 HTTP header에 CSRF를 셋팅하여 주면 이러한 문제점이 발생하지 않게 됩니다. (서버쪽 셋팅도 하셔야 합니다. 이것은 프론트엔드만을 위한 글입니다.)

     

    또한, 화면 리스트에서 상세화면으로 ID값을 넘겨 상세화면에서 해당ID를 토대로 데이터를 조회하는 로직으로 동작할 때에도 CSRF문제가 생깁니다. 이런 문제를 해결하기 위한 방법으로는 아래와 같은 방법이 있습니다.

    <form id="sendData" method="POST">
        <input type="hidden" name="id" id="id"/>
        <input type="hidden" name="_csrf" id="_csrf"/>
    </form>

    위와 같이 전송할 데이터인 ID와 함께 전송할 CSRF토큰을 담을 FORM을 생성합니다.

    var token = $("meta[name='_csrf']").attr("content");
    $("#sendData").attr('action','<c:url value="/조회주소"/>');
    $("#sendData").find('#id').val(args.item.memberId);
    $("#sendData").find('#_csrf').val(token);
    $("#sendData").submit();

    그리고 데이터의 ROW를 클릭하였을 때 해당 ROW의 ID와 매타태그에서 _CSRF값을 받아와 FORM에 셋팅해주고 SUBMIT을 통하여 통신하시면 됩니다.

     

    비밀번호 자동완성
    <form action="scuirty.html" method="get">      
        Username: <input type="text" name="firstname" /><br />      
        Password: <input type="password" name="lastname" autocomplete="off"/>      
    <input type="submit" value="Submit" />  <form>

    비밀번호 자동완성 또한 보안 취약점에 자주 등장합니다. autocomplete를 꺼두는 습관을 가지도록 합시다. SI 프로젝트 외에도 자주 발생할 수 있는 문제입니다.

     

    이 외에도 Jquery를 상요하는 프로젝트에서 데이터를 읽을 때 ${변수명}으로 읽지 마시고 jstl을 이용하여 <c:out value="${변수명}"/>을 습관화 하도록 합시다.

     

    내용이 정말 길어진거 같습니다. 솔직히 다 따로 분리해놓고 보면 오히려 이해가 잘 안될 수 있으니 제가 예전에 작성해둔 템플릿을 첨부하겠습니다. 오랜만에 JSP에 관한 내용을 쓰니깐 새롭네요. 예전엔 이렇게 개발했엇지.. 이런느낌도 들고..끝까지 읽어주셔서 감사합니다.

     

    JSP 템플릿 GUIDE

    반응형

    댓글

Designed by Tistory.