
Briefing 프로젝트를 진행하면서 스프링 시큐리티와 관련된 요구사항들이 있었습니다.
그러나 Spring Security에 대한 이해도가 낮았기 때문에 필터 체인의 흐름, 내부적인 인증 과정에 대한 이해를 하기 위해
학습을 진행했고 이번 포스팅은 인증 과정에 대한 포스팅입니다.
인증에도 여러가지 인증이 존재합니다.
OAuth2에 대한 인증, form Login에 대한 인증 각각 모두 다른 형태의 인증을 거치게 됩니다.
이번 포스팅은 가장 쉬우면서도 인증 과정에 대한 흐름을 익히기 좋은
form Login에서의 인증 과정을 다룹니다.
🌐 Spring Security 인증의 과정

위의 이미지는 Spring Security의 인증 과정에 대한 과정을 나타낸 다이어그램입니다.
그러나 위의 이미지는 아래의 인증에 필요한 요소들이 무엇인지 우선 파악해야 그 순서가 비로소 이해가 됩니다.
위의 다이어그램은 필터체인을 거치는 중, 인증을 진행하는 필터 내부에서의 동작입니다.
만약 필터체인의 흐름이 궁금하시다면 아래의 포스팅을 참고해주세요!
Spring Security의 인증에는 아래의 8가지가 무엇인지 알아야 합니다.
- Security Context
- Authentication
- GrantedAuthority
- Authentication Manager
- Provider Manager
- Authentication Provider
- UserDetails
- UserDetailsService
Security Context
Security Context는 인증과 관련된 정보가 들어있는 Authentication의 객체를 감싸는 객체입니다.
그리고 Security Context를 Security Context Holder가 감싸게 되고
Context Holder가 제공하는 매서드를 통해 인증 정보를 조회하게 됩니다.
실제 Context Holder와 Security Context의 코드를 살펴보면 아래와 같습니다.
Security Context Holder

위의 매서드를 보면 Security Context를 리턴해주는 매서드가 존재합니다.
Security Context

코드를 살펴보면 Authentication을 리턴해주는 매서드가 존재합니다.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
위와 같은 코드를 통해 인증된 사용자의 정보가 담긴 Authentication을 획득할 수 있습니다.
Security Context Holder가 Security Context를 한번 더 감싼 이유는
Security Context를 어떻게 저장할지 Security Context Holder가 결정하기 때문입니다.
Security Context는 그 자체로 어떻게 저장이 되는지 알 필요가 없는 것이죠
그리고 Security Context를 통해 인증 정보를 불러올 때에도 어떻게 저장이 되었는지 몰라도 그저
Security Context Holder를 통해 가져오면 됩니다.
public static SecurityContext getContext() {
return strategy.getContext();
}
위의 Context Holder의 코드를 보면 strategy에서 Context를 가져오는 것을 확인 가능합니다.

위의 코드를 보면 Context Holder에 Security Context를 저장하는 전략이 여러가지인 것을 확인 가능합니다.
기본적으로는 MODE_THREADLOCAL을 사용하며 이는 시큐리티 컨텍스트를 스레드 로컬에 저장을 해서
서로 다른 요청을 처리하는 스레드가 서로 같은 시큐리티 컨텍스트를 사용하지 않도록 하고 있습니다.

