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
