국비학원 50일차 : Spring Boot_10

Digeut·2023년 5월 8일
0

국비학원

목록 보기
42/44

Board API

JwtAuthenticationFilter

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter{
    
    private JwtProvider jwtProvider;

    @Autowired //의존성 주입
    public JwtAuthenticationFilter(JwtProvider jwtProvider){
        this.jwtProvider = jwtProvider;
    }

    @Override //빠른 수정 이용해서 작성함
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        try {

            String jwt = parseToken(request); //jwt를 가져옴

            boolean hasJwt = jwt != null;
            if(!hasJwt) {
                filterChain.doFilter(request, response);
                return; //함수 종료시키는것?
            }

            String email = jwtProvider.validate(jwt); //서브젝트의 이메일 가져옴
            AbstractAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(🍉email, null,AuthorityUtils.NO_AUTHORITIES);
            authenticationToken.setDetails(new WebAuthenticationDetailsSource());

🍉부분에서 AbstractAuthenticationToken은 userEmail을 받아와서 사용하는걸 알 수 있다.


            SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
            securityContext.setAuthentication(authenticationToken);
            SecurityContextHolder.setContext(securityContext);
            
        } catch (Exception exception) {
            exception.printStackTrace();
        }

        filterChain.doFilter(request, response);
        
    }

    //토큰 파싱
    private String parseToken(HttpServletRequest request){

        String token = request.getHeader("Authorization");

        boolean hasToken = 
            token != null && 
            !token.equalsIgnoreCase("null");
        if(!hasToken) return null;

        boolean isBearer = token.startsWith("Bearer ");
        if(!isBearer) return null;

        String jwt = token.substring(7);
        return jwt;

    }
}

Board2Controller

@RestController
@RequestMapping("/api/v2/board")
@RequiredArgsConstructor 
//final만 추가해주고 만들어주면 의존성 외부주입 알아서 해준다
public class Board2Controller {
    
    private final BoardService boardService;


    // 1. 게시물 작성 //글쓴이 이메일 받지 않고 인증처리를 통해서 가져오는걸로 하게된다.
    @PostMapping("") //추가적인 path는 없음
    public ResponseEntity<ResponseDto> postBoard(
        🧃@AuthenticationPrincipal String userEmail, 
        //인증을 거친 email을 받아오는것
        //JwtAuthenticationFilter의 AbstractAuthenticationToken에서 
        //email을 받아오므로 그걸 사용하는것
        @Valid @RequestBody PostBoardRequestDto2 requestBody
    ){
        ResponseEntity<ResponseDto> response = 
        boardService.postBoard(userEmail, requestBody);
        return response;
    }

🍉부분에서 email을 받아서 token이 생성되므로 board를 POST할때에 요청 헤더에 인증 토큰을 포함하여야 한다. 그때 인증처리된 email을 가져와야 한다.

post할때 로그인을 통해 생성된 토큰을 포함시키지않고 POST를 하게 되면 403 Forbidden오류가 뜨게 된다.

POST : http://localhost:4040/api/v2/auth/sign-in을 통해서
body에

{
  "userEmail": "이메일",
  "userPassword": "비밀번호"
}

전달 후 생성된 토큰을 Authorization부분에 넣어서
POST http://localhost:4040/api/v2/board 진행해야 한다.

    // 2. 특정 게시물 조회
    @GetMapping("/{boardNumber}")
    public ResponseEntity<? super GetBoardResponseDto> getBoard(
        @PathVariable("boardNumber") Integer boardNumber  
    ){
        ResponseEntity<? super GetBoardResponseDto> response =
            boardService.getBoard(boardNumber);
        return response;
    }

    // 3. 게시물 목록 조회
    @GetMapping("/list")
    public ResponseEntity<? super GetBoardListResponseDto> getBoardList(){
        ResponseEntity<? super GetBoardListResponseDto> response =
            boardService.getBoardList();
        return response;
    }

    // 4. top3 게시물 목록조회
    @GetMapping("/top3")
    public ResponseEntity<? super GetBoardListResponseDto> getBoardTop3(){
        ResponseEntity<? super GetBoardListResponseDto> response =
            boardService.getBoardTop3();
        return response;
    }

    // 5. 특정 게시물 수정
    @PatchMapping("") 
    public ResponseEntity<ResponseDto> patchBoard( 
        @AuthenticationPrincipal String userEmail,
        @Valid @RequestBody PatchBoardRequestDto2 requestBody
    ){
        ResponseEntity<ResponseDto> response = 
            boardService.patchBoard(userEmail,requestBody);
        return response;
    }

