Spring Security의 흐름
이번 포스트는 스프링 시큐리티의 전체적인 인증 처리의 흐름과 인가 처리의 흐름에 대해 작성할 것이다.
이전까지는 JWT 위주로 토큰 증명 방식이 어떤 개념인지를 자세히 알아보았다.
스프링 시큐리티의 흐름은 이전 블로그의 내용을 위주로 작성한다.
그렇기 때문에 이전 블로그의 내용을 이사할 때 이 흐름에 대한 부분은 옮기지 않을 예정이다.
1. Spring Security의 인증 처리 흐름
위 그림은 사용자가 로그인 인증을 위해 요청을 전송할 경우, 스프링 시큐리티에서 해당 인증 요청을 어떻게 처리하는 지를 나타낸 플로우 차트이다.
일반적인 ID/PW를 이용한 로그인 인증 방식의 흐름을 나타낸 그림이다.
각 컴포넌트에 대한 설명은 다음과 같다.
(1). 사용자가 로그인 폼 등을 이용해 Username과 PW를 포함한 요청을 스프링 시큐리티가 적용된 애플리케이션에 전송한다.
이 요청이 Filter Chain까지 도달하게되면 여러 Filter 중, UsernamePasswordAuthentication이 해당 요청을 전달 받는다.
이때부터 스프링 시큐리티에서의 실질적인 인증 처리가 시작된다.
(2). 사용자의 로그인 요청을 전달 받은 UsernamePasswordAuthenticationFilter는 Username과 Password를 사용해 UsernamePasswordAuthenticationToken을 생성한다.
여기서 UsernamePasswordAuthenticationToken은 Authentication 인터페이스를 구현한 클래스로, 아직 인증되지 않은 Authentication이다.
즉, 정보가 맞든 틀리든 로그인 정보를 바탕으로일단 무조건 생성된다.
(3). 아직 인증되지 않은 Authentication을 가지고 있는 UsernamePasswordAuthenticationFilter는 이 Authentication을 AuthenticationManager에게 전달한다.
여기서 AuthenticationManager는 인증 처리를 총괄하는 인터페이스이고, AuthenticationManager를 구현한 구현 클래스가 ProviderManager이다.
(4),(5). ProviderManager로 부터 Authentication을 전달 받은 AuthenticationProvider는 (5)에서와 같이 UserDetailsService를 이용해 UserDetails을 조회한다.
UserDetails는 DB 등의 저장소에 저장된 사용자의 Username과 사용자의 자격을 증명해주는 Credential인 Password, 그리고 사용자의 권한 정보를 포함하고 있는 컴포넌트이다.
이 UserDetails를 제공하는 컴포넌트가 UserDetailsService이다.
(6). UserDetailsService는 DB 등의 저장소에서 사용자의 Credential을 포함한 사용자의 정보를 조회한다.
(7). DB 등의 저장소에서 조회한 사용자의 Credential을 포함한 사용자의 정보를 기반으로 UserDetails를 생성한다.
(8). 생성된 UserDetails를 다시 AuthenticationProvider에게 전달한다.
(9). UserDetails를 전달받은 AuthenticationProvider는 PasswordEncoder를 사용해 UserDetails에 포함된 암호화된 PW와 인증을 위한 Authentication 안에 포함된 Password가 일치하는지 검증한다.
여기서 검증에 성공한다면 UserDetails를 사용해 인증된 Authentication을 생성한다.
실패한다면 Exception을 발생시키고 인증 처리가 중단된다.
(10). AuthenticationProvider는 인증된 Authentication을 ProviderManager에게 전달한다.
이전, (2)에서의 Authentication은 인증을 위해 필요한 사용자의 로그인 정보를 가지고 있지만, 현재 단계에서 ProviderManager에게 전달한 Authentication은 인증에 성공한 사용자의 정보(Principal, Credential, GrantedAuthorities)를 가지고 있게 된다.
(11). ProviderManager는 인증된 Authentication을 다시 UsernamePasswordAuthenticationFilter에게 전달한다.
(12). 인증된 Authentication을 전달 받은 UsernamePasswordAuthenticationFilter는 마지막으로 SecurityContextHolder를 이용하여 SecurityContext에 인증된 Authentication을 저장한다.
여기서 SecurityContext는 이후, 스프링 시큐리티의 세션 정책에 따라 세션에 저장되어 사용자의 인증 상태를 유지하기도하고, 세션을 생성하지 않고 무상태를 유지하기도 한다.
여기까지가 스프링 시큐리티의 인증 처리 플로우이다.
복잡해 보이지만 컴포넌트 이름을 조금 단순하게 생각하고 읽어보면 이해가 더 쉬울 것이라고 생각한다.
2. Spring Security의 인가 처리 흐름
인증된 사용자라고 하더라도 서버의 모든 리소스에 접근할 수 없다.
권한 부여라는 중요한 보안 요소가 있기 때문이다.
인증 처리에 대한 흐름은 보았고, 이번에는 인가 처리에 대한 흐름이다.
참고로 인증과 인가가 헷갈린다면 단어 사전을 보고오는 편이 가장 정확하다.
위 그림은 사용자가 로그인에 성공한 이후, 스프링 시큐리티에서 인증된 사용자에게 어떻게 권한을 부여하는지를 보여주는 흐름 차트이다.
여기서 스프링 시큐리티의 filter chain 중 URL을 통해 사용자의 액세스를 제한하는 권한 부여를 위한 Filter는 AuthorizationFilter이다.
흐름은 다음과 같다.
(1). AuthorizationFilter는 SecurityContextHolder로 부터 Authentication을 획득한다.
(2). SecurityContextHolder로 부터 획득한 Authentication과 HttpServletRequest를 AuthorizationManager 에게 전달한다.
여기서 AuthorizationManager는 권한 부여를 총괄하는 인터페이스이고, RequestMatcherDelegatingAuthorizationManager는 AuthorizationManager 를 구현하는 구현체 중 하나이다.
RequestMatcherDelegatingAuthorizationManager는 RequestMatcher 평가식을 기반으로 해당 평가식에 매치되는 AuthorizationManager에게 권한 부여 처리를 위임하는 역할을 한다.
즉, RequestMatcherDelegatingAuthorizationManager 가 직접 권한 부여 처리를 하는 것이 아니라 RequestMatcher를 통해 매치되는 AuthorizationManager 구현 클래스에게 위임만 하는 것이다.
(3). RequestMatcherDelegatingAuthorizationManager 내부에서 매치되는 AuthorizationManager 구현 클래스가 있다면, 해당 AuthorizationManager 구현 클래스가 사용자의 권한을 체크한다.
(4). 적절한 권한이라면 다음 요청 프로세스를 이어간다.
(5). AccessDeniedException이 throw되고 ExceptionTranslationFilter가 AccessDeniedException을 처리한다.
위 내용이 스프링 시큐리티에서의 인가 처리 흐름이다.
스프링 시큐리티 5.5버전 이후부터 적용되는 흐름인데, 이 버전부터는 AuthorizationFilter를 통해 보다 간결한 AuthorizationManager API를 이용하여 권한 부여 처리를 할 수 있게 되었다.
이제 스프링 시큐리티의 인증, 인가 처리 흐름에 대해서도 알아보았다.
대부분 이전 블로그의 내용인데 이전보다는 훨씬 이해가 잘 되는 것 같다.
직접 쓸 때는 별 생각 없이 사용했는데, 이 글을 다시 한번 정리하고 나니까 구조가 눈에 더 잘 들어온다.