인증 예외 처리 문제 해결 (2) - Spring Security의 인증 / 인가 예외 처리
(지난 편,,,)
↓
https://dodop-blog.tistory.com/447
지난 편에서는 Filter에서 발생한 예외는 이를 처리하는 또 다른 Filter를 구현하여 예외를 처리할 수 있다고 하였다.
그런데, 이번에 발생한 문제는 Filter구현이 아닌 AuthenticationEntryPoint 구현만으로 원하는 에외 처리를 할 수 있었다.
어떻게 된 걸까?
이와 관련해서 Spring Security에서 발생하는 예외와 이에 대한 처리 방법을 알아보자.
Spring Security 예외
먼저, Spring Security에서 발생할 수 있는 대표적인 예외에는 인증 예외와 인가 예외가 있다.
- 인증 예외
- AuthenticationException
- 인증에 실패하는 경우에 발생
- SpringContextHolder에 authentication 정보가 없으면 예외 발생
- Spring Security 설정에서 다음과 같은 설정에서 발생할 수 있음 (인증된 사용자만 받는 경우)
.authorizeRequests()
- 인가 예외
- AccessDeniedException
- 인가에 실패하는 경우에 발생
- Spring Security 설정에서 다음과 같은 설정에서 발생할 수 있음 (특정 Role을 가진 사용자만 허용하는 경우)
.anyRequest().hasAnyRole(Role.ADMIN)
그렇다면 SpringSecurity에서 발생시키는 예외는 어떻게 처리할 수 있을까?
Spring Security 인증 / 인가 예외 처리
Spring Security에서 인증 / 인가 예외 처리를 담당하는 필터에는 FilterSecurityInterceptor와 ExceptionTranslationFilter가 있다.
- FilterSecurityInterceptor
- 마지막 순서에 실행되는 필터
- 인증된 사용자의 요청에 대한 권한처리를 담당
- 인증 문제를 발견하면 AuthenticationException을, 인가 문제를 발견하면 AccessDeniedException을 throw
- ExceptionTranslationFilter
- FilterSecurityInterceptor에 의해 호출됨
- FilterSecurityInterceptor에서 발생한 예외 처리
- AuthenticationException
- AccessDeniedException
인증 예외 처리 (AuthenticationException)
인증 예외를 처리하는 방법에는 두가지 방법이 존재한다.
- AuthenticationEntryPoint 호출 : 로그인 페이지로 이동, 401 오류 코드 전달 등 예외 처리
- 인증 예외 발생 전의 요청 정보를 저장
- RequestCache : 사용자의 이전 요청 정보를 세션에 저장하여 사용할 수 있는 캐시 메커니즘 제공
- SavedRequest : 사용자가 요청했던 request의 파라미터, 헤더값 저장
- 인증 예외 발생 전의 요청 정보를 저장
- SpringContext의 authentication에 사용자의 요청 정보를 저장
- 인증 예외는 사용자 인증(authentication객체)이 존재하지 않아 발생
- Filter를 통해서 SecurityContext에 authentication 객체를 저장하면 계속 인증을 유지 할 수 있음
인가 예외 처리 (AccessDeniedException)
인가 예외처리에는 AccessDeniedHandler를 사용할 수 있다.
- AccessDeniedHandler 호출
- AccessDecisionManager
- 인증,요청,권한 정보를 이용해 사용자의 자원접근을 허용/거부 여부를 최종 결정하는 주체
- 여러 Voter를 가질 수 있고 Voter의 리턴 값을 이용해 판단
- 인증,요청,권한 정보를 이용해 사용자의 자원접근을 허용/거부 여부를 최종 결정하는 주체
- AccessDecisionVoter
- 접근 허가 여부를 판단하고 AccessDecisionManager에게 반환
- 접근 허가 여부를 판단하고 AccessDecisionManager에게 반환
- AccessDecisionManager
문제가 해결된 이유
이전 게시글에 작성되었던 Filter를 다시보자
class AuthenticationFilter(
// ... 생략
) : OncePerRequestFilter() {
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
val token = resolveToken(request)
checkIsValidToken(accessToken)
val authentication = //... 생략
SecurityContextHolder.getContext().authentication = authentication
filterChain.doFilter(request, response)
}
private fun resolveToken(request: HttpServletRequest): String? {
val bearerToken = request.getHeader("Authorization")
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7)
}
return null
}
private fun checkIsValidToken(token: String?) {
if (!isValidAccessToken(accessToken)) {
throw InvalidTokenException("유효하지 않은 사용자입니다. token : $token")
}
}
}
여기서 Filter에서 발생한 예외를 따로 처리해주는 Filter를 만들어주지 않았는데 어떻게 예외 처리가 될 수 있었던 걸까? 🤔
@Component
class AuthenticationEntryPoint(
private val objectMapper: ObjectMapper,
) : AuthenticationEntryPoint {
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
exception: AuthenticationException,
) {
val errorResponse = ErrorResponse.of(exception)
//... 생략 Object Mapper를 이용해서 Json타입의 ErrorResponse로 반환하도록 수정
}
}
바로 AuthenticationEntryPoint 때문이다...!
AuthenticationFilter에서 SpringContext에 authentication 정보를 넣어주기 전에 에외가 발생하여 인증이 이루어지지 않았고, 사용자 인증이 되지 않아 AuthenticationException이 발생했는데 이를 AuthenticationEntryPoint에서 처리해주었기 때문에 원하는 대로 예외 처리가 적용되었다.
즉, AuthenticationEntryPoint가 Filter에서 발생한 custom예외가 아닌 인증이 되지 않아 발생된 AuthenticationException를 처리해주었기 때문에 예상한 대로 동작한 것이다.
이렇게 Filter와 Interceptor, Spring Security 인증 / 인가 예외와 처리 방법에 대해서 공부하고 나서야 코드가 어떻게 동작하게 된 것인지 제대로 파악하게 되었다.
원래 팀에 합류했을때 같은 내용으로 리더님이 강의를 해주신 적이 있는데, 주먹 구구 식으로 구현하다보니 금세 잊고 구현하고 있던 나,,,
반성하면서 다시 한번 정리해본다,,, ✨
+
( 지금은 리더님이 알려주신 내용으로 얕게 공부해놓았기 때문에
스프링 시큐리티는 강의도 듣고 책도 보면서 내용을 정리할 예정이다! 💪🏼 )
끝 ✨
(참고한 사이트)
https://velog.io/@dailylifecoding/spring-security-exception-handling-and-request-caching
https://anjoliena.tistory.com/108
https://velog.io/@seongwon97/Spring-Security-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80%EC%9D%98-Exception
https://willbfine.tistory.com/553
https://catsbi.oopy.io/f9b0d83c-4775-47da-9c81-2261851fe0d0