기능구현

  • 회원가입(주소API연동, 이메일 인증, ajax를 사용하여 아이디 중복검사)
  • 관리자 페이지(인터셉터 적용, 상품관리, 회원관리)
  • 업로드(이미지 등록, 수정, 삭제)
  • 검색
  • 페이징
  • 구매(장바구니, 포인트사용)
  • 댓글(등록,수정,삭제)
  • 중간에 막혔거나 에러 해결 못하겠으면 ybnr92@gmail.com 으로 문의


목표

  • 제어계층(AuthorController.java)
    • 뷰(View)로부터의 요청 처리하는 url 맵핑 메서드 작성
  • 프레젠테이션 계층(authorEnroll.jsp)
    • 작가 등록에 사용될 데이터를 입력 및 전송할 수 있도록 작성


순서

  • 서비스 단계 테스트
  • url 매핑 메서드 추가(AdminController.java)
  • View작성(authorEnroll.jsp)
  • 버튼 작동 스크립트 추가(authorEnroll.jsp)
  • 작가 등록 성공 경고창(authorManage.jsp)
  • 테스트


AuthorService.java 테스트

  • 지난 포스팅에서 AuthorMapper.java에 구현된 메서드에 대해서는 테스트를 진행하였고 AuthorService.java의 메서드에 대해서는 작성은 하였지만 테스트를 하지 않았다. 그에 대한 테스트도 진행하려 한다.
  • 하나하나 모두 테스트 하는것이 많이 번거롭지만 오류가 발생했을 때 오히려 시간을 줄여주는 큰 역할을 하기 때문에 되도록이면 기능을 구현했을 때 바로바로 테스트를 해주는 것이 좋다. 그리고 항상 테스트 코드를 작성하는 습관을 들여야 한다.
  • src/test/java경로에 com.store.service패키지를 생성한 뒤 AuthorServiceTests.java 클래스를 생성한다.


package com.store.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.store.model.AuthorVO;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file/src/main/webapp/WEB-INF/spring/root-context.xml")
public class AuthorServiceTests {
	
	/* AuthorService 의존성 주입 */
	@Autowired
	private AuthorService service;
	
	@Test
	public void authorEnrollTest() throws Exception {
		
		AuthorVO author = new AuthorVO();
		
		author.setNationId("01");
		author.setAuthorName("테스트");
		author.setAuthorIntro("테스트 소개");
		
		service.authorEnroll(author);
	}
}


  • 위 처럼 그대로 따라하면 에러가 날 것이다. 아래 처럼 바꿔주자.


package com.store.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.store.model.AuthorVO;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml") // 수정
public class AuthorServiceTests {
	
	/* AuthorService 의존성 주입 */
	@Autowired
	private AuthorService service;
	
	@Test
	public void authorEnrollTest() throws Exception {
		
		AuthorVO author = new AuthorVO();
		
		author.setNationId("01");
		author.setAuthorName("테스트");
		author.setAuthorIntro("테스트 소개");
		
		service.authorEnroll(author);
	}
}



url 매핑 메서드 추가(AdminController.java)

  • 먼저 AuthorService.java 인터페이스를 의존성 주입해주는 코드를 추가한다.


@Controller
@RequestMapping("/admin")
public class AdminController {
	
	private static final Logger logger = LoggerFactory.getLogger(AdminController.class);
	
	@Autowired
	private AuthorService authorService;


  • url이 authorEnroll.do인 POST방식의 url매핑 메서드를 추가한다.
  • 파라미터로는 BoardVO타입 변수 RedirectAttributes타입 변수를 추가하였다.


  • BoardVO객체는 뷰가 전송하는 작가 관련 데이터를 받기 위해서이다.
  • RedirectAttributes 객체는 해당 메서드가 종료된 뒤 리다이렉트 방식으로 다른 페이지로 전송할 때 성공 메시지를 전송하기 위해서 추가했다.(리다이렉트 방식으로 이동할 때 데이터 전송을 위해 사용되는 Model 객체라고 생각하면 됨)


package com.store.controller;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.store.model.AuthorVO;
import com.store.service.AuthorService;

@Controller
@RequestMapping("/admin")
public class AdminController {
	
	private static final Logger logger = LoggerFactory.getLogger(AdminController.class);
	
	@Autowired
	private AuthorService authorService;
	
