Spring Framwork 기반 Application에서 Spring Security를 적용하여 로그인 기능을 구현하곤 합니다. 기능 구현 방식에 있어, 전통적인 방식인 id, pw 기반의 세션 인증 방식이 있습니다.
오늘은 이 방식에 대해 상세히 정리해 보도록 하겠습니다. 이 포스팅은 Jwt, OAuth 기반의 인증에 대한 내용과는 별개의 내용이므로 참고 부탁드립니다.
우선 Spring Security의 개념과 관련 용어들에 대해 간단히 설명드리도록 하겠습니다.
Spring Security
- Spring Security는 스프링 기반 애플리케이션의 보안을 담당하는 스프링 하위 프레임워크입니다.
- 보안과 관련해서 체계적으로 많은 옵션들을 제공해 주기 때문에 개발자의 입장에서는 보안 관련 로직을 직접 작성하지 않아도 됩니다.
관련 용어
- Authentication
- 해당 사용자가 본인이 맞는지 확인하는 절차
- Authorization
- 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차
- Principal
- 보호받는 Resource에 접근하는 대상
- Credential
- Resource에 접근하는 대상의 비밀번호
- 권한
- 인증된 주체가 애플리케이션의 동작을 수행할 수 있도록 허락되어 있는지를 결정
- 인증 과정을 통해 주체가 증명된 이후 권한 부여
- SecurityContextHolder
- SecurityContext 객체를 저장하고 있는 wrapper 클래스
- 보안 주체의 세부 정보를 포함하여 응용프로그램의 현재 보안 컨텍스트에 대한 세부 정보 저장
- SecurityContextHolder class의 ThreadLocal 전략
- MODE_THREADLOCAL: default 설정 값, Thread당 SecurityContext 객체 할당
- MODE_INHERITABLETHREADLOCAL: 하위로 생성된 Thread에서 동일한 SecurityContext 객체 공유
- MODE_GLOBAL: Application의 모든 Thread가 단 하나의 SecurityContext만을 공유
- SecurityContext
- Authentication을 보관하는 역할
- ThreadLocal에 저장되어 동일 스레드인 경우, 아무 곳에서나 참조가 가능합니다
- ThreadLocal
- 각각의 Thread별로 별도의 저장공간을 제공하는 컨테이너
- 하나의 Thread에서 실행되는 코드가 동일한 객체를 사용할 수 있도록 해주며, Thread와 관련된 코드에서 파라미터를 사용하지 않고, 객체를 전파하기 위한 용도로 사용됩니다.
- ThreadLocal
- 즉, SecurityContext는 ThreadLocal에 저장되어 있으면서, 동시에 HttpSession에도 저장되어 있습니다.
- SecurityFilterChain
- Spring Security는 표준 서블릿 필터를 기반으로 동작
- SpringBoot의 기본 설정을 사용하는 경우, 인증에 사용되는 Filter들이 모여있는 SecurityFilterChain을 자동으로 등록해 주는데 SecurityFilterChain을 통해 스프링 시큐리티의 인증 과정이 동작하게 됩니다.
- 이때 FilterChainProxy라는 클래스가 등장하는데, 해당 클래스는 DelegatingFilterProxy로부터 Filter 작동에 대한 요청을 위임받아 실제로 인증 처리를 하는 클래스입니다.
- Spring Security를 사용하면 모든 요청은 FilterChainProxy 클래스를 거치게 되며, 내부에 getFilters() 메서드를 통해 SecurityFilterChain의 Filter 목록을 가져오게 됩니다.
- SecurityContextPersistenceFilter
- SecurityContext 객체의 생성, 조회, 저장 등의 LifeCycle을 담당하는 필터입니다.
- 실제로는 SecurityContextRepository interface의 구현체인 HttpSessionSecurityContextRepository가 구현체로 사용되고 있습니다.
관련 용어와 SecurityContextHolder의 ThreadLocal 전략까지 알아보았으니, Spring Security 동작 흐름을 순차적으로 설명드리겠습니다.
- Client가 username, password 기반으로 HTTP 요청을 보냅니다
- Client로부터 HTTP 요청이 오면, 인증 및 권한 부여의 목적으로 일련의 필터를 거치게 됩니다. (Application Filter -> Authentication Filter..)
- AuthentcationFilter를 상속받은 UsernamePasswordAuthenticationFilter의 attemptAuthentication 메서드 내부에서 UsernamePasswordAuthenticationToken을 생성합니다
- 이후, AuthenticationManager interface의 구현체인 ProviderManager에게 UsernamePasswordAuthenticationToken 객체를 넘깁니다.
- ProviderManager는 AuthenticationProvider를 순회하면서 UsernamePasswordAuthenticationToken을 처리해 줄 AuthenticationProvider를 찾습니다.
- AuthenticationProvider interface의 구현체인 CustomAuthenticationProvider에서 인증 로직을 수행합니다.
- UserDetailsSerivce interface를 구현한 CustomUserDetailsService의 loadUserByUsername 메서드를 호출하여 세부 정보를 load합니다.
- 근본적으로 DB에 있는 사용자 정보와 인증용 객체에 담긴 정보를 비교합니다.
- 사용자 이름을 기반으로 UserDetails 객체를 반환하며, 이 객체는 인증 절차에서 사용됩니다.
- 인증절차를 통해 인증이 완료되면 Authentication 객체를 SecurityContextHolder 객체 안에 SecurityContext에 저장합니다.