항해 플러스 6기 프레임워크 사전 스터디에서 개별적으로 진행하는 API를 개발하고 있다.
실행 환경
SpringBoot 3.3.x
Java 21
Member가 로그인(SignIn)할 때 Spring Security를 사용해서 인증을 해보기로 했다.
우선 Spring Security가 간단하게 무엇인지 알아보고, 아키텍처로 동작 과정을 살펴본 후- 로그인 인증 구조도 아키텍처로 동작 과정 살펴보자.
그 다음에는 구현을 해보고, 시행착오 및 해결에 대해 써보자.
Spring Security Login Authentication Architecture
이전 글의 마지막 그림인 Spring Security Full Architecture flow에 연결 지어서 보자.
위 그림처럼 Authentication은 Filter에 의해서 진행되고, DelegatingFilterProxy로 등록된 FilterChainProxy Bean을 이용한 SecurityFilterChain으로 진행되게 된다.
AbstractAuthenticationProcessingFilter
SecurityFilterChain 안에는 Spirng Security에서 제공하는 여러가지 Filter들이 있다.
이때 Authentication을 담당하는 Filter는 AbstractAuthenticationProcessingFilter이다.
이 AbstractAuthenticationProcessingFilter는 추상 클래스로 로그인에 필요한 공통적인 로직을 가지고 있다.
AbstractAuthenticationProcessingFilter는 사용자의 자격 증명을 인증(로그인)하기 위한 기본 Filter로 사용된다.
자격 증명이 인증되기 전에 Spring Security는 일반적으로 AuthenticationEntryPoint를 사용하여 자격 증명을 요청한다.
이후 AbstractAuthenticationProcessingFilter는 해당 Filter에 제출된 모든 인증 요청을 처리하고, 이를 통해 인증을 수행할 수 있다.
아래 그림은 AbstractAuthenticationProcessingFilter가 동작하는 과정이다.
- 사용자가 자격 증명을 제출하면,
- AbstractAuthenticationProcessingFilter는 이를 인증하기 위해 HttpServletRequest에서 Authentication 객체를 생성한다.
- 생성되는 Authentication 타입은 AbstractAuthenticationProcessingFilter의 하위 클래스에 따라 다르다.
- 예를 들어 UsernamePasswordAuthenticationFilter는 HttpServletRequest에서 제출된 username과 password를 사용해 UsernamePasswordAuthenticationToken을 생성한다.
- 생성된 Authentication 객체는 인증을 위해 AuthenticationManager에 전달된다.
- 인증에 실패하면 아래 절차가 진행된다.
- SecurityContextHolder가 비워진다.
- RememberMeServices.loginFail 메서드가 호출된다.
- AuthenticationFailureHanlder가 호출된다.
- 인증에 성공하면 아래 절차가 진행된다.
- SessionAuthenticationStrategy에게 새로운 로그인이 발생했음을 알린다.
- Authentication 객체가 SecurityContextHolder에 설정된다.
- RememberMeServices.loginSuccess 메서드가 호출된다.
- ApplicationEventPublisher가 InteractiveAuthenticationSuccessEvent를 게시한다.
- AuthenticationSuccessHandler가 호출된다.
이를 토대로 username과 password를 form으로 전달해서 로그인하는 UsernamePasswordAuthenticationFilter를 가지고 로그인 과정을 살펴보자.
이 UsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter를 상속받은 클래스이고, SecurityFilterChain 안에 들어있다.
UsernamePasswordAuthenticationFilter
username과 password를 UsernamePasswordAuthenticationFilter가 인증한다.
이 UsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter를 상속 받은 클래스이다.
아래 그림을 보자.
- 사용자가 username과 password를 제출하면,
UsernamePasswordAuthenticationFilter는 HttpServletRequest 인스턴스에서 username과 password를 추출하여
UsernamePasswordAuthenticationToken이라는 Authentication 객체를 생성한다. - UsernamePasswordAuthenticationToken이 AuthenticationManager 인스턴스에 전달되어 인증된다.
AuthenticationManager의 구체적인 모습은 사용자 정보가 어떻게 저장되었는지에 따라 달라진다. - 인증에 실패한 경우
- SecurityContextHolder가 비워진다.
- RememberMeServices.loginFail 메서드가 호출된다.
- AuthenticationFailurehandler가 호출된다.
- 인증에 성공한 경우
- SessionAuthenticationStrategy에게 새로운 로그인이 발생했음을 알린다
- Authentication 객체가 SecurityContextHolder에 설정된다
- RememberMeServices.loginSuccess 메서드가 호출된다.
- ApplicationEventPublisher가 InteractiveAuthenticationSuccessEvent를 게시한다.
- AuthenticationSuccessHandler가 호출된다.
일반적으로 이는 SimpleUrlAuthenticationSuccessHandler로, 로그인 페이지로 redirect 될 때 ExceptionTranslationFilter에 의해 저장된 요청으로 redirect 한다.
Spring Security Login Authentication Full Architecture
이제 로그인 인증 전체 과정이 구체적으로 어떻게 진행되는지 아래 그림과 함께 살펴보자.
- Http Request 수신
- Spring Security는 일련의 FilterChain을 가지고 있다.
- 따라서 요청이 들어오면, Authentication/Authorization을 위해 이 FilterChain을 거치게 된다.
- 사용자가 인증 요청을 보낼 때도 마찬가지로, 해당 요청은 FilterChain을 통과하며, 사용 중인 인증 메커니즘/모델에 따라 관련된 인증 Filter를 찾을 때까지 진행된다.
- 예를 들어
- Http Basic Authentication request는 FilterChain을 통과하여 BasicAuthenticationFilter에 도달할 때까지 진행된다.
- Http Digest Authentication request는 FilterChain을 통과하여 DigestAuthenticationFilter에 도달할 때까지 진행된다.
- Login form Authentication request는 FilterChain을 통과하여 UsernamePasswordAuthenticationFilter에 도달할 때까지 진행된다.
- 사용자 credentials(자격 증명) 기반으로 AuthenticationToken 생성
- 관련된 AuthenticationFilter가 Authentication request를 받으면, request에서 username과 password를 추출하고, 추출된 사용자 자격 증명을 기반으로 Authentication 객체를 생성한다.
- 추출된 username과 password를 사용하여 UsernamePasswordAuthenticationToken이 생성된다.
- 생성된 AuthenticationToken을 AuthenticationManager에 위임
- UsernamePasswordAuthenticationToken 객체가 생성된 후, 이 객체는 AuthenticationManager의 authentication 메서드를 호출하는 데 사용된다.
- AuthenticationManager는 인터페이스이고, 실제 구현은 ProviderManager가 담당한다.
- ProviderManager에는 사용자 요청을 인증하기 위해 사용해야 하는 AuthenticationProvider 목록이 구성되어 있다.
- ProviderManager는 제공된 각 AuthenticationProvider를 순차적으로 확인하고, 전달된 UsernamePasswordAuthenticationToken같은 Authentication 객체를 기반으로 사용자를 인증하려고 시도한다.
- AuthenticationProvider 목록을 사용하여 인증 시도
- AuthenticationProvider는 제공된 Authentication 객체를 사용하여 사용자를 인증하려고 시도한다.
- UserDetailsServicee 필요 여부
- 일부 AuthenticationProvider는 username을 기반으로 사용자 세부 정보를 조회하기 위해 UserDetailsService를 사용할 수 있다.
- 예를 들어 DaoAuthenticationProvider는 username에 해당하는 사용자 정보를 얻기 위해 UserDetailsService를 사용한다.
- UserDetails 또는 User 객체 조회
- UserDetailsService는 username을 기반으로 UserDetails를 조회한다.
- UserDetails의 실제 구현체는 User 객체이다.
- 6번과 동일
- Authentication 객체 또는 AuthenticationException 반환
- AuthenticationProvider 인터페이스에 따르면,
사용자가 성공적으로 인증되면, 완전히 채워진 Authentication 객체가 반환되고,
그렇지 않으면 AuthenticationException이 발생한다. - 완전히 채워진 Authentication 객체의 상태는 아래와 같다.
- authenticated : true
- grant authorities list : 사용자의 권한 목록
- user credentials : username만 포함
- AuthenticationException이 발생하면, 인증 메커니즘을 지원하는 AuthenticationEntryPoint에 의해 처리된다.
- AuthenticationProvider 인터페이스에 따르면,
- Authentication 완료
- AuthenticationManager는 인증이 완료된 후, 얻어진 완전히 채워진 Authentication 객체를 관련된 Authentication Filter에 반환한다.
- SecurityContext에 Authentication 객체 설정
- 관련 AuthenticationFilter는 얻어진 Authentication 객체를 SecurityContext에 저장하여 향후 Filter에서 사용할 수 있도록 한다. 이는 주로 Authorization Filter에서 사용된다.
SecurityContextHolder
위 과정 중 10번에 대해 좀 더 알아보자.
Spring Security Authentication 모델의 핵심은 SecurityContextHolder이다.
SecurityContextHolder는 Spring Security가 인증된 사용자에 대한 세부 정보를 저장하는 곳이다.
이 SecurityContextHolder는 SecurityContext를 포함하고 있다.
즉, SecurityContext는 SecurityContextHolder에서 얻을 수 있고, SecurityContext는 Authentication 객체를 포함하고 있다.
Authentication
Authentication 인터페이스는 Spring Security 내에서 두 가지 주요 용도로 사용된다.
- 인증 과정에서 사용자 정보 제공
- Authentication 인터페이스는 인증 과정에서 사용자 정보를 제공한다. 이 정보는 인증된 사용자의 username, password, authorities 등을 포함한다.
- Spring Security는 인증을 수행하기 위해 Authentication 객체를 사용하며, 이 객체는 주로 AuthenticationManager를 통해 생성된다.
- 사용자 인증 정보 저장
- Authentication 인터페이스는 사용자의 인증 정보를 저장하는 역할을 한다. 이 인터페이스의 구현체는 username, password, autorities를 포함한다.
- Spring Security는 사용자가 로그인할 때 이 객체를 생성하여 SecurityContext에 저장하고, 이후 요청 처리 중에 이를 참조하여 사용자 인증 상태와 권한을 확인한다.
Authentication 객체는 아래 구성요소로 이루어져 있다.
- principal
- 사용자를 식별한다.
- username/password로 인증할 때는 종종 UserDetails의 인스턴스다.
- credentials
- 종종 password를 포함한다.
- 사용자가 인증된 후, 보안을 위해 자주 지워진다.
- authorities
- GrantedAuthority 인스턴스들로, 사용자가 부여받은 높은 수준의 권한을 나타낸다.
도움 받은 곳
https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html
https://chathurangat.wordpress.com/2017/08/23/spring-security-authentication-architecture/#more-54