[Spring] Spring Security와 Filter Chain
💡 Spring Security 란?
Spring Security는 인증과 인가, 권한 등의 애플리케이션 보안을 담당하는 Spring 하위 보안 프레임워크이다.
✔️ 인증(Authentication)과 인가(Authorization)
- 인증(Authentication) : 접근하려는 사용자가 누구인지 확인하는 절차
- 인가(Authorization) : 인증된 사용자가 요청한 자원에 접근이 가능한지 결정하는 절차
쉽게 생각해보면, 웹 사이트에 접근하기 위해 로그인을 하는 것이 인증 절차이고,
로그인 한 사용자가 '내 정보 수정하기' 요청을 했을 때 접근이 가능한지 자원에 대한 권한을 확인하는 것이 인가 절차이다.
Spring Security는 기본적으로 인증 절차를 거친 후에 인가 절차가 진행되며, 인가 절차 과정에서 해당 리소스에 대한 접근 권한이 있는지 확인한다. Spring Security는 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 인증 방식을 사용한다.
- 접근 주체(Principal) : 보호받는 Resource에 접근하는 대상
- 비밀번호(Credential) : Resource에 접근하는 대상의 비밀번호
💡 필터(Filter)
Spring Security는 서블릿 필터(Servlet Filter)를 기반으로, Filter 흐름에 따라 동작한다.
필터는 서블릿 컨테이너의 요청과 응답을 가로채서 애플리케이션의 핵심 비즈니스 로직이 실행되기 전에 특정 작업을 수행할 수 있다.
Spring Security는 이러한 필터를 사용해서 웹 애플리케이션에서 정의된 구성 정보(Configuration)를 바탕으로 보안을 강화한다.
필터는 주로 리소스 요청에 대한 인증, 권한 확인 할 때 사용되며,
요청이 DispatcherServlet에 전달되기 전에 헤더를 검사해서 인증 토큰의 유무와 올바른 요청인지 등에 대해 검사할 수 있다.
✔️ 필터의 흐름
HTTP 요청 -> WAS(서블릿 컨테이너) -> 필터 -> 서블릿 -> 컨트롤러
✔️ 필터 체인(Filter Chain)
필터가 여러 개 연결되어 동작하는 형태를 필터 체인(Filter Chain)이라고 한다.
HTTP 요청 -> WAS(서블릿 컨테이너) -> 필터1 -> 필터2 -> ... -> 필터N -> 서블릿 -> 컨트롤러
💡 스프링 시큐리티 필터 체인 (Spring Security Filter Chain)
Spring Security Filter Chain은 Spring Security에서 제공하는 인증, 인가를 위한 필터들의 모음이다.
Spring Security에서 가장 핵심이 되는 기능들을 제공하며,
거의 대부분의 서비스가 Spring Security Filter Chain에서 실행된다.
Servlet Filter와 대표적인 Filter Chain 4가지에 대해 알아보려고 한다.
1. Application Context 초기화
Application Context 초기화 과정에서 사용자가 정의한 Spring Security Filter Chain이 생성된다.
Spring Security에서 인증, 인가에 대한 처리를 여러 개의 필터를 통해 연쇄적으로 실행하고 수행한다.
이때, 상황에 따라서 필요한 필터를 사용한다.
SecurityFilterChain Bean을 반환하는 fiterchain 메소드의 매개변수인 HttpSecurity를 통해 사용할 필터와 사용자가 직접 정의한 필터를 정의할 수 있다. 이러한 설정을 바탕으로 애플리케이션 구동 시 Security Filter Chain이 구성되고 실행된다.
2. DelegatingFilterProxy
클라이언트의 리소스 요청이 Spring MVC에 도달하기 전에, Servlet Container에서 DelegatingFilterProxy가 요청을 받는다.
DelegatingFilterProxy는 Servlet Container와 Spring Container를 연결해주는 필터이다.
DelegatingFilterProxy는 Servlet 기술이기 때문에 Servlet Container에서만 생성되고 실행된다.
Spring Bean으로 주입하거나 Spring에서 제공되는 기술을 Servlet에서 사용할 수 없다.
* DelegatingFilterProxy는 실제 보안 처리 기능은 하지 않고, Servlet Container에서 위임만 해주는 Servlet Filter이다.
DelegatingFilterProxy 동작 원리
1. Servlet Container로 넘어온 클라이언트의 요청을 DelegatingFilterProxy가 받는다.
2. DelegatingFilterProxy는 SpringSecurityFilterChain 이름으로 생성된 Bean을 ApplicationContext에서 찾는다.
3. Bean을 찾으면 SpringSecurityFilterChain으로 요청을 위임한다.
3. FilterChainProxy
DelegatingFilterProxy로부터 요청을 위임받은 SpringSecurityFilterChain Bean은
FilterChain의 역할을 하는 FilterChainProxy이다.
대표적인 Filter Chain 4가지는 다음과 같다.
(1) SecurityContextPersistenceFilter
SecurityContextRepository에서 SecurityContext를 가져오거나 생성하는 역할을 한다.
SecurityContextRepository는 내부적으로 HttpSessionSecurityContextRepository 클래스를 가진다.
HttpSessionSecurityContextRepository 클래스는 SecurityContext 객체를 생성하고 세션에 저장하며,
저장된 SecurityContext를 조회하고 참조하는 클래스이다.
SecurityContextRepository는 loadContext 메소드로 인증을 시도한 사용자가 이전에 세션에 저장한 이력이 있는지 확인한다.
- 처음 인증을 시도하는 사용자 or 익명 사용자
- 세션에 저장된 이력이 없기 때문에 SecurityContext를 생성하고, SecurityContextHolder에 저장한 후 다음 필터를 실행한다.
- 저장된 이력이 있는 사용자
- SecurityContext를 꺼내와서 SecurityContextHolder에 저장한다
모든 작업을 마치고, 최종적으로 클라이언트에게 인증하기 직전에 항상 Clear SecurityContext를 실행한다.
(2) LogoutFilter
로그아웃 요청을 처리하는 필터로, 로그아웃 요청 시에만 실행된다.
LogoutFilter는 세션 무효화, 인증 토큰 삭제, SecurityContext에서 해당 토큰 삭제 등 다양한 기능을 제공한다.
(3) UsernamePasswordAuthenticationFilter
Form Based Authentication(폼 기반 인증)을 위한 인증 필터이다.
- Authentication 객체를 만들어서 사용자의 아이디와 패스워드를 저장하고, AuthenticationManager에게 인증 처리를 맡긴다.
- AuthenticationManager는 실질적인 인증을 검증 단계를 총괄하는 AuthenticationProvider에게 인증 처리를 위임한다.
- AuthenticationProvider는 UserDetailService와 같은 서비스를 이용해서 인증을 검증한다.
- 최종적으로 인증이 성공되면, 인증에서 성공한 결과를 담은 인증 객체를 생성한 후에 SecurityContext에 저장한다.
(4) ExceptionTranslationFilter
FilterChain을 거치면서 발생하는 인증/인가 예외를 처리하는 필터이다.
- AuthenticationException (인증 예외 처리)
- AuthenticationEntryPoint 호출 : 인증이 실패했을 때 어떻게 핸들링할지 결정한다.
로그인 페이지로 Redirect 되거나 401(Unauthorized) 상태 코드 반환 등의 작업을 수행할 수 있다. - RequestCache / SavedRequest : 요청에 대한 정보를 기록하기 위한 용도로 사용된다. 로그인을 하지 않은 상태에서 접속한 URL에서 인증을 실패하여 로그인 페이지로 가는 경우, 사용자가 접근한 정보를 RequestCache와 SavedRequest에 담고 사용자가 로그인할 때 담아둔 정보를 바탕으로 인증이 되지 않았을 때 요청했던 리소스를 전달해주는 방식이다.
RequestCache에는 클라이언트의 요청 정보를 저장하고, SavedRequest는 요청의 파라미터와 헤더를 저장한다.
- AuthenticationEntryPoint 호출 : 인증이 실패했을 때 어떻게 핸들링할지 결정한다.
- AccessDeniedException (인가 예외 처리)
- AccessDeniedHandler 호출 : 요청 리소스에 사용자 권한이 없는 경우, 예외 처리를 위한 핸들러이다.
출처
긴 글 읽어주셔서 감사합니다 🍀
잘못 작성된 내용은 피드백 주시면 반영하겠습니다 😎