ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SecurityContextHolder의 역할 및 원리
    spring/security 2021. 3. 3. 13:55

    배경

    WAS를 구성할 때 유저의 인증 인가를 관리하는 프레임워크로 spring security를 선택했다면securityContextHolder를 사용해본 경험이 있을 거다. 지금 내 프로젝트에서도 비지니스로직에서 게시물을저장할 때 SecurityContextHolder를 사용해 게시물의 작성자를 등록하고 있다.(이렇게 비지니스로직에 기술종속적 코드가 나오는 게 안 좋은 것 같긴 하다 => 아규먼트리졸버로 미리 처리)

     

    SecurityContextHolder의 역할은 간단하다. 객체의 세션을 CRUD하는 API를 제공하고 사용하면 그만이다. 하지만 본인은 코드 상에서 세션을 따로 저장한 적도 없을 뿐더러 세션을 get할 때 HttpRequest를 파라미터로 제공하는 웹 기술에 종속적인 방식을 사용하지 않는다. 단지 get()하면 모든 게 해결된다. 이거 어떻게이렇게 하는 걸까?

     

     

     

    동작 원리

     

    요청이 들어오고 시큐리티Filter의 차례가 되면 내부적으로 또 여러 필터를 거치게 된다.(HttpRequest를 파라미터로 보유) Filter중 SecurityContextPersistenceFilter를 보게 되면 내부적으로 SecurityContextHolder를 이용해 세션을 set하는 장면을 볼 수 있다.(이때 repo는 요청을 래핑해서 세션에 대한 처리를 원하는 방식으로 동작하도록 처리한다.) SecurityContextHolder가 내부적으로 어떻게 컨텍스트를 저장하는지 살펴보자.

     

     

    SecurityContextHolder는 내부적으로 SecurityContextHolderStrategy라는 객체에게 모든 처리를 위임한다.즉, 컨텍스트를 관리하는 방법을 추상화함으로써 다양한 전략을 구사할 수 있다.(스프링 단골 패턴) 이때 전략을 초기화하는 방법을 살펴보자.

     

    SecurityContextHolderStrategy는 static으로 선언되어 있고 static초기화 블록이 있으니SecurityContextHolder가 처음으로 실행(로딩)되는 시점에 전략이 정해짐을 알 수 있다. 또한 디폴트 전략으로 ThreadLocalSecurityContextHolderStrategy()를 채택하고 있음을 알 수 있다. 결국, 일반적으로 사용하는전략이 ThreadLocalSecurityContextHolderStrategy이고 SecurityContextHolder를 이용해 이 전략의 API를사용하니 ThreadLocalSecurityContextHolderStrategy의 동작 원리를 보자. (이름에서 거의 유추가 가능하지만 실제로 봤을 때 또 어려웠다)

     

    ThreadLocalSecurityContextHolderStrategy

    클래스의 static으로 필드 하나가 바로 초기화된 형태를 볼 수 있다. SecurityContext타입의 오브젝트를 관리하는 ThreadLocal API를 이용한 것이다. ThreadLocal은 내부적으로 스레드 로컬 데이터 영역에 접근하여(key, value)형태의 map을 하나 만들고 객체를 관리해준다.

     

     

    인증된 객체의 저장은 언제?

    유저가 인증을 시도하고 인증에 성공했다면 스레드로컬의 SecurityContext를 인증객체로 저장할 것이다. 이 시나리오를 가지고 인증을 수행하는 AbstractAuthenticationProcessingFilter를 살펴보자

    내부적으로 인증을 시도하고(이 메서드는 추상메서드이고 이 메서드를 상속해 인증에 대한 프로세스를 구현한다 -> 템플릿 메소드 패턴) 인증에 실패하면 현재 스레드로컬의 SecurityContext에서 Authenticaiton은 null로 남아 있을 것이다. 반면 인증에 성공해 Authenticaion이 null이 아니라면 successfulAuthentication이 호출된다.

    드디어 컨텍스트를 찾아와 Authentication을 세팅해주는 걸 볼 수 있다. 이러한 스토리가 있기에 같은 스레드 내에서라면 언제든지 인증 객체를 꺼내올 수 있었던 것이다.

     

    스레드로컬 청소

     

    마지막으로 SecurityContextPersistenceFilter에서 스레드로컬에 저장한 객체를 비워주는 작업까지 완료한다. 추가적으로 saveContext를 통해 스레드로컬에 있던 컨텍스트를 전달해 요청 세션을 저장하는 역할까지 하게 된다. 참고로 요청 세션에 대한 저장 방식은 security의 관심 대상이 아니다. 단지 현재 HttpRequest에게 요청하기만 하면 된다.

     

    여기서 왜 spring security가 독립 객체가 아닌 servlet의 Filter를 이용했는지 알 수 있다.

    SecurityContextPersistenceFilter는 서블릿에서 사용하는 필터가 아닌 security의 로직 안에 포함된 필터다.

    근데 컨텍스트를 클리어하는 시점이 왜 이 필터가 끝나는 시점일까? 시큐리티 필터 또한 내부적으로 본인들의 작업을 전부 처리한 뒤 다음 요청을 넘기는 게 아니라 계속 콜스텍에 쌓인다.

     

     

     

    정리

    - SecurityContextHolder를 이용해 세션을 스레드로컬에서 관리

    - static 메서드와 static 인스턴스를 이용해 스레드로컬에 있는 인증객체를 자유롭게 관리

     

    'spring > security' 카테고리의 다른 글

    Spring boot 2.x 와 Spring security Oauth2 연동  (0) 2021.04.21
    Spring Security에서 CORS 해결  (0) 2021.03.24
    Spring Security 아키텍처  (0) 2021.02.21

    댓글

Designed by Tistory.