💡 개념 간단 정리
BCrypt
Bcrypt는 해시 알고리즘을 이용해 패스워드를 저장한다. 랜덤한 솔트를 생성하기 때문에 같은 문자열에 대해 다른 인코드 결과를 반환한다. 매번 길이가 60인 String을 만든다.
우선 수정 및 추가하게 될 파일을 먼저 살펴 보자. (🤗 표시 참고)
1. pom.xml에 BCrypt 의존성 추가하기
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
2. src/main/java - com.example.demo.service - BoardService
클래스 생성
package com.example.demo.service;
import java.util.List;
import java.util.stream.Collectors;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.dao.BoardDao;
import com.example.demo.entity.Board;
@Service
public class BoardService {
@Autowired
private BoardDao dao;
public Boolean write(Board board) {
String encodedPassword = BCrypt.hashpw(board.getPassword(), BCrypt.gensalt(10));
board.setPassword(encodedPassword);
return dao.save(board)!=0;
}
public List<Board> list() {
List<Board> list = dao.findAll();
for(Board board:list)
board.setPassword(null);
return list;
}
public Board read(Integer bno) {
dao.increaseReadCnt(bno);
return dao.findById(bno).setPassword(null);
}
public Boolean update(String title, String content, String password, Integer bno) {
Board board = dao.findById(bno);
if(board==null)
return false;
if(BCrypt.checkpw(password, board.getPassword())==false)
return false;
return dao.update(title, content, bno)!=0;
}
public Boolean delete(String password, Integer bno) {
Board board = dao.findById(bno);
if(board==null)
return false;
if(BCrypt.checkpw(password, board.getPassword())==false)
return false;
return dao.deleteById(bno)!=0;
}
}
3. src/test/java - com.example.demo - BoardServiceTest
생성 후 테스트
package com.example.demo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.entity.Board;
import com.example.demo.service.BoardService;
@SpringBootTest
public class BoardServiceTest {
@Autowired
private BoardService service;
@Test
public void writeTest() {
Board board = new Board("글 제목","글 내용","spring","1234");
assertEquals(true, service.write(board));
}
@Test
public void readTest() {
Board board = service.read(1 /* 글 번호 */);
assertEquals(1, board.getReadCnt());
System.out.println(board);
}
@Transactional
@Test
public void updateTest() {
assertEquals(true, service.update("변경 제목", "변경 내용", "1234", 1 /* 글 번호 */));
assertEquals(false, service.update("변경 제목", "변경 내용", "1234", 2));
}
@Transactional
@Test
public void deleteTest() {
assertEquals(true, service.delete("1234", 1));
assertEquals(false, service.delete("1234", 2));
}
}
4. src/main/java - com.example.demo.controller - BoardController
생성
package com.example.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import com.example.demo.entity.Board;
import com.example.demo.service.BoardService;
@Controller
public class BoardController {
@Autowired
private BoardService service;
// 글 목록 get
@GetMapping({"/", "/board/list"})
public ModelAndView list() {
return new ModelAndView("board/list").addObject("list", service.list());
}
// 글 읽기 get
@GetMapping("/board/read")
public ModelAndView read(Integer bno) {
return new ModelAndView("board/read").addObject("board", service.read(bno));
}
// 글 작성 get, post
@GetMapping("/board/write")
public void write() {
}
@PostMapping("/board/write")
public String write(Board board) {
service.write(board);
return "redirect:/board/read?bno=" + board.getBno();
}
// 글 변경 post
@PostMapping("/board/update")
public String update(String title, String content, String password, Integer bno) {
Boolean result = service.update(title, content, password, bno);
if(result==false)
return "redirect:/board/list?error";
return "redirect:/board/read?bno=" + bno;
}
// 글 삭제 post
@PostMapping("/board/delete")
public String delete(String password, Integer bno) {
Boolean result = service.delete(password, bno);
if(result==false)
return "redirect:/board/list?error";
return "redirect:/board/list";
}
}
5. src/main/java - com.example.demo.entity - Board
수정
package com.example.demo.entity;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain=true)
public class Board {
private Integer bno;
private String title;
private String content;
private String nickname;
private String password;
private LocalDateTime writeTime = LocalDateTime.now();
private Integer readCnt = 0;
// 테스트 정상 동작을 위해 4변수 생성자 추가
public Board(String title, String content, String nickname, String password) {
this.title = title;
this.content = content;
this.nickname = nickname;
this.password = password;
this.writeTime = LocalDateTime.now();
this.readCnt = 0;
}
}
6. src/main/resources - templates - board - list.html
, read.html
, write.html
생성
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script>
$(document).ready(function() {
const message = location.search.substr(1);
if(message!="")
alert("작업이 실패했습니다.");
})
</script>
</head>
<body>
<div id="page">
<table class="table table-hover">
<thead>
<tr>
<th>번호</th><th>제목</th><th>글쓴이</th><th>시간</th><th>읽기</th>
</tr>
</thead>
<tbody>
<tr th:each="board: ${list}">
<td th:text="${board.bno}"></td>
<td><a th:href="@{/board/read(bno = ${board.bno})}" th:text="${board.title}"></a></td>
<td th:text="${board.nickname}"></td>
<td th:text="${board.writeTime}"></td>
<td th:text="${board.readCnt}"></td>
</tr>
</tbody>
</table>
<div>
<a class="btn btn-info" href="/board/write">글쓰기로</a>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script>
$(document).ready(function() {
$("#delete").click(function() {
const $form = $("<form>").attr("action","/board/delete").attr("method","post").appendTo($("body"));
$("<input>").attr("type","hidden").attr("name","password").val($("#password").val()).appendTo($form);
$("<input>").attr("type","hidden").attr("name","bno").val($("#bno").val()).appendTo($form);
$form.submit();
})
});
</script>
</head>
<body>
<h1>글 읽기</h1>
<form id="read_form" action="/board/update" method="post">
<div class="form-group">
<label for="nickname">닉네임 </label>
<input type="text" class="form-control" id="nickname" th:value="${board.nickname}" name="nickname">
</div>
<div class="form-group">
<label for="password">비밀번호 </label>
<input type="text" class="form-control" id="password" name="password">
</div>
<div class="form-group">
<label for="title">제목 </label>
<input type="text" class="form-control" id="title" th:value="${board.title}" name="title">
</div>
<div class="form-group">
<textarea class="form-control" rows="5" id="content" th:text="${board.content}" name="content"></textarea>
</div>
<input type="hidden" class="form-control" id="bno" th:value="${board.bno}" name="bno">
<div>
<button class="btn btn-default" id="update">변경</button>
<button type="button" class="btn btn-alert" id="delete">삭제</button>
</div>
</form>
<a type="button" class="btn btn-info" href="/board/list">목록으로</a>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<h1>글 쓰기</h1>
<form id="read_form" action="/board/write" method="post">
<div class="form-group">
<label for="nickname">닉네임 </label>
<span id="nickname_msg"></span>
<input type="text" class="form-control" id="nickname" name="nickname">
</div>
<div class="form-group">
<label for="password">비밀번호 </label>
<input type="text" class="form-control" id="password" name="password">
</div>
<div class="form-group">
<label for="title">제목 </label>
<input type="text" class="form-control" id="title" name="title">
</div>
<div class="form-group">
<textarea class="form-control" rows="5" id="content" name="content"></textarea>
</div>
<div>
<button class="btn btn-default" id="write">작성</button>
</div>
</form>
</body>
</html>