    // 6. 특정 게시물 삭제
    @DeleteMapping("/{boardNumber}")
    public ResponseEntity<ResponseDto> deleteBoard(
        🧃@AuthenticationPrincipal String 🍉userEmail,
        @PathVariable("boardNumber") Integer boardNumber
    ){
        ResponseEntity<ResponseDto> response =
            boardService.deleteBoard(userEmail, boardNumber);
        return response;
    }
}

🍉부분에서 email을 받아서 token이 생성되므로 board를 DELETE할때에도 요청 헤더에 인증 토큰을 포함하여야 한다. 그때 인증처리된 email을 가져와야 한다.

인증 처리를 받지 않고 DELETE를 진행하면 403 Forbidden 이 뜨게 된다.

PostBoardRequestDto2

@Data
@NoArgsConstructor
public class PostBoardRequestDto2 {
    
    @NotBlank
    private String boardTitle;
    @NotBlank
    private String boardContent;
    private String boardImageUrl;

    public PostBoardRequestDto2(PostBoardRequestDto dto){ //생성자
        this.boardTitle = dto.getBoardTitle();
        this.boardContent = dto.getBoardContent();
        this.boardImageUrl = dto.getBoardImageUrl();
    }
}

PatchBoardRequestDto2

@Data
@NoArgsConstructor
public class PatchBoardRequestDto2 {
    
    @NonNull
    private Integer boardNumber; 
    //필수값 int는 기본형타입이라서 null을 받을수 없으므로 Integer로 입력
    @NotBlank
    private String boardTitle;
    @NotBlank
    private String boardContent;
    private String boardImageUrl;

    public PatchBoardRequestDto2(PatchBoardRequestDto dto){
        this.boardNumber = dto.getBoardNumber();
        this.boardTitle = dto.getBoardTitle();
        this.boardContent = dto.getBoardContent();
        this.boardImageUrl = dto.getBoardImageUrl();
    }
}

BoardEntity

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "Board")
@Table(name = "Board") //어떤 db와 맵핑할지 지정
public class BoardEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //오토인클리먼트 설정
    private int boardNumber;
    private String writerEmail;
    private String title;
    private String content;
    private String boardImageUrl;
    private String writeDatetime;
    private int viewCount;

    public BoardEntity(PostBoardRequestDto dto){ 
    //생성자 만들기

        Date now = new Date();
        SimpleDateFormat simpleDateFormat = 
        	new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String writeDatetime = simpleDateFormat.format(now);

        this.writerEmail = dto.getBoardWriterEmail();
        this.title= dto.getBoardTitle();
        this.content = dto.getBoardContent();
        this.boardImageUrl = dto.getBoardImageUrl();
        this.writeDatetime = writeDatetime; //생성자가 돌아가는 시점으로 
        this.viewCount = 0; //만들어지면 초기값이 무조건 0
    }

    public BoardEntity(String userEmail,PostBoardRequestDto2 dto){ 
    //생성자 만들기

        Date now = new Date();
        SimpleDateFormat simpleDateFormat = 
        	new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String writeDatetime = simpleDateFormat.format(now);

        this.writerEmail = userEmail;
        this.title= dto.getBoardTitle();
        this.content = dto.getBoardContent();
        this.boardImageUrl = dto.getBoardImageUrl();
        this.writeDatetime = writeDatetime; //생성자가 돌아가는 시점으로 
        this.viewCount = 0; //만들어지면 초기값이 무조건 0
    }

}

BoardService

public interface BoardService {
    public ResponseEntity<ResponseDto> postBoard(PostBoardRequestDto dto);
    public ResponseEntity<ResponseDto> postBoard(String userEmail,PostBoardRequestDto2 dto);

    public ResponseEntity<? super GetBoardResponseDto> getBoard(Integer boardNumber);
    public ResponseEntity<? super GetBoardListResponseDto> getBoardList();
    public ResponseEntity<? super GetBoardListResponseDto> getBoardTop3();

    public ResponseEntity<ResponseDto> patchBoard(PatchBoardRequestDto dto);
    public ResponseEntity<ResponseDto> patchBoard(String userEmail,PatchBoardRequestDto2 dto);
    
    public ResponseEntity<ResponseDto> deleteBoard(String userEmail, Integer boardNumber);
}

BoardServiceImplement

@Service
public class BoardServiceImplement implements BoardService {

    private UserRepository userRepository;
    private BoardRepository boardRepository;
    private CommentRepository commentRepository;
    private LikyRepository likyRepository;

