ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring security 동작 방식 정리
    개발 2020. 5. 2. 19:37

     

    작성중... 

     

    인증 과정과 에러 처리 과정 추가 예정 

     

     

    이번에 사내에서 인증 관련하여 담당하게되어 개발을 진행하면서 공부했던 내용들을 정리해보고자 한다.

     

    * 해당 포스트는 정보 공유가 목적이 아닌 학습 정리를 위한 포스트이므로 친절하게 각 내용을 설명하기 보다는 디버깅을 통해 동작 방식을 이해하기 위함이다.

     

     

    궁금했던 내용들은 "Spring security의 필터는 어떤식으로  동작하나, Exception 처리는 어떤식으로 해야하고 왜 그렇게 동작하는가" 였다

     

    먼저, 현재 프로젝트는 Session 기반이 아닌 Token  으로 개발을 진행중에 있고 그중 Token은 JWT 를 사용하고 있다.

     

    아래 이미지는 Client로부터 Request가 들어왔을 때 동작하는 ApplicationFilterFactory.createFilterChain(...) 메소드의 일부이다. 

     

    Applicaiton 기동과 동시에 Spring 설정 값을 통해 Filter 정보를 로드하고 해당 정보로 각 Request에 대해서 적절한 FilterChain을 생성하고 생성한 FilterChain을 통해 Request 및 Response에 대한 적절한 처리를 해주는 방식이다. 

     

     

     

     

    그 중 Spring Security와 관련된 Filter는 위 이미지 상의 "springSecurityFilterChain"이다. 

     

    요청에 대해서 적절한 filter 를 선택하는 방식은 url pattern 을 매칭하고 추가로 Filter 정보를 갖고 있는 filterMaps 의 각 filterMap의 아래 상수 값과 비트 연산을 통해 체크하는 방식이다.

     

     

     

     

    이렇게 생성된 FilterChain 은 여러 클래스 명칭 그대로 여러 Filter가 연결되어 있는 형태로 순서대로 각 Filter를 통과하며 각각의 처리를 하게된다. 

     

    위에서 ApplicationFilterFactory 를 통해 생성된 AppliationFilter 의 경우 아래와 같이 내부에 Filter 정보를 가지고 동작하게 되는데, 

     

    각 Filter들은 Servlet Filter 인터페이스를 구현하였기에 갖고 있는 메소드인 doFilter를 통해 동작하게 된다. 

     

     

     

     

    이 중 springSecurityFilterChain 의 경우에는 단순한 Filter 형태가 아닌 내부에 또 다른 Filter Chain을 갖고 있는 형태이다. 

     

    springSecurityFilterChain라는 명칭으로 등록된 Filter의 경우 구현체는 DelegatingFilterProxy 인데 이 클래스의 경우 내부에서 WebApplicationContext에서 등록된 Filter 명(springSecurityFilterChain) 으로 생성되어져 있는 Bean을 통해  실제 Spring security 에서 사용할 Filter를 가져와 사용하게 된다. 

     

    그렇기에 과거 XML로 Filter를 등록하여 설정 할 때 Filter명을 함부로 바꾸면 동작하지 않던 이유이기도 하다.

     

    * 아래는 DelegatingFilterProxy 클래스의 일부이다. 

     

     

     

     

    이렇게 가져온 Filter는 FilterChain으로 되어있고 또 내부를 보면 아래와 같이 여러 Filter를 포함하고 있습니다. 

     

     

     

     

    이 중 2번째 인덱스의 Filter 리스트는 어딘가 익숙할텐데 Spring security를 사용하면 기본으로 등록되는 Filter 리스트이다. 

     

    그럼 0번째, 1번째 인덱스의 Filter는 뭘까? 현재 프로젝트의 경우 OAuth2를 사용하고 있고 인증 서버와 리소스 서버를 한 곳에 구현하였기에 그에 따라 생성된 Filter이다.

     

    순서대로 디버깅을 통해 따라 들어가보도록 하자. 

     

    각 FilterChain 별로 requestMatcher를 가지고 있는데 해당 객체를 통해서 현재 Request에 각 filterChain을 적용할 것인지 판단하게 된다. 

     

    현재 보고 있는건 인증 Filter Chain이므로 아래와 같은 requestMatcher를 갖고 있다. 

     

    * 기본적으로 Spring security OAuth2 인증 서버로 설정 할 경우 /oauth/token을 인증을 위한 URL로 하지만 내 경우에는 설정을 통해 /api/v1/login으로 변경하였다. 

     

     

     

     

    이 RequestMatcher를 통해 Request에 대해 유효한 Filter라고 판단했을 경우 FilterChain 내부에 Filter 리스트를 가져와 다시 FilterChain을 생성하여 이 FilterChain 을 통해 Filter 를 동작하게 된다. 

     

     

     

     

     

    그 결과 아래와 같이 인증 관련 Filter들이 동작하게 된다.  결국 Security의 Filter들은 각자의 동작을 처리 후 다음 Filter를 실행 시키면서 동작하는 것이다.

     

     

     

     

     

    기본적인 동작 방법은 위와 같지만 이 각 Filter 처리 중 Exception이 발생할 경우에는 어떤식으로 처리하는걸까?

     

    또는 Servlet단에서 동작 중 문제가 발생했을 경우에는 어떤식으로 처리할까? 

     

     

    위에서 등장한 모든 Filter들은 프로그래머가 직접 개발한 Filter가 아닌 Spring에서 제공해주는 Filter들이다. 그에 따라 직접 예외처리 코드를 넣을 수가 없다.

    그래서 Spring에서는 다양한 Error처리를 위한 기능을 제공하고 있다. 

     

    상세한 내용은 아래 추가로 설명하도록 하겠다. 테스트를 위해 2가지 케이스로 Exception을 발생 시키고 그에 따라 처리 과정을 디버깅을 통해 따라가보겠다. 

     

    먼저 Filter에서의 문제가 발생했을 때이다.

    이 글을 적게된 계기가 이 부분인데 인증 처리 중 Exception이 발생했을 경우 이상하게 Security 관련 필터가 두번 동작하여 이 부분에 의문을 품기 시작하였고 동작 과정을 살펴 보던 중 인증 과정에서 동작했던 Filter와는 조금 다른거 같다고 느껴

     

    디버깅을 해보게 되었다. 

     

     

     

     

    위 소스는 인증 과정 중 BasicAuthenticationFilter에서 Client Id와 secret을 유효성 체크하는 ProviderManager에서 Provider의 authenticate에서 잘못된 Client Id로 UsernameNotFoundException이 발생한 경우이다. 

     

    Filter에서 문제가 없이 동작했을 경우라면야 Chain 되어있는 다음 Filter를 호출하는게 정상이지만 이 경우 문제가 발생하였기에 throw를

     

    통해 예외를 상위로 계속해서 던지게 되어있다.  

     

    참고로 보통 ProviderManager는 내부에 여러 종류의 Provider를 가지고 동작하게 되는데, 이 Provider를 각각 동작하며 가장 마지막에 발생한 Exception만을 상위로 던지게 되어있다. 

     

     

    이렇게 던져진 Exception으로 인해 처음 Request를 실행했던 StandardHostValue.invoke(...) 까지 다시 돌아오게 되는데,

     

    이 과정에서 response 의 isErrorReportRequired() 값은 true가 되고 그에 따라 추가적인 로직이 동작하게 된다. 

     

    계속 따라서 들어가보면 Response의 상태값을 통해 설정된 errorPage를 찾게 된다. 

     

    서버 개발 경험이 거의 없기도 하고 그나마 있는 경력은 모두 Spring 기반이라 기본 지식이 없어서 모르겠지만 아마 Tomcat상에 Error 페이지를 설정하는 부분이 있는 것으로 판단된다.

    여하튼 따로 설정 한 적은 없으므로 아래 로직 실행 결과 errorPage 는 null이 된다. 

     

    이 과정 중 errorPage가 없을 경우 아래와 같이 기본 값이 셋팅되게 된다.

     

     

     

    위의 과정을 거친 후 StandardHostValue.custom(...) 으로 이동하게 되는데, 실질적으로 해당 메소드를 통해 이후 과정을 처리하게 된다. 

     

    아래 이미지 로직을 보면 response의 committed 값을 보고 이후 동작을 처리할지를 판단한다. (어느 부분에서 셋팅 된 값인지 모름)

     

     

     

     

    이 메소드 내부에서 RequestDispatcher을 통해 Request를 Forward 한다. 

     

    이제 실헹되는 로직은 기본으로 설정된 ErrorPage에 의해 /error 로 Forwarding 되는 과정이다. 

     

    RequestDispatcher를 통해 Forward 되면 ApplicationDispatcher.invoke(...) 까지 이동하게 되는데 아래 이미지 와 같이 다시 한번

     

    Request에 따른 ApplicationFilterChain 을 가져오게 된다.

     

    이때도 처음 Request 처리 당시와 같이 ApplicationFilterFactory 를 통해 ApplicationFilterChain을 

     

    가져오는데, Url의 경우 /error 이고 DispatcherType 의 경우 ERROR 로 셋팅되어 filterMaps 를 통해 ApplicationFilterChain 구하게 된다. 

     

     

     

     

     

    '개발' 카테고리의 다른 글

    Clustering Index란?  (0) 2020.04.05
    AWS VPC란?  (0) 2020.03.24
    Java Garbage Collection (GC)  (0) 2020.03.21
    AWS IOT Rule Engine 을 통한 Elasticsearch 연동  (0) 2020.03.20
    AWS Elasticsearch  (0) 2020.03.20
Designed by Tistory.