GDSC 활동을 하면서 여러 백엔드 분들이 시큐리티에 대해 많이 어려워하시는 것 같아 정리한 내용들을 공유해보려고 합니다.
스프링 시큐리티란 ?
스프링 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다.
스프링 시큐리티에 대해 정리하기 전에 헷갈릴 수 있는 단어들을 먼저 알고 가자.
보안 용어 정리
- 인증(Authenticate) : 유저가 누구인지 확인하는 절차
- 인가(Authorization) : 인증된 사용자에 대해 권한을 확인하고 허락하는 것
- 접근 주체(Principal) : 보호된 대상에 접근하는 유저
- 비밀번호(Credential) : 대상에 접근하는 유저의 비밀번호
스프링 시큐리티 특징과 구조
- 보안과 관련하여 체계적으로 많은 옵션을 제공하여 편리하게 사용할 수 있음
- Filter 기반으로 동작하여 MVC와 분리하여 관리 및 동작
- 어노테이션을 통한 간단한 설정
- Spring Security는 기본적으로 세션 & 쿠키 방식으로 인증
- 인증관리자(Authentication Manager)와 접근 결정 관리자(Access Decision Manager)를 통해 사용자의 리소스 접근을 관리
- 인증 관리자는 UserNamePasswordAuthenticationFilter, 접근 관리자는 FilterSecurityInterceptor가 수행
동작 원리
- 사용자가 Form을 통해 로그인 정보를 입력하고 인증 요청을 보낸다.
- AuthenticationFilter(사용할 구현체 UsernamePasswordAuthenticationFilter)가 HttpServletRequest에서 사용자가 보낸 아이디와 패스워드를 인터셉트한다.
프론트 단에서 유효성검사를 할 수도 있지만, 안전을 위해서 다시 한번 사용자가 보낸 아이디와 패스워드의 유효성 검사를 한다.
HttpServletRequest에서 꺼내온 사용자 아이디와 패스워드를 진짜 인증을 담당할 AuthenticationManager 인터페이스(구현체 - ProviderManager)에게 인증용 객체(UsernamePasswordAuthenticationToken)로 만들어줘서 위임한다. - AuthenticationFilter에게 인증용 객체(UsernamePasswordAuthenticationToken)을 전달받는다.
- 실제 인증을 할 AuthenticationProvider에게 Authentication객체를 다시 전달한다.
- DB에서 사용자 인증 정보를 가져올 UserDetailsService 객체에게 사용자 아이디를 넘겨주고 DB에서 인증에 사용할 사용자 정보(사용자 아이디, 암호화된 패스워드, 권한 등)를 UserDetails(인증용 객체와 도메인 객체를 분리하지 않기 위해서 실제 사용되는 도메인 객체에 UserDetails를 상속하기도 한다.)라는 객체로 전달 받는다.
- AuthenticationProvider는 UserDetails 객체를 전달 받은 이후 실제 사용자의 입력정보와 UserDetails 객체를 가지고 인증을 시도한다.
- 인증이 완료되면 사용자 정보를 가진 Authentication 객체를 SecurityContextHolder에 담은 이후 AuthenticationSuccessHandle를 실행한다. (실패시 AuthenticationFailureHandler를 실행)
스프링 시큐리티 아키텍쳐
필터(Filter)
스프링 시큐리티는 서블릿의 필터를 기반으로 동작한다.
일반적으로 클라이언트에서 서버로 요청을 보내면, DispatcherServlet이라는 서블릿이 하나의 HttpServeletRequest를 받아서 요청을 처리하고 응답을 클라이언트로 보낸다.
그런데, 하나 이상의 필터가 포함된다면, 클라이언트에서 보낸 요청이 서블릿으로 전달되기 전에 필터를 거치게 된다.
클라이언트가 애플리케이션에 하나의 요청을 보내면, 컨테이너는 하나의 필터 체인(FilterChain)을 생성한다. 필터 체인에는 필터와 서블릿이 들어있다.
필터는 말그대로 ‘필터’의 역할을 한다. 요청이 다음 필터나 서블릿에 전달되지 않도록 하는 역할이다. 필터에서 요청을 걸러냈을 경우, 필터 내부에서 HttpServletResponse를 만들어 서블릿 대신 응답을 보낼 수 있다.
DelegatingFilterProxy
사용자의 요청이 서블릿에 전달되어 자원에 접근하기 전에, 스프링 시큐리티는 필터의 생명주기를 이용해서 인증과 권한 작업을 수행한다. 서블릿 필터의 생명주기 시점에서 인증과 권한 작업을 수행해야 하는데, 서블릿 컨테이너에서는 스프링 컨테이너에 등록된 빈을 인식할 수 없다.
따라서 스프링 시큐리티에서는 DelegatingFilterProxy라는 서블릿 필터의 구현체를 제공한다.
DelegatingFilterProxy는 서블릿 매커니즘을 통해 서블릿의 필터로 등록될 수 있으며 스프링에 등록된 빈을 가져와 의존성을 주입할 수 있다. 이렇게 DelegatingFilterProxy는 서블릿 컨테이너의 생명주기와 스프링의 ApplicationContext 사이를 연결하는 다리 역할을 한다.
FilterChainProxy
DelegatingFilterProxy를 통해 받은 요청과 응답을 스프링 시큐리티 필터 체인에 전달하고 작업을 위임하는 역할을 한다.
중간에 FilterChainProxy를 두는 이유는 서블릿을 지원하는 시작점 역할을 하기 위해서이다. 만약, 서블릿에서 문제가 발생한다면 FilterChainProxy의 문제라는 것을 바로 알 수 있게된다.
SecurityFilterChain
인증을 처리하는 여러 개의 시큐리티 필터를 담는 필터 체인이다. 또, 어떤 시큐리티 필터를 통해 인증을 수행할지 결정하는 역할을 한다.
따라서 여러 개의 SecurityFilterChain을 구성하여 매칭되는 URL에 따라 다른 SecurityFilterChain이 사용되도록 할 수 있다.
Security Filters
시큐리티 필터는 요청을 스프링 시큐리티 매커니즘에 따라 처리하는 필터이다. 이 필터는 시큐리티의 핵심 기능을 수행하는 지점이다. SecurityFilterChain API를 통해 FilterChainProxy에 삽입되고 스프링 빈으로 등록된다.
시큐리티 필터들에는 순서가 존재하는데 자세한 내용은 공식문서를 참고하자.
docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-security-filters
Reference
- [SpringBoot] Spring Security란?
- spring security 파헤치기 (구조, 인증과정, 설정, 핸들러 및 암호화 예제, @Secured, @AuthenticationPrincipal, taglib)
- Spring Security Architecture 내용정리
- 인증(Authentication)과 인가(Authorization)