    @Autowired
    public BoardServiceImplement(UserRepository userRepository, 
        BoardRepository boardRepository, 
        CommentRepository commentRepository, 
        LikyRepository likyRepository){
        this.userRepository = userRepository;
        this.boardRepository = boardRepository;
        this.commentRepository = commentRepository;
        this.likyRepository = likyRepository;
    } //생성자 안만들고 위에 선언할때 Autowierd써서 사용해도 되지만 생성할때 생성할수가 없어서 이렇게 생성자 만들어서 사용중
        //권장사항이기도 하고 다른 외부 프레임워크에서도 이런 방식을 추천해서 사용한다.
        //아니면 선언때 final 걸어주고 Required..하는거 public위에 어노테이션 추가하면 생성자 만드는것과 같은 효과를 얻을수 있다.

    @Override
    public ResponseEntity<ResponseDto> 🔴postBoard(PostBoardRequestDto dto) { 
    //postBoard 동일한것 2개가 된다. token 포함해서 진행


        String boardWriterEmail = dto.getBoardWriterEmail(); //이메일 가져오는것
        PostBoardRequestDto2 dto2 = new PostBoardRequestDto2(dto);

        ResponseEntity<ResponseDto> response = 
                    postBoard(boardWriterEmail, dto2);

        //성공반환
        return response;
    }

    @Override
    public ResponseEntity<ResponseDto> 🔴postBoard(String userEmail, PostBoardRequestDto2 dto) {

        ResponseDto body = null;

        try{
            //존재하지 '않는' 유저 오류 반환
            boolean existedUserEmail = userRepository.existsByEmail(userEmail);
            if(!existedUserEmail){ //존재하지 않을때니까 !붙이기
                ResponseDto errorbody = new ResponseDto("NU","Non-Existent User Email");
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorbody);
            }

            //검증끝났으니까 삽입작업하기

            BoardEntity boardEntity = new BoardEntity(userEmail, dto);
            boardRepository.save(boardEntity);

            body = new ResponseDto("SU","Success");

        } catch(Exception exception) {
            //데이터베이스 오류 반환
            exception.printStackTrace();
            ResponseDto errorBody = new ResponseDto("DE", "Database Error");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorBody);
        }
        //성공반환
        return ResponseEntity.status(HttpStatus.OK).body(body);
    }

    @Override
    public ResponseEntity<? super GetBoardResponseDto> getBoard(Integer boardNumber) {
        
        GetBoardResponseDto body = null;
        //ResponseDto errorBody = null;

        try{

            if (boardNumber == null) { //board의 타입은 int, Repository의 타입은 Integer이라 null은 처리를 못해주므로 검증처리
                return CustomResponse.validationFaild();
            }

            //게시물 번호 조회
            BoardEntity boardEntity = boardRepository.findByBoardNumber(boardNumber);
            if (boardEntity == null) { //존재하지 않는 게시물 번호 조회시
                return CustomResponse.notExistBoardNumber();
            }
            //조회시 조회수 증가하는 기능 추가
            int viewCount = boardEntity.getViewCount();
            boardEntity.setViewCount(++viewCount);
            boardRepository.save(boardEntity);

            //User가져와서 boardWriter관련된 값 가져오기
            String boardWriterEmail = boardEntity.getWriterEmail();
            UserEntity userEntity = userRepository.findByEmail(boardWriterEmail);

            List<CommentEntity> commentEntities = commentRepository.findByBoardNumber(boardNumber);
            List<LikyEntity> likyEntities = likyRepository.findByBoardNumber(boardNumber);

            body = new GetBoardResponseDto(boardEntity, userEntity, commentEntities, likyEntities); 

        } catch(Exception exception) {
            exception.printStackTrace();
            return CustomResponse.databaseError(); 
        }

        return ResponseEntity.status(HttpStatus.OK).body(body);
    }

    @Override
    public ResponseEntity<? super GetBoardListResponseDto> getBoardList() {
        GetBoardListResponseDto body = null;
        try {

            List<BoardListResultSet> resultSet = boardRepository.getList();
            System.out.println(resultSet.size());
            body = new GetBoardListResponseDto(resultSet);

        } catch (Exception exception) {
            exception.printStackTrace();
            return CustomResponse.databaseError();
        }
        return ResponseEntity.status(HttpStatus.OK).body(body);
    }

    @Override
    public ResponseEntity<? super GetBoardListResponseDto> getBoardTop3() {
        GetBoardListResponseDto body = null;
        try {

            List<BoardListResultSet> resultSet = boardRepository.getTop3List();
            body = new GetBoardListResponseDto(resultSet);
            
        } catch (Exception exception) {
            exception.printStackTrace();
            return CustomResponse.databaseError();
        }
        return ResponseEntity.status(HttpStatus.OK).body(body);
    }

    @Override
    public ResponseEntity<ResponseDto> 🥕patchBoard(PatchBoardRequestDto dto) {
        
        
        String userEmail = dto.getUserEmail(); //검증시 필요한 데이터들 가져옴
        PatchBoardRequestDto2 dto2 = new PatchBoardRequestDto2(dto);

        ResponseEntity<ResponseDto> response = patchBoard(userEmail, dto2);

        return response;
    }

    @Override
    public ResponseEntity<ResponseDto> 🥕patchBoard(String userEmail, PatchBoardRequestDto2 dto) {
        
        int boardNumber = dto.getBoardNumber();
        String boardTitle = dto.getBoardTitle();
        String boardContent = dto.getBoardContent();
        String boardImageUrl = dto.getBoardImageUrl();

        try{
            // 존재하지 않는 게시물 번호 반환
            BoardEntity boardEntity = boardRepository.findByBoardNumber(boardNumber);
            if(boardEntity == null) return CustomResponse.notExistBoardNumber();

            // 존재하지 않는 유저 이메일 반환
            boolean existedUserEmail = userRepository.existsByEmail(userEmail);
            if(!existedUserEmail) return CustomResponse.notExistUserEmail();

            // 권한없음 //가져온 이메일과 writeremail이 같은지 비교해주면된다
            boolean equalWriter = boardEntity.getWriterEmail().equals(userEmail);
            if(!equalWriter) return CustomResponse.noPermissions();

            boardEntity.setTitle(boardTitle);
            boardEntity.setContent(boardContent);
            boardEntity.setBoardImageUrl(boardImageUrl); 
          

            boardRepository.save(boardEntity);

        } catch(Exception exception){
            exception.printStackTrace();
            return CustomResponse.databaseError();
        }

        return CustomResponse.success();
    }

    @Override
    public ResponseEntity<ResponseDto> deleteBoard(String userEmail, Integer boardNumber) {
        
        //ResponseDto body = null;

        try {

            if(boardNumber == null) return CustomResponse.validationFaild(); 
            //Integer타입과 int타입의 차이점 때문에 null처리
            // 존재하지 않는 게시물 번호 반환
            BoardEntity boardEntity  = boardRepository.findByBoardNumber(boardNumber);
            if(boardEntity == null) return CustomResponse.notExistBoardNumber();

            // 존재하지 않는 유저 이메일 반환
            boolean existedUserEmail = userRepository.existsByEmail(userEmail);
            if(!existedUserEmail) return CustomResponse.notExistUserEmail();

            // 권한 없음 반환 우리가 받은 유저이메일이 게시글의 작성자인지 비교하는것
            boolean equalWriter = boardEntity.getWriterEmail().equals(userEmail);
            if(!equalWriter) return CustomResponse.noPermissions();
            
            commentRepository.deleteByBoardNumber(boardNumber); //참조하는 댓글과 좋아요값을 지움
            likyRepository.deleteByBoardNumber(boardNumber);
            boardRepository.delete(boardEntity); //오류 다 확인했을시 지워버리는것

        } catch (Exception exception) {
            exception.printStackTrace();
            return CustomResponse.databaseError();
        }

        return CustomResponse.success();
    }
    
}

