[ 정수원 스프링 시큐리티 #1 ] 스프링 시큐리티 기본 API & Filter 이해 (7)

김수호·2024년 3월 9일
0
post-thumbnail

지난 포스팅에 이어, 이번 포스팅에서는 12) 의 내용을 정리한다.

👉 목차는 다음과 같다.

1) 인증 API - 프로젝트 구성 및 의존성 추가
2) 인증 API - 사용자 정의 보안 기능 구현
3) 인증 API - Form Login 인증
4) 인증 API - Form Login 인증 필터 : UsernamePasswordAuthenticationFilter
5) 인증 API - Logout 처리, LogoutFilter
6) 인증 API - Remember Me 인증
7) 인증 API - Remember Me 인증 필터 : RememberMeAuthenticationFilter
8) 인증 API - 익명사용자 인증 필터 : AnonymousAuthenticationFilter
9) 인증 API - 동시 세션 제어, 세션 고정 보호, 세션 정책
10) 인증 API - 세션 제어 필터 : SessionManagementFilter, ConcurrentSessionFilter
11) 인가 API - 권한 설정 및 표현식
12) 인증/인가 API - 예외 처리 및 요청 캐시 필터 : ExceptionTranslationFilter, RequestCacheAwareFilter
13) Form 인증 - 사이트 간 요청 위조 : CSRF, CsrfFilter

바로 하나씩 확인해보자.


12) 인증/인가 API - 예외 처리 및 요청 캐시 필터 : ExceptionTranslationFilter, RequestCacheAwareFilter

이번 내용에서는 인증과 인가 API와 관련된 내용을 처리하는 필터인 ExceptionTranslationFilter, RequestCacheAwareFilter 두 개의 필터에 대해서 알아보자.

✔️ ExceptionTranslationFilter

  • 이 필터는 크게 두 가지 종류의 예외를 처리한다.
  • ① 인증 예외(AuthenticationException)를 처리한다.
    • 인증 예외 발생시 다음과 같이 처리한다.
      • 1) AuthenticationEntryPoint 인터페이스의 구현체를 호출한다.
        • (구현체 내부에서는) 로그인 페이지 이동 or 401 오류 코드 전달 등을 처리한다.
        • (참고) AuthenticationEntryPoint 인터페이스를 직접 구현해서 설정하게 되면, 스프링 시큐리티가 제공해주는 구현체가 아닌 우리가 만든 구현체가 호출된다.
      • 2) 인증 예외가 발생하기 전의 요청 정보를 저장한다.
        • RequestCache - 사용자의 이전 요청 정보를 세션에 저장하고 이를 꺼내 오는 캐시 매카니즘
          • SavedRequest - 사용자가 요청했던 request 파라미터 값들, 그 당시 헤더값들 등이 저장
        • (참고) SavedRequest 인터페이스의 구현체 클래스 (DefaultSavedRequest): 이전 요청 정보가 저장되는 클래스
        • (참고) RequestCache 인터페이스의 구현체 클래스 (HttpSessionRequestCache): 이전 요청 정보가 저장되는 클래스를 세션에 저장하는 역할을 하는 클래스
      • 3) SecurityContext 내부 인증 객체를 null로 초기화한다.
  • ② 인가 예외(AccessDeniedException)를 처리한다.
    • 인가 예외 발생시 다음과 같이 처리한다.
      • AccessDeniedHandler 인터페이스를 구현한 구현체를 호출한다. ( AccessDeniedHandler 에서 예외에 대한 후속 처리를 진행 )
  • (참고) 위 두 가지 예외는 FilterSecurityInterceptor 라는 필터에 의해서 발생된다. 해당 필터는 스프링 시큐리티가 관리하는 보안 필터 중 가장 마지막에 위치한다. 그리고 해당 필터 앞에 위치하는 필터가 ExceptionTranslationFilter 이다.
    • 따라서 ExceptionTranslationFilter 가 그 다음 필터인 FilterSecurityInterceptor 로 사용자의 요청을 전달해 줄때 try ~ catch 로 감싸서 호출한다. 그러므로 FilterSecurityInterceptor 필터에서 발생하는 인증 예외와 인가 예외는 ExceptionTranslationFilter 에서 처리된다.

 

지금까지 살펴본 내용을 그림으로 이해해보자.

  • 참고)
    • (가정) 사용자는 인증을 받지 않은 상태이고, /user 경로로 접근한다고 가정하자. 그리고 /user 경로는 인증된 사용자만 접근이 가능하도록 설정되어 있다.
    • 사용자가 /user 자원 경로로 요청을 시도한다.
    • 그러면 해당 요청은 스프링 시큐리티가 관리하는 보안 필터 중 가장 마지막에 위치하는, 인가 처리를 하는 FilterSecurityInterceptor 까지 이동한다.
    • 그런데 여기서 보니까, 해당 사용자는 인증을 받지 않았다. 따라서 "인증을 받지 않은 익명 사용자"가 접근한 것이므로, FilterSecurityInterceptor 필터는 인가 예외를 발생시킨다.
    • 그런데 여기서, ExceptionTranslationFilter 가 catch 내부에서 인가 예외를 처리할 때,
      • 만약 해당 사용자가 익명 사용자인 경우 또는
      • remeberMe로 인증된 사용자인 경우에는 AccessDeniedHandler를 호출하지 않고, 인증 예외(AuthenticationException)를 처리하는 과정을 호출하도록 한다. (=즉, 결과적으로 위의 경우, 인가 예외가 발생하지만, 인증 예외를 처리하는 과정으로 처리한다.)
    • 인증 예외 발생시 ExceptionTranslationFilter 필터는 다음과 같은 일을 처리한다.
      • 1) SecurityContext 내부 인증 객체를 null로 초기화한다.
      • 2) HttpSessionRequestCache 를 통해서 예외 발생 전 사용자의 요청 관련 정보를 세션에 저장해둔다. 참고로 해당 요청 정보는 DefaultSavedRequest 객체에 저장되고, 해당 객체가 세션에 저장된다.
      • 3) AuthenticationEntryPoint 인터페이스의 구현체를 호출한다.
        • 참고로 해당 구현체 내부에서는 일반적으로 로그인 페이지로 redirect 처리한다.
    • 만약 위에서 해당 사용자가 인증은 받은 사용자이지만 자원에 접근 가능한 권한이 유효하지 않은 경우라면, FilterSecurityInterceptor 에서 인가 예외가 발생하고, ExceptionTranslationFilter 내부에서 인가 예외에 대한 처리를 진행한다.
      • 인가 예외 발생시 ExceptionTranslationFilter 필터는 AccessDeniedHandler 인터페이스를 구현한 구현체를 호출해서 후속 작업을 처리한다. 보통 구현체 내부에서는 일반적으로 /denied 페이지로 리다이렉트한다. 그래서 사용자로 하여금 자원에 접근할 수 없다는 메세지를 띄운다.
    • (정리) ExceptionTranslationFilter 는 인증 예외와 인가 예외를 처리한다. 그리고 그 예외는 인가 처리를 담당하는 필터인 FilterSecurityInterceptor 에서 발생시킨다.

 