	/* 관리자 메인 페이지 이동 */
	@RequestMapping(value = "main", method = RequestMethod.GET)
	public void adminMainGET() throws Exception {
		
		logger.info("관리자 페이지 이동");
	}
	
	
	/* 상품 관리 페이지 접속 */
	@RequestMapping(value = "goodsManage", method = RequestMethod.GET)
	public void goodsManageGET() throws Exception {
		logger.info(">>>>>>>>>> 상품 관리 페이지 접속");
	}
	
	/* 상품 등록 페이지 접속 */
	@RequestMapping(value = "goodsEnroll", method = RequestMethod.GET)
	public void goodsEnrollGET() throws Exception {
		logger.info(">>>>>>>>>> 상품 등록 페이지 접속");
	}
	
	/* 작가 관리 페이지 접속 */
	@RequestMapping(value = "authorManage", method = RequestMethod.GET)
	public void anthorManageGET() throws Exception {
		logger.info(">>>>>>>>>> 작가 관리 페이지 접속");
	}
	
	/* 작가 등록 페이지 접속 */
	@RequestMapping(value = "authorEnroll", method = RequestMethod.GET)
	public void authorEnrollGET() throws Exception {
		logger.info(">>>>>>>>>> 작가 등록 페이지 접속");
	}
	
	/* 작가등록 */
	@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
	public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
		
	}
	
}


  • 추가한 메서드(작가등록)구현부에 먼저 해당 메서드에 들어온 기록과 뷰로부터 전달받은 데이터를 확인하기 위한 로그 코드를 추가하였다.


	/* 작가등록 */
	@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
	public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
		
		logger.info("authorEnroll : " + author);
	}


  • 작가 등록 쿼리를 수행하는 mapper메서드를 호출하는 AuthorService.javaauthorEnroll메서드를 호출한다. 뷰로부터 전달받은 등록할 작가 정보가 담긴 BoardVO변수를 매개변수로 작성한다.


	/* 작가등록 */
	@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
	public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
		
		logger.info("authorEnroll : " + author);
		
		authorService.authorEnroll(author); // 작가 등록 쿼리 수행
	}


  • 리다이렉트 방식으로 작가 목록 페이지로 이동하도록 리턴값을 추가한다.


	/* 작가등록 */
	@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
	public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
		
		logger.info("authorEnroll : " + author);
		
		authorService.authorEnroll(author); // 작가 등록 쿼리 수행
		
		return "redirect:/admin/authorManage";
	}


  • 작가등록이 성공적으로 완료되었음을 알리는 데이터를 전송해주는 코드를 추가한다.
  • 경고창 메시지에 등록된 작가이름을 표시하기 위해 동록이 완료된 작가이름 데이터를 전송하였다. 뷰로 전송된 데이터가 일회성으로 사용되도록 addFlashAttribute메서드를 사용하였다.
  • 작가 등록 성공 후 이동할 페이지가 작가관리 페이지인데 해당 페이지가 로드될 때마다 서버로부터 전송받은 성공여부데이터를 존재 여부를 체크하여 존재할 시 작가 등록에 성공하였다는 경고창을 뜨게 할 것이다. 그런데 만약 서버로부터 전송된 데이터가 계속 남아있다면 해당 경고창은 계속 나타날 것이다. 이러한 일을 방지하고자 데이터가 일회성으로 사용될 수 있도록 addFlashAttribute메서드를 사용하였다.


	/* 작가등록 */
	@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
	public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
		
		logger.info("authorEnroll : " + author);
		
		authorService.authorEnroll(author); // 작가 등록 쿼리 수행
		
		rttr.addFlashAttribute("enroll_result", author.getAuthorName()); // 등록 성공 메시지(작가이름)
		
		return "redirect:/admin/authorManage";
	}


View 작성(/admin/authorEnroll.jsp)

  • class 속성 값 admin_content_subject<div>태그 바로 아래에 작가 정보를 작성하고 전송할 수 있는 버튼이 있는 태그 코드들을 추가하였다.


<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="../resources/css/admin/authorEnroll.css">
 
<script
  src="https://code.jquery.com/jquery-3.4.1.js"
  integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
  crossorigin="anonymous"></script>
