티스토리 뷰
코드 출처: https://velog.io/@peppermint100/Spring-Boot-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC
스프링부트의 예외 처리에 있어서 가장 핵심은 값에 대한 validation을 계속 순차적으로 진행하면서 생기는 exception을 단 하나의 객체에서만 처리하고 그 객체를 위로 던지는 것이다.
@PostMapping("/login")
//모든 exception을 담아줄 response객체를 return.
public ResponseEntity<TokenContainingResponse> login(@RequestBody LoginRequest loginRequest) throws Exception {
//service layer에서 login 처리 후 token을 리턴
String token = userService.loginAndGenerateToken(loginRequest);
//response에 HttpStatus와 message, token을 담는다.
TokenContainingResponse response = new TokenContainingResponse(HttpStatus.OK, Controller.LOG_IN_SUCCESS_MESSAGE, token);
//exception을 위한 response와 ok 리턴.
return new ResponseEntity<>(response, HttpStatus.OK);
Exception이 발생하든 발생하지 않든 항상 HttpStatus는 OK여야 한다. 서버에서 따로 만든 에러 처리 페이지가 아닌 브라우저 엔진에서 직접 보여주는 에러 화면과 에러 메시지는 유저 만족도를 최악으로 떨어트린다.
//exception을 처리 하는 단 하나의 클래스
public class TokenContainingResponse {
private HttpStatus httpStatus;
private String message;
private String token;
}
@Service
...
public String loginAndGenerateToken(LoginRequest loginRequest) throws Exception {
//Optional로 처리하며 null 발생시 Exception을 던짐
String email = Optional.ofNullable(loginRequest.getEmail()).orElseThrow(EmptyValueExistException::new);
String password = Optional.ofNullable(loginRequest.getPassword()).orElseThrow(EmptyValueExistException::new);
Optional<User> user = userRepository.findByEmail(email);
//Optional로 처리하여 null 발생시 Exception
if(!user.isPresent()){
throw new UserNotExistException();
}
try{
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(),
loginRequest.getPassword()
));
//login 로직 후 실패시 Exception
}catch(Exception e){
throw new LoginFailException();
}
CustomUserDetails userDetails = userDetailsService.loadUserByUsername(user.get().getEmail());
String token = jwtUtil.generateToken(email);
return token;
}
이후에 Exception 객체에 대해 정의한다.
@AllArgsConstructor
@Getter
public class ApiException {
//exception으로 client에 어떤 값을 보여줄지 정한다.
private final String message;
private final HttpStatus httpStatus;
private final ZonedDateTime timestamp;
}
//service에서 생긴 exception은 RuntimeException을 상속받는다.
public class UserNotExistException extends RuntimeException{
}
//Exception을 한 곳에 집중하여 핸들링할 수 있게 도와주는 Bean
@ControllerAdvice
public class ApiExceptionHandler {
//처리하게 될 Exception을 매핑
@ExceptionHandler(value = {UserNotExistException.class})
public ResponseEntity<Object> handleUserNotExistException(UserNotExistException e){
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
//위에서 정의한 exception에 값을 채워준다.
ApiException apiException = new ApiException(
ExceptionMessage.USER_NOT_EXIST_MESSAGE,
httpStatus,
//UTC time에 normalized()한 시간.
ZonedDateTime.now(ZoneId.of("Z"))
);
return new ResponseEntity<>(apiException, httpStatus);
}
ControllerAdvice의 강력한 점은 어떤 Controller에서 exception이 발생하든 명시해둔 exception이 발생하면 (지금의 경우는 UserNotExistException)
이 ExceptionHandler에 매핑되어 미리 지정한 처리를 하게 되는 것이다.
하지만 Spring Security 과정 중에서 생기는 Exception에 대해서는 Controller에서 핸들링 할 수 없는 문제가 생긴다.
이 문제를 해결하기 위해서는
@RestController
@RequestMapping("/exception")
public class ExceptionHandleController {
@GetMapping("/jwt")
public void JwtException(){
throw new UserNotExistException();
}
}
Security 과정 중에서 생기는 exception도 똑같은 이름으로 던지도록 해둔다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(<URLS>)
.permitAll()
.antMatchers(<URLS>)
.permitAll()
.anyRequest()
.authenticated()
.and()
//앞으로 security에서 생길 모든 exception을 처리해주는 곳을 설정
.exceptionHandling().authenticationEntryPoint(new AuthenticationExceptionHandler())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
HttpSecurity를 받는 configure 메서드 설정에서 exception 핸들링을 AuthenticationExceptionHandler에서 할 수 있게 설정한 후
@Component
public class AuthenticationExceptionHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
httpServletResponse.sendRedirect("/exception/jwt");
}
}
response를 받아 위에 Security 중에 생기는 jwt의 exception을 던질 url을 redirect하면
이 url에서 발생하는 exception을 @ControllerAdvice가 캐치하여 핸들링 할 수 있게 된다.
'etc > TIL' 카테고리의 다른 글
HTML과 부트스트랩에서 사진 크기 조절하는 방법 (0) | 2022.06.02 |
---|---|
authentication.getAuthorities()에서 권한 여부 확인하는 방법 (0) | 2022.05.19 |
@Setter없이 Entity를 update하는 방법 (0) | 2022.04.28 |
스프링부트에서 빈 배열만 출력될 때 해결 방법 (0) | 2022.04.28 |
@PreAuthorize()로 메서드를 실행하기 전에 권한 검사를 하자 (0) | 2022.04.26 |
- Total
- Today
- Yesterday
- 아키텍처
- ModelAttribute
- 배포
- RequestPart
- JavaScript
- RequestParam
- Dap
- RequestBody
- 루나빔
- 레디스
- 도커
- vim
- IDE
- neovim
- lunarvim
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |