
필터와 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 패스워드가 아니면 잘못된 접근 에러처리
@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("유효성 검사 성공");
}
DTO 클래스에 직접 유효성 검사 메서드 추가
public class ScheduleRequestDto {
@NotBlank(message = "제목을 입력하세요.")
private String title;
// 다른 필드...
public void validateFields() {
// 직접 유효성 검사 로직을 추가
if (title == null || title.trim().isEmpty()) {
throw new IllegalArgumentException("제목을 입력하세요.");
}
// 다른 필드에 대한 유효성 검사 추가...
}
}
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);
}
}
return new ResponseEntity<>(new UserResponseDto(successMessage, HttpStatus.OK).getHttpStatus()); return ResponseEntity.ok().body(new UserResponseDto(successMessage,HttpStatus.OK ));