그리고 위의 코드를 보면 실제로 MODE_THREADLOCAL인 경우 Security Context를 스레드 로컬을 통해 저장하고
이를 제공하는 것을 확인 가능합니다.
Authentication
Authentication은 Security Context에 담기는 인증정보를 나타내는 객체이며
Authentication은 크게 2가지 용도로 사용이 됩니다.
- 인증을 하기 위해 인증 대상이 되는 요청자의 정보를 담아서 전달 됨
- 인증이 완료가 된 경우 그 사용자의 정보를 담아서 전달 됨
실제 Authentication 객체를 사용해서 인증을 하는 과정은 마지막에 살펴보겠습니다.
인증 과정에서 Authentication은 내부적으로 아래 5개의 정보를 가지게 됩니다.
- principal : 사용자를 식별할 정보가 담깁니다.
- credentials : 사용자의 비밀번호가 주로 담깁니다.
- authorities : GrantedAuthority의 List로, 사용자의 권한입니다.
- details : 추가적인 인증과 관련된 정보로, 아이피 주소 등이 담깁니다.
- authenticated : boolean으로 인증이 되었는지를 나타냅니다.
인증 방식에 따라 위의 5개 정보가 필요할 수도 있고 누락이 되는 경우도 있습니다.
form Login의 경우는 위의 5개가 모두 사용이 되고 OAuth2의 경우는 credentials가 사용이 되지 않습니다.
GrantedAuthority
GrantedAuthority는 사용자가 가진 권한을 나타내며 ROLE_ADMIN, ROLE_USER와 같이 앞에
ROLE_이 붙은 형태로 권한이 표현이 됩니다. 이는 인가 처리에서 사용이 됩니다.
Authentication Manager
Authentication Manager는 Filter를 통해 실제 인증을 처리 하는 것을 나타내는 인터페이스입니다.
Filter는 Authentication Manager를 구현 한 클래스의 authenticate 매서드를 통해
인증을 수행하며 authenticate 매서드의 리턴 값은 인증 된 사용자의 정보가 담긴 Authentication 객체입니다.
return this.getAuthenticationManager().authenticate(authRequest);
위와 같이 authenticate 매서드를 통해 인증을 수행합니다.
Provider Manager
Provider Manager는 Authentication Manager의 대표적인 구현체 입니다.
Provider Manager는 Authentication Manager의 authenticate 매서드를 오버라이드 하며
내부적으로 Authentication Provider의 List를 가지며 현재 인증의 대상이 되는
Authentication에 대한 인증 처리가 가능한 Authentication Provider를 선택하여
실제 인증 처리를 위임합니다.

Authentication Provider
인증을 진행하며 사용자의 정보를 체크해서 인증이 되면 사용자의 정보를 Authentication 객체에 담아서 리턴합니다.
authenticate 매서드를 통해 인증을 수행합니다.

authenticate 매서드의 구현체도 여러가지가 존재하며
OAuth2의 경우는 OAuth2LoginAuthenticationProvider를 사용하며
form Login의 경우는 AbstractUserDetailsAuthenticationProvider와 하위 클래스인 DaoAuthenticationProvider를사용해서 인증을 수행합니다.
UserDetails
UserDetails는 Spring Security에서 사용자의 정보를 담는 객체이며
UserDetails 중 인증이 된 객체를 Security Context의 principal에 담게 됩니다.
대표적인 UserDetails의 구현체는User라고 하는 구현체 입니다.

User 구현체는 form Login시 사용이 됩니다.
UserDetailsService
UserDetailsService는 UserDetails를 로드하는 역할을 합니다.

🚀 form Login 시 인증의 흐름

이제 위의 그림을 통해 form Login 시 인증의 흐름을 짚어 보고
실제 디버깅 코드를 통해 살펴봅시다.
- Http Request가 Delegating Filter Proxy를 통해 해당 URI에 대한 Security Filter Chain의 Filter를 거친다.
- Filter 중 Authentication에 대한 처리를 하는 필터에 Http Request가 도달한다.
- form Login 을 활성화 했을 경우 UsernamePasswordAuthenticationFilter에 도달하게 된다.
- UsernamePasswordAuthenticationFilter는 Authentication Manager에게 Authentication 객체를 생성해서 전달한다. 이 과정에서 UsernamePasswordAuthenticationToken이라는 Authentication의 구현체를 생성한다.
- UsernamePasswordAuthenticationToken을 Authentication Manager의 구현체 중 하나인 ProviderManager에게 전달하고 인증을 위임한다.
- ProviderManager는 내부적으로 가지고 있는 AuthenticationProvider의 목록 중에서 form Login에 대한 인증이 가능한 AuthenticationProvider를 찾는다. 이 경우 DaoAuthenticationProvider가 선택이 된다.
- DaoAuthenticationProvider는 인증을 수행하기 위해 전달받은 Authentication 객체에서 사용자의 정보를 추출하여 해당 사용자에 대한 정보를 UserDetailsService를 통해 받아온다.
- form Login 과정이므로 username에 대한 password가 동일한지 체크하고 동일하면 Authentication 객체를 생성하여 인증 정보를 담고 리턴한다.
- ProviderManager는 Authentication 객체를 Filter로 리턴하고 UsernamePasswordAuthenticationFilter는 doFilterChain을 통해 다음 Filter를 호출하게 된다.
🚀실제 코드에서의 흐름

Briefing에서는 Swagger 접근 시 form Login을 하도록 했습니다.
위의 설정에 따라 Filter Chain에 UsernamePasswordAuthenticationFilter 이 추가가 됩니다.
UsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter의 하위 클래스로
AbstractAuthenticationProcessingFilter의 attemptAuthentication를 오버라이드 했습니다.
처음 인증되지 않은 사용자가 도달하게 되면 기본적으로 /login 으로 이동을 시키고
POST /login 요청에 대해서 form Login 인증을 수행합니다.
UsernamePasswordAuthenticationFilter까지 요청이 도달하는 과정은 아래의 글을 참고해주세요
이제 인증이 되는 과정을 살펴봅시다!
form Login 을 활성화 했을 경우 UsernamePasswordAuthenticationFilter에 도달하게 된다.

