고래씌
[Spring] 8-1. 일반 게시글 등록(크로스사이트스크립트 공격 방지) 본문
1. 게시글 등록
=> views/board/boardEnrollForm.jsp 파일 생성
▶ boardEnrollForm.jsp
=> 사진도 보내고 해야하기 때문에 multipart 사용
=> 첨부파일은 ORIGIN_NAME과 CHANGE_NAME에 컬럼에 저장할 예정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<jsp:include page="/WEB-INF/views/common/header.jsp" />
<div class="content">
<br><br>
<div class="innerOuter">
<h2>게시글 작성하기</h2>
<br>
<form action="${contextPath }/board/insert/${boardCode }" id="enrollForm"
method="post" enctype="multipart/form-data">
<!-- 사진도 보내고 해야하기 때문에 multipart 사용 -->
<!-- 첨부파일은 ORIGIN_NAME과 CHANGE_NAME에 컬럼에 저장할 예정 -->
<table align="center">
<tr>
<th>제목</th>
<td><input type="text" id="title" class="form-control" name="boardTitle" required></td>
</tr>
<tr>
<th>작성자</th>
<td>${loginUser.userId }</td>
</tr>
<tr>
<th>첨부파일</th>
<td><input type="file" id="upfile" class="form-control" name="upfile"></td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea id="content" style="resize:none;" rows="10" class="form-control"
name="boardContent" required="required"></textarea>
</td>
</tr>
</table>
<div align="center">
<button type="submit" class="btn btn-primary">등록</button>
<button type="reset" class="btn btn-danger">취소</button>
</div>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/common/footer.jsp" />
</body>
</html>
▶ BoardController.java
@GetMapping("/insert/{boardCode}")
public String enrollBoard(@PathVariable("boardCode") String boardCode) {
return "board/boardEnrollForm";
}
▶ boardListView.java
=> 글쓰기 버튼을 클릭하면 다음과 같이 이동되도록 설정
▶ pom.xml
=> 메이븐레파지토리에서 다음과 같이 복사하여 붙여놓기함.
<!-- 파일업로드와 관련된 의존성 -->
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- 입출력 관련 라이브러리 -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
▶ root-context.xml
☞ 인코딩 처리 설정과 용량제한 설정을 해줄 예정
1) 업로드 되는 파일에 대한 설정
- maxUploadSize : 한번에 업로드 되는 파일의 총 용량 설정
- maxInMemorySize : 디스크에 임시파일을 생성하기전에 메모리에 보관시킬 최대 바이트 크기
<!-- 업로드 되는 파일에 대한 설정 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 인코딩 처리 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!--
maxUploadSize : 한번에 업로드 되는 파일의 총 용량 설정
maxInMemorySize : 디스크에 임시파일을 생성하기전에 메모리에 보관시킬 최대 바이트 크기
-->
<!-- 총용량 -->
<property name="maxUploadSize" value="10000000"></property>
<property name="maxInMemorySize" value="10000000"></property>
</bean>
▶ BoardController.java
=> 맨위에 application 추가
☞ url로 전달받은 동적 파라미터 => @PathVariable("boardCode") String boardCode
☞ @ModelAttribute("loginUser") Member loginUser => loginUser와 일치하는 것을 가져올 수 있음
☞ @ModelAttribute("loginUser") Member loginUser => sessionAttributes에 의해 session으로 이관된 데이터를 매개변수에서 얻어올 때 사용하는 방법
☞ 이미지 파일을 저장할 경로는 다음과 같다.
String webPath = "/resources/images/board/" + boardCode + "/"; |
☞ Utils는 아래에서 생성하므로 Utils 생성후, 추가함
if(!upfile.getOriginalFilename().equals("")) { // 원본파일이름이 비어있지않다면 String changeName = Utils.saveFile(upfile, serverFolderPath); // 업로드 파일, 저장되는 경로 b.setOrginName(upfile.getOriginalFilename()); b.setChangeName(changeName); } |
// url로 전달받은 동적 파라미터 => @PathVariable("boardCode") String boardCode
// @ModelAttribute("loginUser") Member loginUser => loginUser와 일치하는 것을 가져올 수 있음
// @ModelAttribute("loginUser") Member loginUser => sessionAttributes에 의해 session으로 이관된 데이터를 매개변수에서 얻어올 때 사용하는 방법
@PostMapping("/insert/{boardCode}")
public String insertBoard(
Board b,
@PathVariable("boardCode") String boardCode,
@ModelAttribute("loginUser") Member loginUser,
Model model,
HttpSession session,
@RequestParam(value="upfile", required=false) MultipartFile upfile // 첨부파일
) {
// 이미지, 파일을 저장할 저장경로 얻어오기
String webPath = "/resources/images/board/" + boardCode + "/"; // boardCode가 T라면 사진게시판, C라면 일반게시판..
String serverFolderPath = application.getRealPath(webPath);
// resources 폴더안에는 아무것도 없기 때문에 디렉토리가 존재하지 않는다면 생성해주는 코드 추가
File dir = new File(serverFolderPath);
if(!dir.exists()) { // 존재하지 않는다면 디렉토리 생성
dir.mkdirs(); // 디렉토리를 여러개 추가해야하니까 mkdirs로 추가
}
// 사용자가 첨부파일을 등록한 경우
// upfile은 첨부파일이 있든, 없든 무조건 객체는 생성됨.
// 단, 첨부파일 등록을 하지 않은경우 내부에 데이터가 비어있다. ("")
// 사용자가 전달한 파일이 있는지 없는지는 filename이 존재하는지로 확인하면 된다.
if(upfile != null && !upfile.getOriginalFilename().equals("")) { // 원본파일이름이 비어있지않다면
String changeName = Utils.saveFile(upfile, serverFolderPath); // 업로드 파일, 저장되는 경로
b.setOriginName(upfile.getOriginalFilename());
b.setChangeName(changeName);
}
// Board 객체에 데이터 추가
b.setBoardWriter(loginUser.getUserNo()+"");
b.setBoardCd(boardCode);
log.info("board {}", b);
int result = boardService.insertBoard(b);
String url = "";
if(result > 0) {
session.setAttribute("alertMsg", "글작성 성공함");
url = "redirect:/board/list/" + boardCode;
}else {
model.addAttribute("errorMsg", "게시글 작성 실패함");
url = "common/errorPage";
}
return url;
}
▶ Utils.java
=> 파일을 저장할 함수
=> 파일을 저장시키면서, 파일명을 수정한 후 수정된 파일명을 반환
=> common 폴더아래에 Utils.java 파일 생성
package com.kh.spring.common;
import java.io.File;
import java.io.IOException;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Utils {
// 파일 저장 함수
// 파일을 저장시키면서, 파일명을 수정한 후 수정된 파일명을 반환
public static String saveFile(MultipartFile upfile, String savePath) {
// 랜덤파일명 생성하기
String originName = upfile.getOriginalFilename();
String currentTime = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date());
int random = (int)(Math.random() * 90000 + 10000); // 10000 ~ 99999 5자리 랜덤값
String ext = originName.substring(originName.indexOf(".")); // 이것을 조합해서 변경된 파일이름을 반환할 예정
String changeName = currentTime + random + ext; // 3가지 문자열을 합쳐서 랜덤한 확장자를 만들어줄 것임
try {
upfile.transferTo(new File(savePath + changeName));
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
return changeName;
}
}
▶ BoardService.java
int insertBoard(Board b);
▶ BoardServiceImpl.java
@Override
public int insertBoard(Board b) {
/*
* 게시글 삽입
*
* 게시글 삽입시 게시글의 제목, 내용에 들어가는 문자열에 크로스사이트스크립트 공격을 방지하기 위한 메소드 추가
* textarea, 제목에 들어가는 엔터 및 스페이스바를 개행문자로 변환처리
*/
b.setBoardContent(Utils.XSSHandling(b.getBoardContent()));
b.setBoardContent(Utils.newLineHandling(b.getBoardContent()));
b.setBoardTitle(Utils.XSSHandling(b.getBoardTitle()));
return boardDao.insertBoard(b);
}
☞ 게시글 삽입시 게시글의 제목, 내용에 들어가는 문자열에 크로스사이트스크립트 공격을 방지하기 위한 메소드 추가
☞ Utils.java에 추가
1) 크로스사이트스크립트 공격 방지
▶ Utils.java
public static String XSSHandling(String content) {
if(content != null) {
content = content.replaceAll("&", "&");
content = content.replaceAll("<", "<");
content = content.replaceAll(">", ">");
content = content.replaceAll("\"", """); // 쌍따옴표 막으려면 "뒤에 \이 와야 쌍따옴표로 인식
}
return content;
}
2) 가독성을 위해 추가
▶ Utils.java
- 개행문자 처리
- textarea → \n, p → <br>
public static String newLineHandling(String content) {
return content.replaceAll("(\r\n|\r|\n|\n\r)", "<br>");
}
- 수정할때는 개행처리 된 것을 다시 해제해주어야함.
- 개행해제 처리
public static String newLineClear(String content) {
return content.replaceAll("<br>", "\n");
}
▶ BoardDao.java
int insertBoard(Board b);
▶ BoardDaoImpl.java
@Override
public int insertBoard(Board b) {
log.info("등록 이전 b {}" , b);
int result = sqlSession.insert("boardMapper.insertBoard", b);
log.info("등록 이후 b {}", b);
return result;
}
▶ board-mapper.xml
<!-- 게시글 등록 -->
<insert id="insertBoard" parameterType="board" useGeneratedKeys="true">
<!-- useGeneratedKeys => insert 되고 나서 setter함수를 호출해서 board안에 넣어줌. selectKey와 무조건 함께 사용 -->
<selectKey keyProperty="boardNo" resultType="int" order="BEFORE">
SELECT SEQ_BNO.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO BOARD
(
BOARD_NO,
BOARD_TITLE,
BOARD_CONTENT,
ORIGIN_NAME,
CHANGE_NAME,
BOARD_CD,
BOARD_WRITER
)VALUES (
SEQ_BNO.CURRVAL, <!-- #{boardNo} 써도됨 -->
#{boardTitle},
#{boardContent},
#{originName},
#{changeName},
#{boardCd},
#{boardWriter}
)
</insert>
▶ 결과
=> 게시글 등록을 하면 이와같이 목록에 추가된 것을 확인
'Server > Spring' 카테고리의 다른 글
[Spring] 8-3. 일반 게시판 상세보기, 조회수 증가 (0) | 2024.01.25 |
---|---|
[Spring] 8-2. 사진게시판(사진 여러개일 때(다중 insert문)) (0) | 2024.01.24 |
[Spring] 7. Application 전역에 있는 데이터를 저장(Intercepter) (0) | 2024.01.24 |
[Spring] 6-2. 게시판 검색기능 (0) | 2024.01.24 |
[Spring] 6-1. 게시판 목록, 페이징 (0) | 2024.01.23 |