그러면 스프링 시큐리티가 제공하는 예외 처리 API를 살펴보자.

  • 참고)
    • http.exceptionHandling() 을 작성함으로써 인증/인가 예외처리 기능이 작동한다. (즉, ExceptionTranslationFilter가 동작한다.)
    • 크게 authenticationEntryPoint(..)accessDeniedHandler(..) 2가지 하위 API 설정을 통해 예외를 처리할 수 있다.
      • 1) authenticationEntryPoint(..) API를 통해서, 우리가 직접 AuthenticationEntryPoint 인터페이스를 구현해서 설정해주면 된다. 그러면 인증 예외가 발생시 우리가 설정한 클래스가 호출되어 후속 처리가 진행된다. 참고로 해당 인터페이스에는 commence(..) 라는 메서드가 있다. 이 메서드 안에서 인증 예외가 발생했을 경우에 대한 후속 처리를 직접 구현하면 된다. ( 로그인 페이지로 리다이렉트 or 인증 예외와 관련된 오류코드(401) 처리 등)
      • 2) accessDeniedHandler(..) API를 통해서, 우리가 직접 AccessDeniedHandler 인터페이스를 구현해서 설정해주면 된다. 내부에서는 인가 예외 발생시에 대한 후속 작업을 구현하면 된다. ( 접근 불가 페이지로 리다이렉트 등 )

 

👉 이제 예외 처리 API를 사용해서 직접 두개의 인터페이스를 구현한 구현체를 만들어보고, 인증/인가 예외가 발생했을 경우에 우리가 만든 구현체를 호출해서 어떻게 후속작업을 처리하게 되는지 코드로 작성해서 확인해보자.

  • SecurityConfig 수정
    • 1) authenticationEntryPoint(..)accessDeniedHandler(..) 를 직접 구현해서 설정했다.
      • 인증 예외 발생시에는 /login 페이지로 리다이렉트하고, 인가 예외 발생시에는 /denied 페이지로 리다이렉트 하도록 작성했다.
      • 참고로 인터페이스를 우리가 직접 구현했으므로, 리다이렉트시 /login, /denied 은 우리가 만든 경로로 이동한다. ( 스프링 시큐리티가 제공하는 기본 로그인 페이지가 아닌, 직접 만든 로그인 페이지로 이동하도록 처리된다. )
        • 따라서, SecurityController에 경로를 추가해줘야 한다.
          (아래 SecurityController 추가 참고)
    • 2) (인증에 성공한 이후) successHandler 에서는, ExceptionTranslationFilter 에서 저장한 이전 요청 정보를 꺼내서, 사용자가 원래 가고자 했던 경로로 이동한다.
    • 3) 참고로 /login/denied 경로 중에서 /login 경로는 누구나 접근 가능해야 하므로, .antMatchers("/login").permitAll() 로 인가 설정을 해주었다.
  • SecurityController 추가
    • /login, /denied 를 추가했다.
  • 서버 기동 후 정상적으로 동작하는지 확인을 위해 아래 사항을 확인해보자.
    • 1) 인증을 하지 않은 익명사용자가, 인증이 필요한 루트 경로에 접근하는 경우, authenticationEntryPoint 에 해당되어, 우리가 만든 /login 경로로 리다이렉트 되는지 확인하자.
    • 2) authenticationEntryPoint(..) 를 주석처리하고, 인증을 하지 않은 익명사용자가 /user 경로로 접근해보자. 그리고 리다이렉트된 로그인 페이지에서 user 계정으로 인증하면, 이전 요청 정보였던 /user 경로로 이동되는지 확인해보자.
    • 3) 인증한 user 계정에서 /admin 경로를 요청해보자. 이 경우는 인증한 사용자의 권한이 유효하지 않으므로, /denied 페이지로 리다이렉트 되는 것을 확인해보자.

 

✔️ RequestCacheAwareFilter

  • HttpSessionRequestCache 와 DefaultSavedRequest 클래스와 관련해서 RequestCacheAwareFilter 가 있는데 이 필터는 세션에 DefaultSavedRequest 가 존재한다면 이전 필터로 부터 전달 받은 요청 객체가 아닌 DefaultSavedRequest 객체를 담은 SavedRequestAwareWrapper 객체를 생성해서 다음 필터로 전달해 주는 역할을 하고 있다.
    • 로그인 성공 후 원래 요청 정보를 재구성 하기 위해 사용.

 

✔️ 참고 링크


강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 정수원 강사님께 있습니다.

profile
현실에서 한 발자국

0개의 댓글