UsernamePasswordAuthenticationFilter의 상위 클래스인 AbstractAuthenticationProcessingFilter에서
attemptAuthentication을 호출하게 되고 이를 통해 UsernamePasswordAuthenticationFilter에 도달합니다.
UsernamePasswordAuthenticationFilter는 Authentication Manager에게 Authentication 객체를 생성해서 전달한다.
이 과정에서 UsernamePasswordAuthenticationToken이라는 Authentication의 구현체를 생성한다.

Http request에서 username과 password를 추출하여 UsernamePasswordAuthenticationToken라는 Authentication의 구현체를 생성하고 이를 AuthenticationManger로 넘기고 있습니다.
ProviderManager는 내부적으로 가지고 있는 AuthenticationProvider의 목록 중에서 form Login에 대한 인증이 가능한 AuthenticationProvider를 찾는다. 이 경우 DaoAuthenticationProvider가 선택이 된다.

DaoAuthenticationProvider는 인증을 수행하기 위해 전달받은 Authentication 객체에서 사용자의 정보를 추출하여
해당 사용자에 대한 정보를 UserDetailsService를 통해 받아온다.

위의 코드를 보면 retrieveUser 매서드를 호출합니다.

retrieveUser 매서드 내부에서 getUserDetailsService를 통해 UserDetailsService를 추출하고
loadUserByUsername을 통해 UserDetails를 리턴 받고 이를 리턴합니다.
form Login 과정이므로 username에 대한 password가 동일한지 체크하고 동일하면
Authentication 객체를 생성하여 인증 정보를 담고 리턴한다.

위와 같은 일련의 과정을 통해
인증이 된 정보가 새로운 Authentication에 담기게 되고
이 Authentication 객체가 Security Context에 담기고 최종적으로 세션에 담기게 됩니다.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] Filter 흐름, 예외 처리 흐름 파고들기 (0) | 2023.12.31 |
---|

Briefing 프로젝트를 진행하면서 스프링 시큐리티와 관련된 요구사항들이 있었습니다.
그러나 Spring Security에 대한 이해도가 낮았기 때문에 필터 체인의 흐름, 내부적인 인증 과정에 대한 이해를 하기 위해
학습을 진행했고 이번 포스팅은 인증 과정에 대한 포스팅입니다.
인증에도 여러가지 인증이 존재합니다.
OAuth2에 대한 인증, form Login에 대한 인증 각각 모두 다른 형태의 인증을 거치게 됩니다.
이번 포스팅은 가장 쉬우면서도 인증 과정에 대한 흐름을 익히기 좋은
form Login에서의 인증 과정을 다룹니다.
🌐 Spring Security 인증의 과정

위의 이미지는 Spring Security의 인증 과정에 대한 과정을 나타낸 다이어그램입니다.
그러나 위의 이미지는 아래의 인증에 필요한 요소들이 무엇인지 우선 파악해야 그 순서가 비로소 이해가 됩니다.
위의 다이어그램은 필터체인을 거치는 중, 인증을 진행하는 필터 내부에서의 동작입니다.
만약 필터체인의 흐름이 궁금하시다면 아래의 포스팅을 참고해주세요!
Spring Security의 인증에는 아래의 8가지가 무엇인지 알아야 합니다.
- Security Context
- Authentication
- GrantedAuthority
- Authentication Manager
- Provider Manager
- Authentication Provider
- UserDetails
- UserDetailsService
Security Context
Security Context는 인증과 관련된 정보가 들어있는 Authentication의 객체를 감싸는 객체입니다.
그리고 Security Context를 Security Context Holder가 감싸게 되고
Context Holder가 제공하는 매서드를 통해 인증 정보를 조회하게 됩니다.
실제 Context Holder와 Security Context의 코드를 살펴보면 아래와 같습니다.
Security Context Holder

위의 매서드를 보면 Security Context를 리턴해주는 매서드가 존재합니다.
Security Context