</head>
</head>
<body>
 
    <div class="wrapper">
        <div class="wrap">
            <!-- gnv_area -->    
            <div class="top_gnb_area">
                <ul class="list">    
                    <li><a href="/main">메인 페이지</a></li>
                    <li><a href="/member/logout.do">로그아웃</a></li>
                    <li>고객센터</li>            
                </ul>
            </div>
            <!-- top_subject_area -->
            <div class="admin_top_wrap">
                <span>관리자 페이지</span>
                
            </div>
            <!-- contents-area -->
            <div class="admin_wrap">
                <!-- 네비영역 -->
                <div class="admin_navi_wrap">
                  <ul>
                      <li >
                          <a class="admin_list_01" href="/admin/goodsEnroll">상품 등록</a>
                      </li>
                      <li>
                          <a class="admin_list_02" href="/admin/goodsManage">상품 관리</a>
                      </li>
                      <lI>
                          <a class="admin_list_03" href="/admin/authorEnroll">작가 등록</a>                            
                      </lI>
                      <lI>
                          <a class="admin_list_04" href="/admin/authorManage">작가 관리</a>                            
                      </lI>
                      <lI>
                          <a class="admin_list_05">회원 관리</a>                            
                      </lI>                                                                                             
                  </ul>
                </div>
                <div class="admin_content_wrap">
                    <div class="admin_content_subject"><span>작가 등록</span></div>
                    <!-- 작가 정보 작성 및 전송 코드 추가 -->
                    <div class="admin_content_main">
                    	<form action="/admin/authorEnroll.do" method="post" id="enrollForm">
                    		<div class="form_section">
                    			<div class="form_section_title">
                    				<label>작가 이름</label>
                    			</div>
                    			<div class="form_section_content">
                    				<input name="authorName">
                    			</div>
                    		</div>
                    		<div class="form_section">
                    			<div class="form_section_title">
                    				<label>소속 국가</label>
                    			</div>
                    			<div class="form_section_content">
                    				<select name="nationId">
                    					<option value="none" selected>=== 선택 ===</option>
                    					<option value="01">국내</option>
                    					<option value="02">국외</option>
                    				</select>
                    			</div>
                    		</div>
                    		<div class="form_section">
                    			<div class="form_section_title">
                    				<label>작가소개</label>
                    			</div>
                    			<div class="form_section_content">
                    				<input name="authorIntro" type="text">
                    			</div>
                    		</div>
                   		</form>
                   			<div class="btn_section">
                   				<button id="cancelBtn" class="btn">취 소</button>
	                    		<button id="enrollBtn" class="btn enroll_btn">등 록</button>
	                    	</div> 
                    </div>
                    <!-- 작가 정보 작성 및 전송 코드 추가 -->
                </div>
                <div class="clearfix"></div>
            </div>
        
        <!-- Footer 영역 -->
        <div class="footer_nav">
            <div class="footer_nav_container">
                <ul>
                    <li>회사소개</li>
                    <span class="line">|</span>
                    <li>이용약관</li>
                    <span class="line">|</span>
                    <li>고객센터</li>
                    <span class="line">|</span>
                    <li>광고문의</li>
                    <span class="line">|</span>
                    <li>채용정보</li>
                    <span class="line">|</span>
                </ul>
            </div>
        </div> <!-- class="footer_nav" -->
        
        <div class="footer">
            <div class="footer_container">
                
                <div class="footer_left">
                    <img src="../resources/img/Logo.png">
                </div>
                <div class="footer_right">
                    (주) 이름없는 회사    대표이사 : OOO
                    <br>
                    사업자등록번호 : ooo-oo-ooooo
                    <br>
                    대표전화 : oooo-oooo(발신자 부담전화)
                    <br>
                    <br>
                    COPYRIGHT(C) <strong>yoonbitnara.github.io</strong>    ALL RIGHTS RESERVED.
                </div>
                <div class="clearfix"></div>
            </div>
        </div> <!-- class="footer" -->        
        
    </div>    <!-- class="wrap" -->
</div>    <!-- class="wrapper" -->
 
</body>
</html>



조심해야 할 태그

  • form 태그
    • action 속성 값에 작가등록 기능을 수행하는 url을 작성하였고 해당 url 매핑 메서드가 POST방식이기 때문에 method 속성 값을 POST로 작성하였다.
<form action="/admin/authorEnroll.do" method="post" id="enrollForm">
 
