송민준의 개발노트

썸머노트(summernote) 사용하기(스프링, 404에러 해결), XSS관련 본문

웹/Spring Framework

썸머노트(summernote) 사용하기(스프링, 404에러 해결), XSS관련

송민준 2020. 1. 14. 14:48

https://summernote.org/getting-started/#run-summernote

 

Summernote - Super Simple WYSIWYG editor

Super Simple WYSIWYG Editor on Bootstrap Summernote is a JavaScript library that helps you create WYSIWYG editors online.

summernote.org

 

1. 위 썸머노트 페이지로 이동 해서 부트스트랩 4 버전으로 cdn을 받는다.(부 4 기준)

  기본적으로 썸머노트는 부트스트랩과 제이쿼리 기반으로 만들어진거기 때문에(아닌 것도 있음) 밑에 코드 위에 불러오는게 있어야 한다. 밑에 KR은 한글 문제를 해결해줄것이다.

<!-- include summernote css/js -->
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.15/dist/summernote.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.15/dist/summernote.min.js"></script>
<script src="https://github.com/summernote/summernote/tree/master/lang/summernote-ko-KR.js"></script>

 

2. 썸머노트는 div와 form에 적용이 가능한데 form에 쓰는 것을 권장하며 post 방식을 쓰는 것을 권장한다.

<form method="post">
  <textarea id="summernote" name="editordata"></textarea>
</form>
<div id="summernote">Hello Summernote</div>

 

3. javascript에 추가

$(document).ready(function() {
  $('#summernote').summernote();
});

4. 다음과 같이 속성 도 변경 가능하다.

<script>
      $('#summernote').summernote({
        placeholder: 'Hello Bootstrap 4',
        tabsize: 2,
        height: 100,
        toolbar: [
          ['style', ['style']],
          ['font', ['bold', 'underline', 'clear']],
          ['color', ['color']],
          ['para', ['ul', 'ol', 'paragraph']],
          ['table', ['table']],
          ['insert', ['link', 'picture', 'video']],
          ['view', ['fullscreen', 'codeview', 'help']]
        ]
      });
    </script>

5. 이대로 막 적으면 submit을 해도 잘 되긴 한다. 다만 image업로드를 하면 에러가 발생한다. 이에 썸머노트에서 제공하는 callback 함수를 이용하여 ajax로 서버에 이미지를 전송하도록 하겠다.

 1) form 안에 textarea 선언

	<div class="form-group">
			<label for="board_content">내용</label>
			<textarea name="BOARD_CONTENT" id="board_content"></textarea>
	</div>

 

 2) script 추가

function sendFile(file, el) {
		var form_data = new FormData();
		form_data.append('file', file);
		$.ajax({
			data: form_data,
			type : "post",
			url: 'summer_image',
			cache :false,
			contentType : false,
			enctype : 'multipart/form-data',
			processData : false,
			success : function(img_name) {
				$(el).summernote('editor.insertImage', img_name);
			}
		});
	}
$(function() {
		$('#board_content').summernote({
			 	placeholder: '최대 500자 작성 가능합니다.',
		        height: 300,
		        lang: 'ko-KR',
		        callbacks: {
		        	onImageUpload: function(files, editor, welEditable) {
		        		for(var i = files.length -1; i>=0; i--) {
		        			sendFile(files[i], this);
		        		}
		        	}
		        }
		 });
});

 

3) controller ( out.pirntln 뒤에 있는 경로는 본인이 저장하는 경로에...)

@ResponseBody
	@PostMapping("/summer_image")
	public void summer_image(MultipartFile file, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		String file_name = file.getOriginalFilename();
		String server_file_name = fileDBName(file_name, save_folder);
		System.out.println("server file : " + server_file_name);
		file.transferTo(new File(save_folder + server_file_name));
		out.println("resources/upload"+server_file_name);
		out.close();
	}
    private String fileDBName(String fileName, String saveFolder) {
		Calendar c = Calendar.getInstance();
		int year = c.get(Calendar.YEAR);
		int month = c.get(Calendar.MONTH);
		int date = c.get(Calendar.DATE);

		String homedir = saveFolder + year + "-" + month + "-" + date;
		System.out.println(homedir);
		File path1 = new File(homedir);
		if (!(path1.exists())) {
			path1.mkdir();
		}
		Random r = new Random();
		int random = r.nextInt(100000000);

		int index = fileName.lastIndexOf(".");

		String fileExtension = fileName.substring(index + 1);
		System.out.println("fileExtension = " + fileExtension);

		String refileName = "bbs" + year + month + date + random + "." + fileExtension;
		System.out.println("refileName = " + refileName);

		String fileDBName = "/" + year + "-" + month + "-" + date + "/" + refileName;
		System.out.println("fileDBName = " + fileDBName);

		return fileDBName;
	}

 

 

이클립스에 환경에서 개발하다보면 저장하는게 실경로와 가상경로가 다르다. 두번을 저장하게 되는데 이때 시간적 딜레이가 생겨서 404에러가 뜬다.(경로의 이미지를 참조를 못함)

 

두번 저장하는게 아닌 참조하는 곳에만 저장하기 위해서

톰캣에 들어가서 server options - Serve modules without publicshing 을 체크해준다.

 

그럼 해결이 되는데

다른 방법으론... 좀 가라이긴 하는데 아래와 같이 setTimeout으로 시간 간격을 준다.

파일 생성이 완료되는데 대략 4초가 걸려서 4초를 주고 사용자에게 업로드중인걸 알리기위해

스피너를 사용했다. (좋은 방법 아님)

		$.ajax({
			data: form_data,
			type : "post",
			url: 'summer_image',
			cache :false,
			contentType : false,
			enctype : 'multipart/form-data',
			processData : false,
			success : function(img_name) {
				$(".spinner-border").css("display","inline-block");
				setTimeout(function() {
					$(".spinner-border").css("display","none");
					$(el).summernote('editor.insertImage', img_name);
				}, 4000);
			}
		});

 

 

* XSS 공격에 대해서 필터링을 하고 싶어서 이것저것 찾아보니 jsoup를 써서 하면 간편하다고 한다.

  다만 현재 버전의 summernote는 XSS 필터링을 제공해주고 있다.

https://summernote.org/deep-dive/#xss-protection-for-codeview

 

Summernote - Super Simple WYSIWYG editor

Super Simple WYSIWYG Editor on Bootstrap Summernote is a JavaScript library that helps you create WYSIWYG editors online.

summernote.org

위로 가보면 default로 whitelist 필터링이 설정되어 있고 태그는 false로 되어 있다.

커스텀도 가능한 것 같으니 다음에 필요하면 해보겠음