코드를 살펴보면 Authentication을 리턴해주는 매서드가 존재합니다.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
위와 같은 코드를 통해 인증된 사용자의 정보가 담긴 Authentication을 획득할 수 있습니다.
Security Context Holder가 Security Context를 한번 더 감싼 이유는
Security Context를 어떻게 저장할지 Security Context Holder가 결정하기 때문입니다.
Security Context는 그 자체로 어떻게 저장이 되는지 알 필요가 없는 것이죠
그리고 Security Context를 통해 인증 정보를 불러올 때에도 어떻게 저장이 되었는지 몰라도 그저
Security Context Holder를 통해 가져오면 됩니다.
public static SecurityContext getContext() {
return strategy.getContext();
}
위의 Context Holder의 코드를 보면 strategy에서 Context를 가져오는 것을 확인 가능합니다.

위의 코드를 보면 Context Holder에 Security Context를 저장하는 전략이 여러가지인 것을 확인 가능합니다.
기본적으로는 MODE_THREADLOCAL을 사용하며 이는 시큐리티 컨텍스트를 스레드 로컬에 저장을 해서
서로 다른 요청을 처리하는 스레드가 서로 같은 시큐리티 컨텍스트를 사용하지 않도록 하고 있습니다.

그리고 위의 코드를 보면 실제로 MODE_THREADLOCAL인 경우 Security Context를 스레드 로컬을 통해 저장하고
이를 제공하는 것을 확인 가능합니다.
Authentication
Authentication은 Security Context에 담기는 인증정보를 나타내는 객체이며
Authentication은 크게 2가지 용도로 사용이 됩니다.
- 인증을 하기 위해 인증 대상이 되는 요청자의 정보를 담아서 전달 됨
- 인증이 완료가 된 경우 그 사용자의 정보를 담아서 전달 됨
실제 Authentication 객체를 사용해서 인증을 하는 과정은 마지막에 살펴보겠습니다.
인증 과정에서 Authentication은 내부적으로 아래 5개의 정보를 가지게 됩니다.
- principal : 사용자를 식별할 정보가 담깁니다.
- credentials : 사용자의 비밀번호가 주로 담깁니다.
- authorities : GrantedAuthority의 List로, 사용자의 권한입니다.
- details : 추가적인 인증과 관련된 정보로, 아이피 주소 등이 담깁니다.
- authenticated : boolean으로 인증이 되었는지를 나타냅니다.
인증 방식에 따라 위의 5개 정보가 필요할 수도 있고 누락이 되는 경우도 있습니다.
form Login의 경우는 위의 5개가 모두 사용이 되고 OAuth2의 경우는 credentials가 사용이 되지 않습니다.
GrantedAuthority
GrantedAuthority는 사용자가 가진 권한을 나타내며 ROLE_ADMIN, ROLE_USER와 같이 앞에
ROLE_이 붙은 형태로 권한이 표현이 됩니다. 이는 인가 처리에서 사용이 됩니다.
Authentication Manager
Authentication Manager는 Filter를 통해 실제 인증을 처리 하는 것을 나타내는 인터페이스입니다.
Filter는 Authentication Manager를 구현 한 클래스의 authenticate 매서드를 통해
인증을 수행하며 authenticate 매서드의 리턴 값은 인증 된 사용자의 정보가 담긴 Authentication 객체입니다.
return this.getAuthenticationManager().authenticate(authRequest);
위와 같이 authenticate 매서드를 통해 인증을 수행합니다.
Provider Manager
Provider Manager는 Authentication Manager의 대표적인 구현체 입니다.
Provider Manager는 Authentication Manager의 authenticate 매서드를 오버라이드 하며
내부적으로 Authentication Provider의 List를 가지며 현재 인증의 대상이 되는
Authentication에 대한 인증 처리가 가능한 Authentication Provider를 선택하여
실제 인증 처리를 위임합니다.

Authentication Provider
인증을 진행하며 사용자의 정보를 체크해서 인증이 되면 사용자의 정보를 Authentication 객체에 담아서 리턴합니다.
authenticate 매서드를 통해 인증을 수행합니다.

authenticate 매서드의 구현체도 여러가지가 존재하며
OAuth2의 경우는 OAuth2LoginAuthenticationProvider를 사용하며
form Login의 경우는 AbstractUserDetailsAuthenticationProvider와 하위 클래스인 DaoAuthenticationProvider를사용해서 인증을 수행합니다.
UserDetails
UserDetails는 Spring Security에서 사용자의 정보를 담는 객체이며
UserDetails 중 인증이 된 객체를 Security Context의 principal에 담게 됩니다.
대표적인 UserDetails의 구현체는User라고 하는 구현체 입니다.

User 구현체는 form Login시 사용이 됩니다.
UserDetailsService
UserDetailsService는 UserDetails를 로드하는 역할을 합니다.

