Chaeyeon0 / GreenDay_Study

여은개의 공부 일지
0 stars 0 forks source link

[20240518] 403 에러 해결, 게시판 Controller/Service 패키지 작성 #8

Open janghw0126 opened 1 month ago

janghw0126 commented 1 month ago

1. 403 에러 해결

image

테스트 해보는 도중에 자꾸 403 에러가 떠서 왜 그런지 한번 알아보니 기본적으로 스프링에서는 따로 예외 처리를 하지 않으면 예외 발생 시 500에러가 발생한다. 그런데 스프링 시큐리티를 적용하면 메소드에서 예외가 발생했을 때 403에러가 발생한다. 심지어 존재하지 않는 url로 접속하여 404 not found가 발생해야 하는 상황에서도 403 forbidden이 발생한다고 한다.

image

쉽게 말하자면, 클라이언트가 요청한 리소스에 대한 접근권한이 없음을 나타내며, 이는 서버 측에서 엑세스 권한이 거부되었음을 의미한다.

💣 CSRF 필터

요 녀석이 위의 문제를 해결해줄껀데, 이 자식은 뭐냐

Spring Security는 POST, PUT 등의 업데이트 요청에 대해 요청 헤더의 일부로 CSRF 토큰을 제공하도록 하는 CRSF 필터를 기본적으로 사용한다.

여기서 403 에러의 원인을 파악할 수 있는데, 게시판 저장은 POST 요청이므로, 기본적으로 적용된 CSRF 필터에서 CSRF 토큰을 요구한다. 하지만 CSRF 토큰을 제공하지 않았기 때문에, '권한이 없다'는 403 에러가 발생한 것이다.

그래서 문제를 해결하기 위해 CSRF 필터를 해제하기로 결정하였다.

시큐리티 설정에서 모든 경로에 대해 접근을 허용하거나 특정 경로에 대해서만 접근을 허용하도록 설정할 수 있어서 설정을 수정하여 모든 경로에 대해 접근을 허용하도록 설정하였다.

package com.example.greenday_board.config;

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@AllArgsConstructor
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/**").permitAll() // 모든 경로에 대한 접근 허용
                )
                .csrf().disable(); // 필요 시 CSRF 보호 비활성화

        return http.build();
    }
}

→ 이렇게 하면 모든 경로에 대해 접근을 허용하고 CSRF 보호를 비활성화해서 403 에러 오류 방지가 가능하다.

[!NOTE] 이때, 로그인 페이지가 필요 없는 경우 보안 설정을 간소화할 수 있다. 만약, 게시판 어플리케이션이고 별도의 인증이나 권한 관리가 필요없다면, 모든 경로에 대해 접근을 허용하도록 설정할 수 있다. 하지만 우리는 게시판 수정이나 삭제의 경우에 로그인이 필요하기 때문에 이는 나중에 기능을 더 개발하고 구체화해가면서 권한을 추가할 예정이다 !

2. Controller/ Service 패키지 작성

⬇️ Controller package

package com.example.greenday_board.controller;

import com.example.greenday_board.dto.BoardDto;
import com.example.greenday_board.service.BoardService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@AllArgsConstructor
public class BoardController {
    private BoardService boardService;

    /* 게시글 목록 */
    @GetMapping("/")
    public String list(Model model) {
        List<BoardDto> boardList = boardService.getBoardlist();

        model.addAttribute("boardList", boardList);
        return "board/list.html";
    }

    @GetMapping("/post")
    public String write() {
        return "board/write.html";
    }

    @GetMapping("/post/{no}")
    public String detail(@PathVariable("no") Long no, Model model) {
        BoardDto boardDTO = boardService.getPost(no);

        model.addAttribute("boardDto", boardDTO);
        return "board/detail.html";
    }

    @GetMapping("/post/edit/{no}")
    public String edit(@PathVariable("no") Long no, Model model) {
        BoardDto boardDTO = boardService.getPost(no);

        model.addAttribute("boardDto", boardDTO);
        return "board/update.html";
    }

    @PutMapping("/post/edit/{no}")
    public String update(BoardDto boardDTO) {
        boardService.savePost(boardDTO);

        return "redirect:/";
    }

    @DeleteMapping("/post/{no}")
    public String delete(@PathVariable("no") Long no) {
        boardService.deletePost(no);

        return "redirect:/";
    }

    @PostMapping("/post")
    public String write(BoardDto boardDto) {
        boardService.savePost(boardDto);

        return "redirect:/";
    }
}

⬇️ Service package

package com.example.greenday_board.service;

import com.example.greenday_board.domain.entity.BoardEntity;
import com.example.greenday_board.domain.repository.BoardRepository;
import com.example.greenday_board.dto.BoardDto;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@AllArgsConstructor
@Service
public class BoardService {
    private BoardRepository boardRepository;

    @Transactional
    public List<BoardDto> getBoardlist() {
        List<BoardEntity> boardEntities = boardRepository.findAll();
        List<BoardDto> boardDtoList = new ArrayList<>();

        for ( BoardEntity boardEntity : boardEntities) {
            BoardDto boardDTO = BoardDto.builder()
                    .id(boardEntity.getBoard_id())
                    .title(boardEntity.getTitle())
                    .content(boardEntity.getBoard_content())
                    .writer(boardEntity.getUser_email())
                    .createdDate(boardEntity.getCreatedDate())
                    .build();

            boardDtoList.add(boardDTO);
        }

        return boardDtoList;
    }
    @Transactional
    public BoardDto getPost(Long id) {
        Optional<BoardEntity> boardEntityWrapper = boardRepository.findById(id);
        BoardEntity boardEntity = boardEntityWrapper.get();

        BoardDto boardDTO = BoardDto.builder()
                .id(boardEntity.getBoard_id())
                .title(boardEntity.getTitle())
                .content(boardEntity.getBoard_content())
                .writer(boardEntity.getUser_email())
                .createdDate(boardEntity.getCreatedDate())
                .build();

        return boardDTO;
    }

    @Transactional
    public Long savePost(BoardDto boardDto) {
        return boardRepository.save(boardDto.toEntity()).getBoard_id();
    }

    @Transactional
    public void deletePost(Long id) {
        boardRepository.deleteById(id);
    }
}

🗯️ 실행 결과

간단한 프론트를 추가해서 실행해보았을 때

image

화면이 잘 떴다는 것을 확인할 수 있었고, 글쓰기 버튼을 누르면

image

이 페이지가 뜨는데 여기서 글을 쓰고 등록까지 누르면,

image

일케 잘 저장된 것을 확인할 수 있다. 글고 글 제목 누르면

image

일케 내가 썼던 글을 확인할 수 있는데 일단 글 제목은 우리 그린데이 게시판에는 필요없으니까 글 제목 대신에 내용이 바로 게시판에 구현될 수 있도록 코드를 계속 수정해나가야 될 것 같다.

image

DB에도 데이터가 잘 담겼다는 것을 확인할 수 있었다!

이제 수정 기능이랑 삭제 기능만 구현하면 어느 정도 게시판은 마무리 될 것 같은데 이 두 녀석이 저엉말 너무너무 쉽지 않다,, 아마 이 두 부분을 구현하는 데 시간이 좀 많이 걸릴 것 같댜,,,💧

끄읏🫠

Chaeyeon0 commented 1 month ago

게시판이 잘 만들어지고 있는 모습이 보이니 너무 다행이네요 ㅎㅎ 저도 얼른 분발 해보겠습니다 !