늦은 프로그래밍 이야기
230103 TIL 본문
팀프로젝트
401, 403 예외처리
시큐리티 적용 후 토큰 값이 잘못 되었을 때 예외처리가 작동하지 않는 문제
JwtUtil에 validationToken이 에외를 catch하지 못하는 문제를 해결하는 파트를 담당하여 작업하게 되었다. SignatureException이 발생하나 catch가 되지 않았다.
import io.jsonwebtoken.security.SecurityException;
JwtUtil에 SecurityException을 import하지 않아서 발생한 문제였고, import를 넣어주니 Security Exception을 catch하며 예외 문구를 출력 해주며 문제가 해결되었다.
개인과제에서는 문제가 해결되었지만 팀프로젝트에서 구현하는 과정에서 같은 문제가 발생하였는데, SecurityException을 import 하였으나, 문제가 해결되지 않았다.
JwtUtil 클래스의 validateToken에 SignatureException 작성해줘도 예외 문구가 출력되지 않았고,
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SignatureException e) {
log.info("Token Error")
} catch (SecurityException | MalformedJwtException e) {
log.info("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
log.info("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
log.info("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false;
}
RestControllerAdvice에 SignarureException을 처리하는 메소드를 만들어 주어도 예외 문구가 출력되지 않았다.
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
@org.springframework.web.bind.annotation.ExceptionHandler(SignatureException.class)
public ResponseEntity<StatusResponseDto> SignatureExceptionHandler(SignatureException e) {
StatusResponseDto responseDto = new StatusResponseDto(StatusEnum.UNAUTHORIZE, e.getMessage());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
return new ResponseEntity<>(responseDto, headers, HttpStatus.UNAUTHORIZED);
검색을 해보았더니 Spring Security의 Filter에서 발생한 Exception은 RestControllerAdvice가 잡아주지 못한다고 하는 글을 보았다. 그래서 Filter 내부의 jwtExceptionHandler와 doFilterInternal의 문제일 거라 생각하고 여러 방법으로 수정해 보았는데 해결이 되지 않았다.
다른 동료 수강생들의 도움을 받아 이것 저것 시도해보고, 코드 사이사이에 콘솔로 로그도 찍어보면서 어디까지 작동하는지 확인해보기도 하며 노력하였지만, 문제는 해결되지 않았고 해결되지 않은 상태에서 하루 한번 push를 해야 되서 remote repo에 푸쉬하고 팀원들과 함께 원인을 파악해보는데 JwtAuthFilter 클래스의 jwtExceptionHandler 메소드로 정보를 보내준 후 return을 하지 않아서 코드가 계속 진행 되어서 Servlet에서 발생하는 에러여서 예외 문구가 출력되지 않는 것을 발견하였다.
@Override
protected void doFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain) throws ServletException, IOException {
String token = jwtUtil.resolveToken(request);
if (token != null) {
if(!jwtUtil.validateToken(token)) {
jwtExceptionHandler(response, "Token Error", HttpStatus.UNAUTHORIZED.value());
return;
}
Claimsinfo = jwtUtil.getUserInfoFromToken(token);
setAuthentication(info.getSubject());
}
filterChain.doFilter(request, response);
}
정말 사소한 문제였고 단 6글자가 없어서 시간을 엄청나게 낭비하고, 다른 동료 수강생들의 시간까지 뺏은 것 같아 미안한 마음이었다. 어려운 기능들에 매몰되어서 return이 없는 간단한 문제를 알아차리지 못한 것이 매우 화가 나기도 했지만, 기본적인 것에 다시 한번 주의를 기울이게 되는 계기가 되었던 것 같고, Security의 Filter에 대해 자세히 알지 못했지만, 이번에 많이 찾아보고 코드의 흐름을 이해해보면서 Filter의 예외처리에 대해서는 상세히 알게 된 것 같다.
토큰이 null일 때 ServletException으로 Request method ‘PUT’ is not supported 에러가 뜨는 문제
토큰을 안 넣어주었을 때 토큰 에러 문구를 띄우고 싶었지만, ServletException이 발생하면서 PUT 메소드를 지원하지 않는다는 문구가 출력되었다. 이걸 NullPointerException으로 바꿔도 주고 여러 시도도 해보고, 그냥 ServletException으로 놔두고 문구만 바꾸려고 시도도 해보았지만, 동료 수강생에게 의도와 맞지 않는 Exception이 발생하면 혼란을 야기할 수 있다는 얘기를 듣고, 다시 고민하기 시작하였다.
그러고 나서 기존에 문제가 발생했을 때 스프링 시큐리티 기본 로그인 페이지 폼을 삭제했을 때 해결 되었던 경우가 많아서 해당 코드를 삭제하고 실행 해보았는데 ServletException 에러는 사라지고 아무 동작도 하지 않는 상태가 되었다.
그 후 동료 수강생의 조언을 받아 강의에서 배웠던 AuthenticationEntryPoint를 상속 받아서 CustomAuthenticationEntryPoint 클래스를 작성하여 token 값이 null일 때 401 상태코드와 Token Error 문구를 출력하는데 성공하였다.
@Component
public class CustomAuthenticationEntryPoint implementsAuthenticationEntryPoint{
private static final SecurityExceptionDtoexceptionDto=
new SecurityExceptionDto(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
@Override
public void commence(HttpServletRequestrequest,
HttpServletResponseresponse,
AuthenticationException authException) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
try (OutputStream os = response.getOutputStream()) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValue(os,exceptionDto);
os.flush();
}
}
}
로그인 페이지에 대한 의문만 가득하였는데 동료 수강생이 생각한 내용을 듣고는 로그인 페이지에 대해 이해가 되었다. 토큰 값이 없으면 로그인을 하지 않은 상태이고, 로그인을 하라고 스프링 시큐리티에서 로그인 페이지를 띄워주는 것 같다. 토큰 값이 없는 상태로 토큰 값이 필요한 작업을 진행하려고 하면 로그인을 하지 않은 상태이기 때문에 값을 받아오지 못하고 PUT 메소드를 불러오지 못하는 메시지를 띄운다는 것이다. Postman만 사용하며 실제로 페이지가 구현된 작업을 많이 진행하지 않다보니 그런 쪽으로 생각이 잘 되지 않는 것 같다. 앞으로 페이지가 구현된 상태에서도 생각을 해봐야 할 것 같다.
admin 권한의 API에서 user 토큰 값을 받았을 때 403 예외 출력하기
다른 문제들에 비해 상당히 간단하게 작업을 완료하였다. RestControllerAdvice에 Forbidden 상태코드로 AccessDeniedExceptionHandler 메소드를 만들어주니 403 예외 문구가 출력되었다.
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@org.springframework.web.bind.annotation.ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<StatusResponseDto> AccessDeniedExceptionHandler(AccessDeniedException e) {
StatusResponseDto responseDto = new StatusResponseDto(StatusEnum.FORBIDDEN, e.getMessage());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
return new ResponseEntity<>(responseDto, headers, HttpStatus.FORBIDDEN);
}
시큐리티 필터에서의 예외처리는 DispatchServlet 이전에 발생하는 예외라서 RestController에서 처리하지 못한다고 했는데 403 예외는 어떻게 처리할 수 있는지 의문이 생겼다. 강의에서는 Forbidden 페이지를 따로 구현해서 403 에러를 핸들링 하는데 그것과 연관이 있는건지 어떻게 전역 예외처리로 핸들링 할 수 있는지 알아 보아야 할 것이다.
'내일배움캠프 > TIL, WIL' 카테고리의 다른 글
| 230105 TIL (0) | 2023.01.05 |
|---|---|
| 230104 TIL (계층형 카테고리) (0) | 2023.01.05 |
| 230102 TIL (0) | 2023.01.02 |
| 9주차 WIL (0) | 2023.01.01 |
| 221230 TIL (Git, 팀프로젝트) (0) | 2022.12.31 |