🚀 form Login 시 인증의 흐름

이제 위의 그림을 통해 form Login 시 인증의 흐름을 짚어 보고
실제 디버깅 코드를 통해 살펴봅시다.
- Http Request가 Delegating Filter Proxy를 통해 해당 URI에 대한 Security Filter Chain의 Filter를 거친다.
- Filter 중 Authentication에 대한 처리를 하는 필터에 Http Request가 도달한다.
- form Login 을 활성화 했을 경우 UsernamePasswordAuthenticationFilter에 도달하게 된다.
- UsernamePasswordAuthenticationFilter는 Authentication Manager에게 Authentication 객체를 생성해서 전달한다. 이 과정에서 UsernamePasswordAuthenticationToken이라는 Authentication의 구현체를 생성한다.
- UsernamePasswordAuthenticationToken을 Authentication Manager의 구현체 중 하나인 ProviderManager에게 전달하고 인증을 위임한다.
- ProviderManager는 내부적으로 가지고 있는 AuthenticationProvider의 목록 중에서 form Login에 대한 인증이 가능한 AuthenticationProvider를 찾는다. 이 경우 DaoAuthenticationProvider가 선택이 된다.
- DaoAuthenticationProvider는 인증을 수행하기 위해 전달받은 Authentication 객체에서 사용자의 정보를 추출하여 해당 사용자에 대한 정보를 UserDetailsService를 통해 받아온다.
- form Login 과정이므로 username에 대한 password가 동일한지 체크하고 동일하면 Authentication 객체를 생성하여 인증 정보를 담고 리턴한다.
- ProviderManager는 Authentication 객체를 Filter로 리턴하고 UsernamePasswordAuthenticationFilter는 doFilterChain을 통해 다음 Filter를 호출하게 된다.
🚀실제 코드에서의 흐름

Briefing에서는 Swagger 접근 시 form Login을 하도록 했습니다.
위의 설정에 따라 Filter Chain에 UsernamePasswordAuthenticationFilter 이 추가가 됩니다.
UsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter의 하위 클래스로
AbstractAuthenticationProcessingFilter의 attemptAuthentication를 오버라이드 했습니다.
처음 인증되지 않은 사용자가 도달하게 되면 기본적으로 /login 으로 이동을 시키고
POST /login 요청에 대해서 form Login 인증을 수행합니다.
UsernamePasswordAuthenticationFilter까지 요청이 도달하는 과정은 아래의 글을 참고해주세요
이제 인증이 되는 과정을 살펴봅시다!
form Login 을 활성화 했을 경우 UsernamePasswordAuthenticationFilter에 도달하게 된다.

UsernamePasswordAuthenticationFilter의 상위 클래스인 AbstractAuthenticationProcessingFilter에서
attemptAuthentication을 호출하게 되고 이를 통해 UsernamePasswordAuthenticationFilter에 도달합니다.
UsernamePasswordAuthenticationFilter는 Authentication Manager에게 Authentication 객체를 생성해서 전달한다.
이 과정에서 UsernamePasswordAuthenticationToken이라는 Authentication의 구현체를 생성한다.

Http request에서 username과 password를 추출하여 UsernamePasswordAuthenticationToken라는 Authentication의 구현체를 생성하고 이를 AuthenticationManger로 넘기고 있습니다.
ProviderManager는 내부적으로 가지고 있는 AuthenticationProvider의 목록 중에서 form Login에 대한 인증이 가능한 AuthenticationProvider를 찾는다. 이 경우 DaoAuthenticationProvider가 선택이 된다.

DaoAuthenticationProvider는 인증을 수행하기 위해 전달받은 Authentication 객체에서 사용자의 정보를 추출하여
해당 사용자에 대한 정보를 UserDetailsService를 통해 받아온다.

위의 코드를 보면 retrieveUser 매서드를 호출합니다.

retrieveUser 매서드 내부에서 getUserDetailsService를 통해 UserDetailsService를 추출하고
loadUserByUsername을 통해 UserDetails를 리턴 받고 이를 리턴합니다.
form Login 과정이므로 username에 대한 password가 동일한지 체크하고 동일하면
Authentication 객체를 생성하여 인증 정보를 담고 리턴한다.

위와 같은 일련의 과정을 통해
인증이 된 정보가 새로운 Authentication에 담기게 되고
이 Authentication 객체가 Security Context에 담기고 최종적으로 세션에 담기게 됩니다.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] Filter 흐름, 예외 처리 흐름 파고들기 (0) | 2023.12.31 |
---|