Notice
Recent Posts
Recent Comments
Link
관리 메뉴

look-forest

스프링 시큐리티 전체 구조 개요 본문

Spring/Spring Security (feat. JWT, OAuth2)

스프링 시큐리티 전체 구조 개요

studyHub 2024. 10. 28. 14:55

시큐리티 동작 원리

시프링 시큐리티는 WAS의 필터 단에서 요청을 가로챈 후 시큐리티 역할 수행

  1. WAS의 필터에 필터를 만들어넣고 해당 필터에서 요청을 가로챔 
  2. 스프링 컨테이너 내부에 구현되어있는 시큐리티 감시 로직을 거침
  3. 시큐리티 로직을 마친 후 다시 WAS의 다음 필터 진행  

WAS의 필터 체인에서 Spring의 SecurityFilter 체인으로 잠시 넘어간다

 

스프링 시큐리티 로직은 여러개의 필터들이 나열된 필터 체인 형태로 구성

각각의 필터에서 CSRF, 로그인/로그아웃, 인가 등 작업 수행   

 

전체 구조

  • DelegatingFilterProxy
    스프링 빈을 찾아 요청을 넘겨주는 서블릿 필터. 스프링 시큐리티 의존성을 추가하면 자동 추가됨
  • FiterChainProxy
     DelagatingFilterProxy에 의해 호출되는 SecurityFilterChain을 들고 있는 빈
  • SecutiryFilterChain
    스프링 시큐리티 필터들의 묶음으로, 실제 시큐리티 로직이 처리되는 부분
    FilterChainProxy가 SecurityFilterChain들을 들고 있다.

모식도

 


DelegatingFilterProxy 와 FilterChainProxy

DelegatingFilterProxy 등록 Config : SecurityFilterAutoConfiguration

스프링 시큐리티 의존성을 추가하면 자동으로 등록됨.

이때 위임받을 FilterChainProxy도 등록하는데, 이름은 springSecurityFilterChain 이다.

 

FilterChainProxy : springSecurityFilterChain

필터 체인들을 가지고있다.

디버그 모드로 확인해보면 이렇게 많은 필터를 기본으로 포함하고 있음을 확인할 수 있다

 

SecurityFilterChain 등록

스프링 시큐리티 의존성을 추가하면 기본적인 DefaultSecurityFilterChain 하나가 등록된다.
커스텀 SecurityFilterChain을 등록하기 위해서는 SecurityFilterChain 인터페이스를 리턴하는 @Bean 메소드를 등록하면 된다. (한 개 이상 등록 가능)

(커스텀 SecurityFilterChain 를 등록하면 기존 DefaultSecurityFilterChain 는 등록되지 않음)

 

 

 


SecurityFilterChain 구조

SecurityFilterChain 내부 각각의 필터가 하나의 로직 (로그아웃, 로그인, 인가, 등등) 수행의 시작점이 된다.

필터는 요청을 낚아챌 뿐이고, 실제 로직 수행은 시큐리티 관련 스프링 빈에 위임해 처리하는 경우가 많다.  

 

서블릿 필터에 있는 DelegatingFilterProxy가 어떻게 스프링 컨테이너에 있는 SecutiryFilterChain 빈들을 호출할까?

  1. 스프링이 띄워질때, ServletContext에 WebApplicationContext를 저장한다.
    DelegatingFilterProxy는 ServletContext를 통해서 WebApplicationContext를 찾는다.
  2. DelegatingFilterProxy는 Spring ApplicationContext에 등록된 필터 빈을 찾아 실행하는 역할.
    springSecurityFilterChain이라는 이름으로 등록된 Bean을 찾아 실행.
    (Spring ApplicationContext에 FilterChainProxy 빈이 등록됨)
  3. FilterChainProxy는 실제 Spring Security 필터 체인을 실행하는 중앙 컨트롤러 역할.
    FilterChainProxy은 내부적으로 여러 개의 SecurityFilterChain을 관리함.
  4. FilterChainProxy는 요청 URL에 따라 적절한 SecurityFilterChain을 선택.
    SecurityFilterChain 내부에는 List<SecurityFilter>가 존재하고, 각각의 보안 필터를 실행함.
  5. 각 Filter에서 Spring Security의 빈 사용
public class DelegatingFilterProxy extends GenericFilterBean {

    private Filter delegate;

    // Spring ApplicationContext에서 필터 빈을 찾아서 실행
    private void initDelegate(WebApplicationContext wac) {
        this.delegate = wac.getBean("springSecurityFilterChain", Filter.class);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        if (this.delegate != null) {
            this.delegate.doFilter(request, response, chain);
        }
    }
}

 

 

