이 예제에는 스프링 시큐리티를 사용하였습니다. 시큐리티와 관련된 내용은 스프링 시큐리티 관련 글을 참고하세요.
개요
Spring Boot의 기본 기능(Thymeleaf, 시큐리티 포함)을 이용해서 전통적 형태의 웹 게시판을 만들었습니다. 게시판은 웹 프로그래밍에 있어서 필수라고 볼 수 있는 CRUD(Create, Read, Update, Delete)를 연습하기에 적당한 예제입니다.
이 게시판은 로그인 환경을 가정하여 만들었습니다. 로그인 관련 부분은 시큐리티에서 다룹니다. 이로 인한 게시판의 추가 요구사항은 다음과 같습니다.
- 로그인이 되어있지 않다면 게시판의 글 목록만 보여주고, 내용은 볼 수 없게 한다.
- 게시글의 수정, 삭제 기능은 해당 글을 작성한 사용자만 접근할 수 있도록 한다.
이 예제에서는 스프링 부트의 내용을 연습하기 위해 자바스크립트는 따로 사용하지 않았습니다. 그리고 목적상 고급 기능(페이징 기능 등)은 아직 구현되지 않았습니다.
프로젝트의 구조 (일부)
- SimpleBoardController: 게시판 관련 컨트롤러
- SimpleBoardDAO: 게시판 테이블 DAO, 작업 편의상 서비스, DTO는 따로 작성하지 않았습니다. DTO 대신
Map
을 사용합니다.
데이터베이스 구조
데이터베이스 설정하는 방법은 Spring Boot: mariadb 연결하기 (JDBC-Maven 기준)를 참고하세요.
코드 (일부)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@charset "UTF-8"; | |
.text-red { | |
color: red; | |
} | |
.text-blue { | |
color: blue; | |
} | |
.text-grey { | |
color: grey; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml" | |
xmlns:th="http://www.thymeleaf.org" | |
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>게시글 보기</title> | |
<link rel="stylesheet" type="text/css" href="/board.css"> | |
</head> | |
<body> | |
<hr> | |
<div sec:authorize="isAuthenticated()"> | |
<p>current username: <span class="text-red" th:text="${#authentication.getPrincipal().getUsername()}">anon</span></p> | |
<a href="/logout">Logout</a> | |
</div> | |
<div sec:authorize="!isAuthenticated()"> | |
<a href="/login">Login</a> | |
</div> | |
<hr> | |
<h3 th:text="${article.title}">title</h3> | |
<p>by <span th:text="${article.username}">anon</span> <span class="text-grey" th:text="${'[' + article.writeDate + ']'}">date</span></p> | |
<h5>content </h5> | |
<pre th:text="${article.content}">….</pre> | |
<p th:if="${#authentication.getPrincipal().getUsername() eq article.username}"> | |
<a th:href="${'/board/update/' + article.seq}">[modify]</a> | |
<a th:href="${'/board/delete/' + article.seq}">[delete]</a> | |
</p> | |
<p><a href="/board">[Back to list]</a></p> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml" | |
xmlns:th="http://www.thymeleaf.org" | |
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>delete</title> | |
<link rel="stylesheet" type="text/css" href="/board.css"> | |
</head> | |
<body> | |
<hr> | |
<div sec:authorize="isAuthenticated()"> | |
<p>current username: <span class="text-red" th:text="${#authentication.getPrincipal().getUsername()}">anon</span></p> | |
<p>articleId: <span th:text="${articleId}"></span></p> | |
<hr> | |
<h4>이 글을 정말 삭제하시겠습니까?</h4> | |
<form action="/board/proc/delete" method="POST"> | |
<input type="hidden" name="articleId" th:value="${articleId}"> | |
<p><a th:href="${'/board/read/' + articleId}">NO</a> <button>[YES]</button></p> | |
</form> | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml" | |
xmlns:th="http://www.thymeleaf.org" | |
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>modify</title> | |
<link rel="stylesheet" type="text/css" href="/board.css"> | |
</head> | |
<body> | |
<hr> | |
<div sec:authorize="isAuthenticated()"> | |
<p>current username: <span class="text-red" th:text="${#authentication.getPrincipal().getUsername()}">anon</span></p> | |
<p>articleId: <span th:text="${article.seq}"></span></p> | |
<hr> | |
<div> | |
<form method="post" action="/board/proc/update"> | |
<input type="hidden" th:value="${article.seq}" name="articleId"> | |
<input type="text" placeholder="title…" name="title" th:value="${article.title}"> | |
<textarea placeholder="content…" name="content" th:text="${article.content}"></textarea> | |
<button>submit</button> | |
</form> | |
</div> | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml" | |
xmlns:th="http://www.thymeleaf.org" | |
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>초간단 게시판</title> | |
<link rel="stylesheet" type="text/css" href="/board.css"> | |
</head> | |
<body> | |
<hr> | |
<div sec:authorize="isAuthenticated()"> | |
<p>current username: <span class="text-red" th:text="${#authentication.getPrincipal().getUsername()}">anon</span></p> | |
<a href="logout">Logout</a> | |
<hr> | |
<div> | |
<form method="post" action="/board/proc/write"> | |
<input type="text" placeholder="title…" name="title"> | |
<textarea placeholder="content…" name="content"></textarea> | |
<button>submit</button> | |
</form> | |
</div> | |
</div> | |
<div sec:authorize="!isAuthenticated()"> | |
<a href="/login">Login</a> | |
</div> | |
<hr> | |
<table> | |
<thead> | |
<tr> | |
<th>No.</th> | |
<th>username</th> | |
<th>title</th> | |
<th>date</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr th:each="article, i: ${list}"> | |
<td th:text="${article.seq}"></td> | |
<td th:text="${article.username}"></td> | |
<td sec:authorize="isAuthenticated()"> | |
<a th:href="${'/board/read/' + article.seq}" th:text="${article.title}"></a> | |
<span class="text-blue" th:if="${i.count eq i.size}">[1등]</span> | |
</td> | |
<td sec:authorize="!isAuthenticated()" th:text="${article.title}"></td> | |
<td th:text="${article.writeDate}"></td> | |
</tr> | |
</tbody> | |
</table> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.springboot.security.controller; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.ui.Model; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RequestMethod; | |
import com.springboot.security.dao.SimpleBoardDAO; | |
@Controller | |
public class SimpleBoardController { | |
@Autowired SimpleBoardDAO sbd; | |
@RequestMapping("/board") | |
public String board(Model model) { | |
List<Map<String, Object>> list = sbd.getBoardList(); | |
// System.out.println(list); | |
model.addAttribute("list", list); | |
return "simple-board"; | |
} | |
@RequestMapping(value="/board/proc/write", method=RequestMethod.POST) | |
public String writeAnArticle(Authentication auth, String title, String content) { | |
int result = 0; | |
if(title != null && !title.equals("") && content != null && !content.equals("")) { | |
Map<String, Object> article = new HashMap<>(); | |
article.put("username", auth.getName()); | |
article.put("title", title); | |
article.put("content", content); | |
result = sbd.insertAnArticle(article); | |
} else { | |
System.err.println("Do not write blank article!"); | |
} | |
return "redirect:/board?writeResult=" + result; | |
} | |
@RequestMapping("/board/read/{articleId}") | |
public String readAnArticle(Model model, @PathVariable("articleId") int articleId) { | |
Map<String, Object> article = sbd.getAnArticle(articleId); | |
model.addAttribute("article", article); | |
return "simple-board-view-content"; | |
} | |
@RequestMapping("/board/delete/{articleId}") | |
public String deleteAnArticleView(Authentication auth, Model model, @PathVariable("articleId") int articleId) { | |
model.addAttribute("articleId", articleId); | |
return "simple-board-view-delete"; | |
} | |
@RequestMapping(value="/board/proc/delete", method=RequestMethod.POST) | |
public String deleteAnArticleProc(Authentication auth, Model model, int articleId) { | |
int result = sbd.deleteAnArticle(articleId, auth.getName()); | |
return "redirect:/board?deleteResult=" + result; | |
} | |
@RequestMapping("/board/update/{articleId}") | |
public String updateAnArticleView(Authentication auth, Model model, @PathVariable("articleId") int articleId) { | |
Map<String, Object> article = sbd.getAnArticle(articleId); | |
model.addAttribute("article", article); | |
return "simple-board-view-modify"; | |
} | |
@RequestMapping(value="/board/proc/update", method=RequestMethod.POST) | |
public String updateAnArticleProc(Authentication auth, Model model, int articleId, String title, String content) { | |
int result = 0; | |
if(title != null && !title.equals("") && content != null && !content.equals("")) { | |
Map<String, Object> article = new HashMap<>(); | |
article.put("articleId", articleId); | |
article.put("username", auth.getName()); | |
article.put("title", title); | |
article.put("content", content); | |
result = sbd.updateAnArticle(article); | |
} else { | |
System.err.println("Do not write blank article!"); | |
} | |
return "redirect:/board?writeResult=" + result; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.springboot.security.dao; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.jdbc.core.JdbcTemplate; | |
import org.springframework.stereotype.Repository; | |
@Repository | |
public class SimpleBoardDAO { | |
@Autowired JdbcTemplate jt; | |
public int insertAnArticle(Map<String, Object> article) { | |
String sql = "insert into simple_board values(0, ?, sysdate(), ?, ?)"; | |
return jt.update(sql, | |
article.get("username"), | |
article.get("title"), | |
article.get("content")); | |
} | |
public List<Map<String, Object>> getBoardList(){ | |
String sql = "select * from simple_board order by write_date desc"; | |
return jt.query(sql, (rs, rowNum) -> { | |
Map<String, Object> anArticle = new HashMap<>(); | |
anArticle.put("seq", rs.getString(1)); | |
anArticle.put("username", rs.getString(2)); | |
anArticle.put("writeDate", rs.getString(3)); | |
anArticle.put("title", rs.getString(4)); | |
anArticle.put("content", rs.getString(5)); | |
return anArticle; | |
}); | |
} | |
public Map<String, Object> getAnArticle(int articleId){ | |
String sql = "select * from simple_board where seq=?"; | |
return jt.queryForObject(sql, new Object[] {articleId}, (rs, rowNum) -> { | |
Map<String, Object> anArticle = new HashMap<>(); | |
anArticle.put("seq", rs.getString(1)); | |
anArticle.put("username", rs.getString(2)); | |
anArticle.put("writeDate", rs.getString(3)); | |
anArticle.put("title", rs.getString(4)); | |
anArticle.put("content", rs.getString(5)); | |
return anArticle; | |
}); | |
} | |
public int deleteAnArticle(int articleId, String username) { | |
String sql = "delete from simple_board where seq = ? and username = ?"; | |
return jt.update(sql, articleId, username); | |
} | |
public int updateAnArticle(Map<String, Object> article) { | |
String sql = "update simple_board set title = ?, content = ? where seq = ? and username = ?"; | |
return jt.update(sql, | |
article.get("title"), | |
article.get("content"), | |
article.get("articleId"), | |
article.get("username")); | |
} | |
} |
동작 내용
게시글 작성
게시글 수정
게시글 삭제
3개의 댓글
최남규 · 2020년 6월 18일 1:36 오전
좋은글 감사합니다 혹시 작성하신 소스 받을수 있을까요?
제공 가능하시면 부탁드리겠습니다.
cnam9@naver.com
감사합니다.
yoonbumtae (BGSMM) · 2020년 6월 19일 8:34 오후
소스 주소는 https://github.com/ayaysir/spring-boot-security-example-1 입니다.
Spring Boot 예제: 회원가입 폼 만들기 - BGSMM · 2019년 12월 10일 2:17 오전
[…] 후 게시판(Spring Boot 예제: 초간단 게시판)에 글 […]