Board 리팩토링 : 서비스 레이어에서 권한 검증을 위한 DTO 활용
1. BoardRequest 생성

toEntity(User sessionUser)
메서드를 사용하여 DTO를 엔티티로 변환합니다.
주의할 점은 sessionUser
가 null 인 경우 작동하지 않기 때문에 로그인 환경에서만 작동합니다.public class BoardRequest {
@Data
public static class SaveDTO {
private String title;
private String content;
public Board toEntity(User sessionUser) {
return Board.builder()
.title(title)
.content(content)
.user(sessionUser)
.build();
}
}
}

user 나 session 에서 오류 발생하면 엔티티내부의 빌더에 User 가 추가되어 있지 않기 때문입니다.
따라서 해당 부분을 추가해 주어야합니다.
@PostMapping("/board/save")
public String save(BoardRequest.SaveDTO saveDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
boardRepository.save(saveDTO.toEntity(sessionUser));
return "redirect:/board";
}
@Transactional
public void save(Board board) {
Query query = em.createNativeQuery("insert into board_tb (title, content, created_at) values (?, ?,now())");
query.setParameter(1, title); // position : ? 의 순서
query.setParameter(2, content); // 이 쿼리를
query.executeUpdate();
}
// 변경 후
@Transactional
public void save(Board board) {
em.persist(board);
}
다소 복잡했던 코드가 단순해 졌습니다.
2. 컨트롤러에서 세션 체크
앞서 이야기 한 것 처럼 sessionUser 를 매개변수로 전달하기 때문에 이게
null
값을 가지면 에러가 발생합니다. 따라서 session 에 유저 정보가 있는지 (로그인 정보) 확인을 해야합니다. @PostMapping("/board/save")
public String save(BoardRequest.SaveDTO saveDTO) { //스프링 기본 전략 = x-www-form-urlencoded 작성 /보드 레포지토리 객체는 ioc 에 있음 따라서 autowired 함
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
throw new RuntimeException("로그인이 필요헙니다.");
}
boardRepository.save(saveDTO.toEntity(sessionUser));
return "redirect:/board";
}
if문 추가를 통해 세션 정보가 없을 시 예외를 던지도록 추가했습니다.
@Test
public void save_test() {
// 1. given (매개변수를 강제로 만들어주는것)
String title = "제목";
String content = "내용 1";
// 2. when
boardRepository.save(Board.builder().title(title).content(content).build());
// 3. eye(눈으로 확인)
}
테스트 코드 수정을 하고 실행해 보면 무사히 완료 됩니다 ^^*
3. 인가설정


로그인을 하지 않은 상태이거나 다른 작성자의 글에도 수정 삭제 버튼이 활성화 됩니다.
이때 필요한게 인가설정인데
세션에 저장된 정보와 게시글 작성자 정보를 비교하여 처리하겠습니다.
@GetMapping("/board/{id}")
public String detail(@PathVariable("id") Integer id, HttpServletRequest request) {
Board board = boardRepository.findById(id);
request.setAttribute("model", board);
request.setAttribute("isOwner", false);
System.out.println(board.toString());
return "board/detail";
}


일단 일괄적으로
isOwner
을 확인하여 false 일 때 안보이도록 가려놓았습니다.하드코딩 해놓았기 때문에 모두 안보이겠죠
일단 다음으로 넘어갑니다.
4. serviec 추가 계속되는 리팩토링.
비즈니스 로직은 보통 serviec 레이어에서 해결하게 됩니다.
해결 못한 인가 설정을 위해 로직을 한 번 작성해 봅니다.

