v6
★ LAB · BOARD

안전한 게시판

회원과 게시판 — 실습

📍 지금 어디를 만지고 있나요?
브라우저
회원과 게시판
DB

사전 준비

이번 실습의 목표

작성자 ID 와 세션 사용자 ID 를 비교해 본인일 때만 허용.

1
스키마 진화 — created_at 합류
-- MySQL 에서 실행
ALTER TABLE myboard
  ADD COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP;

-- 기존 row 도 자동으로 ALTER 시점의 시각으로 채워짐
SELECT num, title, writer, created_at FROM myboard;

// com/smhrd/domain/Board.java — createdAt 필드 합류
@Data @AllArgsConstructor @NoArgsConstructor
public class Board {
    private int           num;
    private String        title;
    private String        writer;
    private String        content;
    private LocalDateTime createdAt;   // ← 추가
}
CHECKPOINT
  • MySQL SHOW COLUMNS FROM myboard;5번째 컬럼 created_at 보이는가?
  • SELECT created_at FROM myboard; — v5 에서 만든 기존 row 도 시각이 채워져 있는가? (DEFAULT CURRENT_TIMESTAMP 의 위력)
2
Mapper / Service — Update / Delete + isOwner
<!-- BoardMapper.xml — SELECT 절에 created_at 합류, update/delete 추가 -->
<select id="selectOne" parameterType="int"
        resultType="com.smhrd.domain.Board">
  SELECT num, title, writer, content, created_at
  FROM myboard WHERE num = #{num}
</select>

<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>

// BoardService — 본인 확인
public boolean isOwner(int num, String userId) {
    Board b = mapper.selectOne(num);
    return b != null && b.getWriter().equals(userId);
}
public void update(Board b, String userId) {
    Board o = mapper.selectOne(b.getNum());
    if (!o.getWriter().equals(userId))
        throw new SecurityException("작성자가 아닙니다");
    mapper.update(b);
}
public void delete(int num, String userId) {
    Board o = mapper.selectOne(num);
    if (!o.getWriter().equals(userId))
        throw new SecurityException("작성자가 아닙니다");
    mapper.delete(num);
}
CHECKPOINT
  • SELECT 절이 num, title, writer, content, created_at 5 컬럼인가?
  • writer.equals(userId) 비교 — 문자열 비교라 == 가 아닌 .equals() 사용했나?
3
Controller / JSP + 실험
@PostMapping("/board/update")
public String update(Board b, HttpSession session, RedirectAttributes ra) {
    Member u = (Member) session.getAttribute("loginUser");
    if (!service.isOwner(b.getNum(), u.getId())) {
        ra.addFlashAttribute("err", "작성자만 수정 가능합니다");
        return "redirect:/board/view?num=" + b.getNum();
    }
    service.update(b, u.getId());
    return "redirect:/board/view?num=" + b.getNum();
}

@PostMapping("/board/delete")
public String delete(@RequestParam int num, HttpSession session,
                     RedirectAttributes ra) {
    Member u = (Member) session.getAttribute("loginUser");
    if (!service.isOwner(num, u.getId())) {
        ra.addFlashAttribute("err", "작성자만 삭제 가능합니다");
        return "redirect:/board/view?num=" + num;
    }
    service.delete(num, u.getId());
    return "redirect:/board/list";
}

<!-- view.jsp -->
<p>작성자: ${board.writer} | 작성일: ${board.createdAt}</p>
<c:if test="${board.writer == sessionScope.loginUser.id}">
    <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>삭제</button>
    </form>
</c:if>

실험: user1 로 글을 쓰고 → 로그아웃 → user2 로 로그인 → 같은 글에 들어가본다.

CHECKPOINT
  • user2 화면에 수정·삭제 버튼이 안 보이는지 (UI 편의)
  • F12 로 hidden num 값을 변조해도 서버에서 차단되는지 (진짜 보안)
  • 본인 글 화면에는 ${board.createdAt} 시각이 출력되는지

실습 완료 체크리스트

ALTER ADD COLUMN created_at 실행 + 기존 row 채워짐 확인
Board 클래스에 createdAt 필드 합류
isOwner() — writer.equals(userId) 문자열 비교
Service / Controller 이중 체크
JSP c:if 로 버튼 숨김 (UI 편의)