</form>


  • 작가 등록 기능을 수행하는 url 매핑 메서드가 작가 등록에 사용할 데이터를 AuthorVO객체를 파라미터로 전달 받기 때문에 AuthorVO클래스에 정의한 변수 이름과 authorEnroll.jsp에 정보가 입력될 <input>, <select>태그의 name 속성 값이 일치하도록 작성해야 한다. 일치하지 않으면 데이터는 서버로 전송되지 않는다.
<input name="authorName">
    <select name="nationId">
    <input name="authorIntro" type="text">


  • 소속 국가 정보를 입력할 때 <input>을 사용하지 않고 <select>를 사용하였다. 국내, 국외 두 정보중 한 가지를 선택하도록 강제하기 위해 <select>를 사용하였다.
<select name="nationId">
	<option value="none" selected>=== 선택 ===</option>
	<option value="01">국내</option>
	<option value="02">국외</option>
</select>


버튼 작동 스크립트 추가(authorEnroll.jsp)

  • 취소 버튼과 작가등록 버튼 두개가 있다. 두 개의 버튼이 작동되도록 스크립트 코드를 작성하였다.
<script>
/* 등록버튼 */
$("#enrollBtn").click(function(){
	$("#enrollForm").submit();
});

/* 취소버튼 */
$("#cancelBtn").click(function() {
	location.href="/admin/authorManage";
});
</script>
  • 취소 버튼을 누르면 작가 관리 페이지(authorManage.jsp)로 이동하도록 하였고 등록 버튼을 누르면 작가 등록 기능이 실행되도록 작성하였다.


작가 등록 성공 경고창(authorManage.jsp)

  • 작가 등록 수행 후 작가 관리 페이지에 이동함과 동시에 등록이 성공하였음을 알리는 데이터(enroll_result)를 전송하였다. 이 데이터를 활용하여 성공을 알리는 경고창을 띄우는 코드를 작성한다.
  • authorManage.jsp하단에 <script>태그를 추가 후 페이지가 로드될 때 반드시 실행이 되는 익명 함수를 추가한다.
<script>
$(document).ready(function() {
	
});
</script>


  • 함수 구현부에 서버에서 전송되는 데이터를 체크 후 존재 할 시 작가 등록 성공 메시지를 알리는 경고창을 띄운다.
$(document).ready(function() {
	
	let result = "${enroll_result}";
	
	checkResult(result);
	
	function checkResult(result) {
		
		if (result == "") {
			
			return;
		}
	alert("작가'${enroll_result}' 님을 등록하였습니다.");
	}
	
});


  • 작성한 코드 그대로 사용하더라도 정상적으로 작동을 하지만 ${enroll_result}는 사용자가 작성한 값이 그대로 전송되기 때문에 XSS 공격과 같이 스크립트 코드를 주입시키는 웹사이트 공격에 취약할 수 있다.
  • 예를 들면 ${enroll_result}는 작가 등록 때 작성한 작가 이름인데 사용자가 <script>alert('공격');</script>을 작가 이름에 작성을 한다면 작가 관리 페이지에 alert문이 실행될 수 있다.
  • 이를 방지하기 위해 작가이름 작성을 할 때 유효성 검사를 통해 스크립트 코드를 작성하지 못하도록 할 수도 있다.
  • 스크립트 코드가 입력되더라도 출력되는 값(${})에도 스크립트 코드가 실행이 되지 않도록 할 수 있는 방법이 있는데 JSTL의 <c:out>을 사용하는 방법이다.
  • <c:out>는 변수의 내용을 출력할 때 사용되는 태그인데 해당 태그에 HTML문자를 escape하는 기능이 있기 때문이다.
  • 표현언어(EL)인 ${enroll_result}<c:out>을 적용시키고자 한다. 먼저 JSTL 라이브러리 코드를 추가한다.


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>


  • 기존 ${enroll_result}<c:out> 태그를 적용 한다.
	// 변경 전
    let result = "${enroll_result}";
 
    // 변경 후
    let result = '<c:out value="${enroll_result}"/>';


테스트

  • 취소 테스트
    • 취소 버튼을 눌러 정상적으로 작가 관리 페이지로 이동하는지 확인



  • 등록 테스트
    • 작가 정보 데이터베이스 등록
    • 작가 관리 페이지 이동
    • 등록 성공 경고창 확인




Leave a comment