Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- this예약어
- OPP개념
- 메서드
- break문
- 자바 멀티스레딩
- Thread
- JAVA기초
- 시스템 환경 변수 편집
- IntelliJ IDEA
- 접근제어지시자
- 포함관계
- Java데이터 타입
- 반복문
- 연관관계
- 생성자
- for문
- 컴파일
- Java
- While
- 메서드 오버로딩
- continue문
- multi-threading
- JAVA객체지향
- 집합관계
- 상수
- 형 변환
- function
- 인텔리제이 한글 깨짐 해결법
- java변수
- 인텔리제이 기초 설정
Archives
- Today
- Total
최원종의 개발 블로그
V3-9 게시글 수정(Dirty Checking과 권한 관리) 본문

board/update-form.mustache파일
{{> layout/header}}
<div class="container p-5 flex-grow-1">
<div class="card">
<div class="card-header"><b>글수정 화면입니다</b></div>
<div class="card-body">
<!-- 익명 게시글 작성 -->
<form action="/board/{{id}}/update" method="post">
<div class="mb-3">
<input type="text" name="username" class="form-control"
placeholder="enter username" value="{{board.user.username}}" readonly>
</div>
<div class="mb-3">
<input type="text" name="title" class="form-control"
placeholder="enter title" value="{{board.title}}">
</div>
<div class="mb-3">
<textarea type="text" name="content" rows="5" class="form-control" placeholder="enter title">
{{board.content}}
</textarea>
</div>
<button class="btn btn-primary">글수정 완료</button>
</form>
</div>
</div>
</div>
{{> layout/footer}}
BoardRequest 핵심 메서드 분석
1. UpdateDTO.validate() (수정 로직의 첫 관문)
목적: 사용자가 수정한 데이터가 올바른 형식인지 검사함.
로직: 제목(title): 공백을 제외하고 반드시 존재해야 함.
내용(content): 최소 3글자 이상이라는 비즈니스 규칙을 적용.
효과: 서버 리소스를 낭비하기 전, 잘못된 수정 요청을 입구에서 차단함.
2. SaveDTO.toEntity(User user) (객체 생성의 일관성)
목적: 전송 받은 데이터와 세션의 사용자 정보를 조합하여 새로운 게시글 엔티티를 생성함.
기술: 빌더 패턴(@Builder)을 사용하여 필드 주입 순서에 상관없이 명확하게 객체를 생성하며,
연관 관계인 User 객체를 매개변수로 받아 데이터 무결성을 지킴.
BoardRequest 코드
package com.tenco.blog.board;
import com.tenco.blog.user.User;
import lombok.Builder;
import lombok.Data;
// 요청 데이터를 담는 DTO 클래스
// 컨트롤러.. 비즈니스 .. 데이터 계층 사이에서 데이터 전송 역할 객체
public class BoardRequest {
@Data
@Builder
public static class SaveDTO {
private String title;
private String content;
// 편의 기능 설계 가능
// DTO 에서 Entity로 변환해주는 편의 메서드
public Board toEntity(User user) {
return Board.builder()
.title(title)
.user(user)
.content(content)
.build();
}
public void validate() {
if(title == null || title.trim().isEmpty()) {
throw new IllegalArgumentException("제목은 필수입니다");
}
if(content == null || content.length() < 3) {
throw new IllegalArgumentException("내용은 3글자 이상 작성해야 합니다.");
}
}
}
// 내부 정적 클래스 게시글 수정 DTO 설계
@Data
public static class UpdateDTO {
private String title;
private String content;
// 게시글 수정시 유효성 검사 편의 메서드
public void validate() {
if(title == null || title.trim().isEmpty()) {
throw new IllegalArgumentException("제목은 필수입니다");
}
if(content== null || content.length() < 3) {
throw new IllegalArgumentException("내용은 3글자 이상 작성해야 합니다.");
}
}
}
}
게시글 수정 화면 요청(BoardController)
코드 수정 및 추가된 부분
2단계 보안 적용: 단순한 로그인 체크(인증)를 넘어, 해당 글의 주인인지 확인하는 인가 처리가 추가됨.
에러 핸들링: 권한이 없을 경우 RuntimeException을 던져 비정상적인 접근을 강력하게 차단함.
데이터 바인딩: 수정할 기존 데이터를 Model에 담아 뷰(update-form)로 전달하여 사용자 편의성을 높임.
핵심 메서드 분석
1. 인증 처리 (Authentication):
세션에서 sessionUser를 꺼내 로그인 상태인지 확인함.
로그인하지 않았다면 로그인 페이지로 보냄.
2. 게시글 조회: URL 경로에 포함된 {id}를 사용해 수정할 게시글을 DB에서 가져옴.
3. 인가 처리 (Authorization): "세션의 사용자 ID"와 "게시글 작성자의 ID"를 대조함.
일치하지 않으면 수정 화면 자체를 보여주지 않고 예외를 발생시킴.
4. 모델 전달: 모든 검증을 통과하면 board 객체를 모델에 담아,
수정 폼에서 기존 제목과 내용을 미리 보여줄 수 있게 함.
BoardController 코드
// http://localhost:8080/board/1/update-form
// 게시글 수정 화면 요청
@GetMapping("/board/{id}/update-form")
public String updateFormPage(@PathVariable(name = "id") Integer id, Model model, HttpSession session) {
// 인증 처리
User sessionUser = (User) session.getAttribute("sessionUser");
if(sessionUser == null) {
return "redirect:/login-form";
}
// 인가 처리
Board board = boardPersistRepository.findById(id);
if(sessionUser.getId() != board.getUser().getId()) {
throw new RuntimeException("수정 권한이 없습니다");
}
model.addAttribute("board", board);
return "board/update-form";
}
게시글 수정 기능 요청
핵심 메서드 분석
updateProc (POST)
인증 검사 (Authentication): 세션에서 사용자를 확인하여 비로그인 상태면 로그인 페이지로 보냄.
데이터 검증 (validate): updateDTO.validate()를 호출하여 빈 값이나
짧은 글 등 부적절한 데이터 전송을 막음.
인가 처리 (Authorization): DB에서 해당 글을 조회한 후,
sessionUser.getId() != board.getUser().getId() 조건을 통해
본인의 글이 아니면 수정 권한 없음 예외를 던짐.
수정 실행 (updateById): 모든 검증을 통과하면 실제 영속성 계층에 수정을 요청함.
결과 응답: 성공 시 해당 게시글의 상세 페이지(/board/{id})로 리다이렉트하여
수정된 내용을 바로 확인할 수 있게 함.
게시글 수정 기능 요청(BoardController) 코드
// /board/{id}/update
@PostMapping("/board/{id}/update")
// 메세지 컨버터란 객체가 동작해서 자동으로 객체를 생성하고 값을 매핑해 준다.
public String updateProc(@PathVariable(name = "id") Integer id,
BoardRequest.UpdateDTO updateDTO, HttpSession session) {
// 인증 검사
User sessionUser = (User) session.getAttribute("sessionUser");
if(sessionUser == null) {
return "redirect:/login-form";
}
try {
// 유효성 검사
updateDTO.validate();
// 인가 검사
Board board = boardPersistRepository.findById(id);
if (sessionUser.getId() != board.getUser().getId()) {
throw new RuntimeException("수정할 권한이 없습니다");
}
boardPersistRepository.updateById(id, updateDTO);
} catch (Exception e) {
// /board/{id}/update-form
return "redirect:/board/" + id + "/update-form";
}
return "redirect:/board/" + id;
}
주요 기술 포인트 요약
메시지 컨버터: 폼 데이터를 자바 객체(UpdateDTO)로 자동 변환 및 매핑하는 기술
Fail-Fast 전략: validate()를 가장 먼저 수행하여 잘못된 요청을 빠르게 거절함
이중 보안 체크: GET(화면 요청)과 POST(실제 수정) 양쪽에서 인가 처리를 수행함
Redirect 전략: 성공/실패 시 적절한 URL로 이동시켜 중복 전송 및 UX 단절을 방지
전체 코드 - BoardController
더보기
package com.tenco.blog.board;
import com.tenco.blog.user.User;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Slf4j
@Controller // IoC
@RequiredArgsConstructor // DI
public class BoardController {
// DI
private final BoardPersistRepository boardPersistRepository;
/**
* 게시글 작성 화면 요청
* @return 페이지 반환
* 주소설계 : http://localhost:8080/board/save-form
*/
@GetMapping("/board/save-form")
public String saveForm(HttpSession httpSession) {
// 로그인 여부 체크 - 즉 로그인 한 사용자만 이 페이지 안에 들어 올 수 있음.
// 1. 인증 검사
User sessionUser = (User)httpSession.getAttribute("sessionUser");
if(sessionUser == null) {
return "redirect:/login-form";
}
return "board/save-form";
}
/**
* 게시글 작성 기능 요청
* @return 페이지 반환
* 주소설계 : http://localhost:8080/board/save-form
*/
@PostMapping("/board/save")
// 사용자 요청 -> HTTP 요청 메시지(Post)
public String saveProc(BoardRequest.SaveDTO saveDTO, HttpSession session) {
log.info("=== 게시글 저장 요청 ===");
// 이 요청 시 사용자가 로그인을 했다면 로그인 정보를 세션 메모리에서 가져오면 된다.
// 1. 세션에서 로그인한 사용자 정보 가져오기
User sessionUser = (User) session.getAttribute("sessionUser");
// 2. 로그인 여부 확인
if(sessionUser == null) {
return "redirect:/login-form";
}
try {
// 3. 로그인 된 사용자
// 3.1 유효성 검사
saveDTO.validate();
Board board = saveDTO.toEntity(sessionUser);
boardPersistRepository.save(board);
return "redirect:/";
} catch (Exception e) {
System.out.println("에러 발생 : " + e.getMessage());
return "board/save-form";
}
}
/**
* 게시글 목록 화면 요청
* 주소설계 : http://localhost:8080/
*/
@GetMapping({"/", "index"})
public String list(Model model) {
List<Board> boardList = boardPersistRepository.findAll();
model.addAttribute("boardList", boardList);
return "board/list";
}
// 게시글 상세보기 화면 요청
// http://localhost:8080/board/1
@GetMapping("/board/{id}")
public String detailPage(@PathVariable(name = "id") Integer id, Model model) {
Board board = boardPersistRepository.findById(id);
// board는 연관관계가 User 엔티티와 ManyToOne 관계 설정이 되어 있다.
// 직접 쿼리구문을 작성하지 않을 때 즉, 엔티티 매니저의 메서드로 객체를 조회시
// 자동으로 JOIN 구문을 호출해 준다.
// 단 Fatch 전략에 따라 EAGER, LAZY 전략에 따라 한번에 다 조인해서 가져오거나
// 필요할 때 한번 더 요청하는것이 LAYZY 전략이다.
// 코드상에서 User 에 정보를 요구 (현재 LAYZY 전략)
// System.out.println(board.getUser().getUsername());
model.addAttribute("board", board);
return "board/detail";
}
// 삭제 기능 요청
// 1. 로그인 여부 확인
// 2. 삭제할 게시글이 본인이 작성한 게시글인지 확인 (권한 확인, 인가 처리)
// 3. 인가 처리 후 삭제 진행
@PostMapping("/board/{id}/delete")
public String deleteProc(@PathVariable(name = "id") Integer id, HttpSession session) {
log.info("=== 게시글 삭제 요청 ===");
// 인증 검사
User sessionUser = (User) session.getAttribute("sessionUser");
if(sessionUser == null) {
return "redirect:/login-form";
}
try {
// 삭제할 게시글 조회 (권한 체크, 인가 처리)
Board board = boardPersistRepository.findById(id);
if(board.getUser().getId() == sessionUser.getId() ) {
boardPersistRepository.deleteById(id);
}
} catch (Exception e) {
return "redirect:/";
}
return "redirect:/";
}
// http://localhost:8080/board/1/update-form
// 게시글 수정 화면 요청
@GetMapping("/board/{id}/update-form")
public String updateFormPage(@PathVariable(name = "id") Integer id, Model model, HttpSession session) {
// 인증 처리
User sessionUser = (User) session.getAttribute("sessionUser");
if(sessionUser == null) {
return "redirect:/login-form";
}
// 인가 처리
Board board = boardPersistRepository.findById(id);
if(sessionUser.getId() != board.getUser().getId()) {
throw new RuntimeException("수정 권한이 없습니다");
}
model.addAttribute("board", board);
return "board/update-form";
}
// /board/{id}/update
@PostMapping("/board/{id}/update")
// 메세지 컨버터란 객체가 동작해서 자동으로 객체를 생성하고 값을 매핑해 준다.
public String updateProc(@PathVariable(name = "id") Integer id,
BoardRequest.UpdateDTO updateDTO, HttpSession session) {
// 인증 검사
User sessionUser = (User) session.getAttribute("sessionUser");
if(sessionUser == null) {
return "redirect:/login-form";
}
try {
// 유효성 검사
updateDTO.validate();
// 인가 검사
Board board = boardPersistRepository.findById(id);
if (sessionUser.getId() != board.getUser().getId()) {
throw new RuntimeException("수정할 권한이 없습니다");
}
boardPersistRepository.updateById(id, updateDTO);
} catch (Exception e) {
// /board/{id}/update-form
return "redirect:/board/" + id + "/update-form";
}
return "redirect:/board/" + id;
}
}
'Spring boot 입문' 카테고리의 다른 글
| V4 (인증과 권한 처리와 예외 처리)-1 에러 페이지 만들기 (0) | 2026.05.15 |
|---|---|
| V3-10 회원정보보기 및 수정 및세션 동기화 처리 (0) | 2026.05.15 |
| V3-8 게시글 삭제(인증 검사와 인가처리 ) (0) | 2026.05.13 |
| V3-7 게시글 쓰기(로그인한 사용자와 게시글 연결) (0) | 2026.05.13 |
| V3-6 연관 관계 및 N + 1 문제 확인 (0) | 2026.05.13 |
