Update / Delete + 본인 확인
created_at 컬럼을 ALTER ADD 로 합류시킨다v5 의 myboard 에는 created_at 이 없어서 「언제 쓴 글인지」 화면에 못 보여줌. 정렬도 num 역순뿐.
운영 중인 테이블에 컬럼을 추가하는 SQL. DEFAULT 절을 쓰면 기존 row 도 채워짐.
ALTER TABLE myboard
ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP;
「기존 row 는 어떻게?」 — MySQL 이 ALTER 시점의 현재 시각을 자동으로 채워 넣습니다. 데이터 유실 없음.
package com.smhrd.domain;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@Data @AllArgsConstructor @NoArgsConstructor
public class Board {
private int num;
private String title;
private String writer;
private String content;
private LocalDateTime createdAt; // ← v6 추가
}
MyBatis 의 mapUnderscoreToCamelCase 설정이 created_at ↔ createdAt 자동 매핑.
<mapper namespace="com.smhrd.mapper.BoardMapper">
<select id="selectList" resultType="com.smhrd.domain.Board">
SELECT num, title, writer, content, created_at
FROM myboard
ORDER BY num DESC
</select>
<select id="selectOne" parameterType="int"
resultType="com.smhrd.domain.Board">
SELECT num, title, writer, content, created_at
FROM myboard
WHERE num = #{num}
</select>
👉 두 단어가 비슷해 보이지만 다른 단계. 인증 통과해도 인가는 별도.
수정·삭제 요청이 들어왔을 때:
writer 를 DB 에서 조회id 와 비교
<update id="update" parameterType="com.smhrd.domain.Board">
UPDATE myboard
SET title = #{title}, content = #{content}
WHERE num = #{num}
</update>
<delete id="delete" parameterType="int">
DELETE FROM myboard WHERE num = #{num}
</delete>
</mapper>
@Service
public class BoardService {
@Autowired
private BoardMapper mapper;
public Board selectOne(int num) {
return mapper.selectOne(num);
}
// 본인 확인 헬퍼 — 화면(JSP) 에서도 사용
public boolean isOwner(int num, String userId) {
Board b = mapper.selectOne(num);
return b != null && b.getWriter().equals(userId);
}
// ... 다음 슬라이드: update / delete 에서도 검증
}
// 본인 확인 후 수정 (서비스 레벨에서도 한 번 더)
public void update(Board b, String userId) {
Board original = mapper.selectOne(b.getNum());
if (!original.getWriter().equals(userId)) {
throw new SecurityException("작성자가 아닙니다");
}
mapper.update(b);
}
public void delete(int num, String userId) {
Board original = mapper.selectOne(num);
if (!original.getWriter().equals(userId)) {
throw new SecurityException("작성자가 아닙니다");
}
mapper.delete(num);
}
Service 에서 한 번 더 체크하는 이유:
학생용 버전은 단순화해서 Controller 에서만 해도 OK. 이중은 권장.
@PostMapping("/board/update")
public String update(Board b,
HttpSession session,
RedirectAttributes ra) {
Member user = (Member) session.getAttribute("loginUser");
if (user == null) {
return "redirect:/login";
}
if (!service.isOwner(b.getNum(), user.getId())) {
ra.addFlashAttribute("err", "작성자만 수정 가능합니다");
return "redirect:/board/view?num=" + b.getNum();
}
service.update(b, user.getId());
return "redirect:/board/view?num=" + b.getNum();
}
@PostMapping("/board/delete")
public String delete(@RequestParam int num,
HttpSession session,
RedirectAttributes ra) {
Member user = (Member) session.getAttribute("loginUser");
if (user == null) {
return "redirect:/login";
}
if (!service.isOwner(num, user.getId())) {
ra.addFlashAttribute("err", "작성자만 삭제 가능합니다");
return "redirect:/board/view?num=" + num;
}
service.delete(num, user.getId());
return "redirect:/board/list";
}
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h1>${board.title}</h1>
<p>작성자: ${board.writer} | 작성일: ${board.createdAt}</p>
<div>${board.content}</div>
<c:if test="${board.writer == sessionScope.loginUser.id}">
<div>
<a href="/board/edit?num=${board.num}">수정</a>
<form action="/board/delete" method="post" style="display:inline;">
<input type="hidden" name="num" value="${board.num}" />
<button onclick="return confirm('정말 삭제?')">삭제</button>
</form>
</div>
</c:if>
<c:if test="${not empty err}">
<p style="color:red;">${err}</p>
</c:if>
JSP 의 c:if 로 버튼을 숨겨도 — 그게 보안이 아닙니다.
UI 는 친절, 서버는 엄격. 둘 다 필요하지만 서버가 진짜 보안.
@PostMapping("/update")
public String update(Board b) {
service.update(b);
return "redirect:/board/view";
}
// 누구나 수정 가능
@PostMapping("/update")
public String update(Board b,
HttpSession session) {
Member u = (Member)
session.getAttribute("loginUser");
if (!service.isOwner(
b.getNum(), u.getId())) {
return "redirect:/board/view";
}
service.update(b, u.getId());
return "redirect:/board/view";
}
| 실험 | 결과 |
|---|---|
| 본인 확인 빼고 다른 사람 글 수정 시도 | v5 처럼 동작 (위험) |
| F12 로 hidden num 값 변조 후 제출 | 서버가 본인 확인 → err 메시지 |
| Postman 으로 직접 POST 보내기 | UI 우회 시도 → 서버에서 차단 |
👉 「UI 만 잘 만들면 보안된다」는 착각을 깨는 실험.
👉 인가 패턴은 게시판을 넘어 모든 보안 결정의 토대.
오늘이 우리 프로젝트의 여덟 번째 마일스톤:
v6 부터 우리 게시판은 실제 운영 가능합니다.
동작하지만 위험. 누구나 누구의 글이든 수정·삭제 가능.
본인 글만 수정·삭제. UI 와 서버 이중 방어. 인증과 인가의 차이를 코드로 안다.
ALTER TABLE myboard ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMPwriter.equals(userId) 비교가 본인 확인의 핵심다음: v7 페이징 게시판 (보강) → Part 6 REST API.