🥕 🔴 부분의 경우 인증된 토큰을 헤더에 포함 시키지 않은 경우과 포함 시킨경우를 오버로딩한것

WebSecurityConfig

class FailedAuthenticationEntiryPoint implements AuthenticationEntryPoint{

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.setContentType("application/json");
        🌷response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("{\"code\": \"AF\",\"message\" : \"Authentication failed\"}"); // \를 이스케이프 문자를 문자열로 받기위해서 작성
    }
    
}

@EnableWebSecurity
@Configuration
public class WebSecurityConfig {
    
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Autowired
    public WebSecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter){
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    protected SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception{

        httpSecurity.cors().and()
                    .csrf().disable()
                    .httpBasic().disable()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .authorizeHttpRequests().antMatchers("/api/v1/**","/api/v2/auth/**","/api/v2/board/list","/api/v2/board/top3").permitAll()
                    .antMatchers(HttpMethod.GET,"/api/v2/board/*").permitAll()
                    .anyRequest().authenticated().and()
                    .exceptionHandling().authenticationEntryPoint(new FailedAuthenticationEntiryPoint());

        httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }
}

🌷이 부분 진행하고 나면 UnAuthorized인 경우 AF의 오류 코드를 반환하게 된다.

profile
개발자가 될 거야!

0개의 댓글