스프링 시큐리티 제공 필터 간단 설명

더보기
  • DisableEncodeUrlFilter
    URL로 간주되지 않는 부분을 포함하지 않도록 설정
  • WebAsyncManagerIntegrationFilter
    비동기로 처리되는 작업에 대해 알맞은 시큐리티 컨텍스트(세션)을 적용
  • SecurityContextHolderFilter
    접근한 유저에 대해 시큐리티 컨텍스트 관리
  • HeaderWriterFilter
    보안을 위한 응답 헤더 추가 (X-Frame-Options, X-XSS-Protection and X-Content-Type-Options)
  • CorsFilter
    CORS 설정 필터
  • CsrfFilter
    CSRF 방어 필터
  • LogoutFilter
    로그아웃 요청 처리 시작점 GET : “/logout”
  • UsernamePasswordAuthenticationFilter
    username/password 기반 로그인 처리 시작점 POST : “/login”
  • DefaultLoginPageGeneratingFilter
    기본 로그인 페이지 생성 GET : “/login”
  • DefaultLogoutPageGeneratingFilter
    기본 로그아웃 페이지 생성 GET : “/logout”
  • BasicAuthenticationFilter
    http basic 기반 로그인 처리 시작점
  • RequestCacheAwareFilter
    이전 요청 정보가 존재하면 처리 후 현재 요청 판단
  • SecurityContextHolderAwareRequestFilter
    ServletRequest에 서블릿 API 보안을 구현
  • AnonymousAuthenticationFilter
    최초 접속으로 인증 정보가 없고, 인증을 하지 않았을 경우 세션에 익명 사용자 설정
  • ExceptionTranslationFilter
    인증 및 접근 예외에 대한 처리
  • AuthorizationFilter
    경로 및 권한별 인가 (구. filterSecurityIntercepter)

SecurityContextHolder

각각 필터의 작업 진행 상태를 공유할 필요가 있다.

SecurityFilterChain 내부에 존재하는 각각의 필터가 시큐리티 관련 작업을 진행할 때, 모든 작업은 기능 단위로 분업하여 진행하기 때문에 앞서 한 작업을 뒷단 필터가 알기 위한 저장소가 필요하다.
(가령, 인가 필터가 작업을 하려면 유저의 ROLE 정보가 필요한데, 앞단의 필터에서 유저에게 ROLE값을 부여한 결과를 인가 필터까지 공유해야 확인할 수 있다.)

사용자 각각에 대한 상태 공유를 위한 저장소 필요!

 

이 저장하려는 정보는 Authentication 객체에 담긴다. 

  • 로그아웃 필터 : 로그아웃 로직을 수행하면서 SecurityContext의 Authentication 객체를 비움
  • 로그인 필터 : 인증을 완료한 뒤 유저 정보를 담은 Authentication 객체를 넣음

Authentication 객체

  • Principal : 유저에 대한 정보
  • Credentials : 증명 (비밀번호, 토큰)
  • Authorities : 유저의 권한(ROLE) 목록

Authentication 객체는 SecurityContext에 포함되어 관리되며 SecurityContext는 0개 이상 존재할 수 있다. 
그리고 사용자 만큼의, 이 N개의 SecurityContext는 하나의 SecurityContextHolder에 의해서 관리된다.

SecurityContextHolder.getContext().getAuthentication().getAuthorities(); 위와 같이 접근 가능

 

멀티 쓰레드 환경에서 SecurityContext를 만들고 다루는 전략

다수의 사용자인 멀티 쓰레드 환경에서 SecurityContextHolder를 통해 SecurityContext를 부여하는 관리 전략은 다른 클래스에게 위임한다.
(사용자별로 다른 저장소를 제공해야 인증 정보가 겹치는 일이 발생하지 않는다.)
즉 SecurityContextHolder는 SecurityContext들을 관리하는 메소드를 제공하지만, 

실제로 등록, 초기화, 읽기와 같은 작업은 SecurityContextHolderStrategy 인터페이스를 활용한다.

 

SecurityContextHolderStrategy 구현체는 여러가지가 있지만 기본적으로는 ThreadLocal 활용 방식을 사용한다.

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

	private static final ThreadLocal<Supplier<SecurityContext>> contextHolder = new ThreadLocal<>();

}

 

Authentication 객체를 관리하는 SecurityContext는 사용자의 요청이 서버로 들어오면 생성되고, 처리가 끝난 후 응답되는 순간에 초기화된다.


참고 자료

https://spring.io/guides/gs/securing-web

https://www.devyummi.com/page?id=6695e062d31df967ae77c97b