내일배움캠프 Spring 29일차 TIL

Skadi·2024년 2월 1일
0

스프링 숙련

JWT Token과 Spring Security가 적용된 스케쥴앱 서버 제작

더 공부하게 된 내용들

  1. 필터와 Security의 관계

    • http.authorizeHttpRequests((authorizeHttpRequests) ->
              authorizeHttpRequests
                  .requestMatchers(PathRequest.toStaticResources().atCommonLocations())
                  .permitAll() // resources 접근 허용 설정
                  .requestMatchers("/user/**").permitAll() // '/user/'로 시작하는 요청 모두 접근 허가
                  .anyRequest().authenticated() // 그 외 모든 요청 인증처리
          );
          http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
          http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    • 여기 나타난 "/user/**"에 해당한다고 해서 하단에 추가한 필터에서 인증/인가를 시행하지 않는 것이 아니다. 따라서 예외처리를 어떻게 해야하나 고민을 많이 한 결과...

      @Override
      protected void doFilterInternal(
         HttpServletRequest req, HttpServletResponse res, FilterChain filterChain)
         throws ServletException, IOException {
      
         String tokenValue = jwtUtil.getTokenFromRequest(req);
      
         if (StringUtils.hasText(tokenValue)) {
             // JWT 토큰 substring
             tokenValue = jwtUtil.substringToken(tokenValue);
             log.info(tokenValue);
      
             if (!jwtUtil.validateToken(tokenValue)) {
                 jwtTokenError.messageToClient(res, 400, "토큰에 문제", "failed");
      
                 return;
             }
      
             Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
      
             try {
                 setAuthentication(info.getSubject());
             } catch (Exception e) {
                 jwtTokenError.messageToClient(res, 400, "토큰에 문제", "failed");
                 return;
             }
         } else if (req.getRequestURI().startsWith("/user/")) {
             filterChain.doFilter(req, res);
             return;
         } else {
      
             jwtTokenError.messageToClient(res, 400, "토큰에 문제", "failed");
             return;
         }
      
         filterChain.doFilter(req, res);
      }
    • if (StringUtils.hasText(tokenValue)) : 토큰의 존재여부를 확인

    • if (!jwtUtil.validateToken(tokenValue)) : 토큰이 있을 경우 검증

    • else if (req.getRequestURI().startsWith("/user/")) : 토큰이 없을 경우 해당 요청의 URL이 /user/로 시작하면 회원가입 or 패스워드라 생각하고 PASS

    • else : 토큰이 없는데 회원가입 or 패스워드가 아니면 잘못된 접근 에러처리

  2. @Valid 와 @RestControllerAdvice의 조합

  • @Valid를 처리하는 방법에는 여러가지가 있다.
    1. @Valid와 함께 BindingResult를 사용 (처음 이 방법을 채택)

    @PostMapping("/yourEndpoint")
    public ResponseEntity<?> yourEndpoint(@Valid @RequestBody YourDto yourDto, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            
            List<String> errorMessages = fieldErrors.stream()
                    .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
                    .collect(Collectors.toList());
    
            // 필드와 에러 메시지를 리스트로 받아옵니다.
            // 예를 들어, 첫 번째 에러의 필드는 fieldErrors.get(0).getField(), 
            // 에러 메시지는 fieldErrors.get(0).getDefaultMessage()로 접근할 수 있습니다.
    
            // 필요에 따라 에러 처리 로직을 추가하면 됩니다.
            return ResponseEntity.badRequest().body(errorMessages);
        }
    
        // 유효성 검사에 성공한 경우에 대한 로직을 추가하면 됩니다.
        return ResponseEntity.ok("유효성 검사 성공");
    }
    1. DTO 클래스에 직접 유효성 검사 메서드 추가

       public class ScheduleRequestDto {
      
           @NotBlank(message = "제목을 입력하세요.")
           private String title;
      
           // 다른 필드...
      
           public void validateFields() {
               // 직접 유효성 검사 로직을 추가
               if (title == null || title.trim().isEmpty()) {
                   throw new IllegalArgumentException("제목을 입력하세요.");
               }
               // 다른 필드에 대한 유효성 검사 추가...
           }
       }
    2. ControllerAdvice를 사용한 전역 예외 처리

    • @Valid에서 검증된 에러는 MethodArgumentNotValidException.class로 처리할 수 있다.

        @RestControllerAdvice
      public class GlobalExceptionHandler {
      
         @ExceptionHandler(MethodArgumentNotValidException.class)
         @ResponseStatus(HttpStatus.BAD_REQUEST)
         public ResponseEntity<List<String>> handleValidationException(MethodArgumentNotValidException ex) {
             List<String> errors = ex.getBindingResult().getFieldErrors()
                     .stream()
                     .map(error -> error.getField() + ": " + error.getDefaultMessage())
                     .collect(Collectors.toList());
      
             return ResponseEntity.badRequest().body(errors);
         }
      }
  1. 추가사항
    	return new ResponseEntity<>(new UserResponseDto(successMessage, HttpStatus.OK).getHttpStatus());
        return ResponseEntity.ok().body(new UserResponseDto(successMessage,HttpStatus.OK ));
  • 두 코드 모두 ResponseEntity 값으로 리턴하는, 즉 클라이언트에게 상태메시지와 상태코드를 반환하는 코드를 작성했는데 왜 위의 코드는 작동하지 않고 아래 코드만 작동하는지 아직 잘 모르겠다.

0개의 댓글