@RequiredArgsConstructor
@Service
public class BoardService {
private final BoardRepository boardRepository;
public Board 상세보기(int id, User sessionUser) {
Board board = boardRepository.findById(id); // 조인 (Board - User)
boolean isOwner = false;
// (findById 에서 처리해서 얘는 절대 null 이 올 수 없음)
if (board.getUser().getId() == sessionUser.getId()) {
isOwner = true;
}
return board;
}
}
id 를 통해 게시물을 찾고 idOwner 을 적절하게 처리하는 과정이 추가되었습니다.
이 코드의 문제점!
isOwner
을 리턴하지 못합니다 혹은 board를. (리턴값은 항상 하나이기 때문에)그렇다고 권한 체크하는 부분을
Controller
에 넘길 수 없습니다. 왜냐하면 이러한 권한 체크는 컨트롤러의 권한이 아니기 때문입니다.이럴때 데이터 가공을 위해 필요한
DTO
를 생성합니다.5. BoardDTO 추가 (BoardResponse)

public class BoardResponse {
@Data
public static class DetailDTO {
// 화면에서 안보여도 pk 는 꼭 넣어줘야함!!!
private Integer boardId;
private String title;
private String content;
private Integer userId;
private String username;
private Boolean isOwner;
public DetailDTO(Board board, User sessionUser) {
this.boardId = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.userId = board.getUser().getId();
this.username = board.getUser().getUsername();
this.isOwner = false;
if (board.getUser().getId() == sessionUser.getId()) {
isOwner = true;
}
}
}
}
DTO 단에서 데이터 가공을 해줍니다.
생성자를 통해
DetailDTO
의 필드를 초기화 합니다.
생성자 내부if (board.getUser().getId() == sessionUser.getId()) {
isOwner = true;
}
라인이
isOwner
필드의 값을 결정하게 됩니다. (문제가 해결되었습니다!)리턴 타입이 DetailDTO 가 되며 컬럼명이 변경되었습니다.
Board의 id → boardId
User 의 id → userId (템플릿 코드 바꿔주어야 합니다.)
5. service 로직 재수정
필요한 데이터를
DetailDTO
에 넣었기 때문에 리턴은 BoardResponse.DetailDTO
가 됩니다. public BoardResponse.DetailDTO 상세보기(int id, User sessionUser) {
Board board = boardRepository.findById(id); // 조인 (Board - User)
return new BoardResponse.DetailDTO(board, sessionUser);
}
서비스에서 repository 에게 요청을 보내도록 수정했기 때문에 Controller 도 수정을 합니다.
6. Controller 수정
@GetMapping("/board/{id}")
public String detail(@PathVariable("id") Integer id, HttpServletRequest request) {
// Board board = boardRepository.findById(id);
// request.setAttribute("model", board);
// request.setAttribute("isOwner", false);
User sessionUser = (User) session.getAttribute("sessionUser");
BoardResponse.DetailDTO detailDTO = boardService.상세보기(id, sessionUser);
request.setAttribute("model", detailDTO);
return "board/detail";
}
주석된 부분이 기존의 코드입니다.
isOwner
을 처리 못하는 코드입니다.Service 에서 호출한 DTO 를 통해 지금 로그인한 유저와 게시글을 작성한 사람의 정보 비교가 완료되었기 때문에
detailDTO
가 모든 정보를 갖고 있게 됩니다.
7. 템플릿 수정 및 인가

변경 후 실행을 합니다.


이렇게 인가 설정이 완료되었습니다.
스프링부트 게시판 시리즈 v2 -1. https://inblog.ai/hj/27190 (User 테이블 생성 및 쿼리 수정) -2. https://inblog.ai/hj/27193 (User, Board 테이블 조인 과 JPQL) -3. https://inblog.ai/hj/27224 (회원 가입) -4. https://inblog.ai/hj/27225 DTO 를 통한 리팩토링 -5. https://inblog.ai/hj/27310 로그인, 로그아웃 -6. https://inblog.ai/hj/27316 서비스 레이어 추가 및 DTO 활용 -7. https://inblog.ai/hj/27430 예외처리 핸들러 설정과 User 서비스 리팩토링 -8. https://inblog.ai/hj/27431 Board 기능 리팩토링 -9. https://inblog.ai/hj/27560 게시글 수정, 더티체킹(flush) -10. https://inblog.ai/hj/27561 인터셉터, AOP 사용 / 마무리
Share article