본문 바로가기

백엔드/스프링(부트)

스프링 시큐리티는 복잡합니다. 다만, 공통된 인증 흐름은 존재합니다.

반응형

들어가는 말

최근에 스프링부트 공부를 시작하면서, 큰 난간이었던 것들 중 하나는 스프링 시큐리티에 관한 것이었습니다. 아무리 내부적으로 추상화 및 캡슐화되어 사용자는 노출된 인터페이스를 기반으로 도구를 사용하면 된다고 해도, 스프링 시큐리티가 내부적으로 어떻게 사용자의 자격증명을 처리하는지 그 흐름을 이해하지 못하고는 제가 도구를 쓰는 것인지, 도구가 나를 쓰는 것인지 긴가민가할 때가 많았습니다.

 

따라서 오늘은 스프링 시큐리티가 내부적으로 어떤 흐름을 통해 동작하는지 간략하게 살펴보며, 앞으로 학습의 기틀을 단단히 다지는 시간을 가질까 합니다.

 

스프링 시큐리티?

스프링 시큐리티는 스프링 기반 애플리케이션에서 인증(Authentication)과 인가(Authorization)를 처리하는 보안 프레임워크입니다. 로그인, 권한 제어, 세션 관리 등 보안과 관련된 대부분의 기능을 제공합니다. 프레임워크 라고 이름 지어진 만큼, 모든 것이 체계적으로 프레임이 짜여져 있고, 사용자는 이것을 그져 가져다가 사용법대로 따라쓰면 됩니다.

 

다만, 자바는 확장 가능한 방식으로 기존에 상위 클래스나 인터페이스에 정의된 메소드를 상황에 맞게 재사용하거나, 기능을 확장해서 필요한 처리를 사용자가 정의한 방식대로 흐름을 유도할 수 있기 때문에, 보다 유연한 프레임워크 라고볼 수 있습니다.

 

스프링 시큐리티의 플로우

스프링 시큐리티는 사용자의 인증 요청을 어떻게 처리할까요? 제가 찾아본 공식문서의 여러 자료에서는 인증을 요청하는 방식에 따라서 사용되는 필터가 다르기 때문에, 가히 복잡하다는 느낌을 많이 받았는데요. 그럼에도 공통적으로 따르고 있는 처리 흐름은 존재 한다 라고 느꼈습니다. 그래서 이를 그래프로 표현해보면 아래와 같습니다.

 

위 그래프의 동작 흐름을 구체적그로 설명하면 아래와 같습니다.

 

① 요청 수신: 클라이언트가 서버로 요청을 보냅니다.


② 필터 체인 활성화: Spring Security는 요청을 가로채서 필터 체인의 doFilter() 메소드를 통해 처리합니다.

 

③ AuthenticationManager 호출: 필터 체인 중간에서 AuthenticationManager의 authenticate() 메소드를 호출하여 요청의 인증을 시도합니다. 이 부분에 대한 정확한 설명은 스프링 시큐리티 공식문서 에서 확인할 수 있습니다. 

authenticate() 의 인자로 전달된 AuthenticationToken 객체를 검증해서 인증을 성공한다면, 완벽하게 채워진 Authentication 객체를 반환해주는 역할을 합니다. 그리고 이러한 인증로직을 실질적으로 수행하는 일꾼들이 여러 개의 AuthenticationProvider 입니다. 

 

AuthenticationManager과 AuthenticationProvider 의 상호작용: AuthenticationManager는 여러 AuthenticationProvider 중 하나를 선택해 인증을 시도합니다. 이때 자격증명(사용자 이름과 비밀번호 등)이 포함된 Authentication 객체를 사용합니다. 

단순하게 말하면, AuthenticationManager 는 관리자이고, AuthenticationProvider 는 일꾼입니다. 즉, 관리자는 자신이 시키고자 하는 일에 부합하는 여러 일꾼 중 몇을 선택해서 작업을 맡기는 것입니다. 그리고 일꾼이 작업을 마치고 관리자에게 보고 하면, 관리자는 보고 받은 내용으로 완성된 보고서를 정리하고, 자신을 호출한 상위 부서에 보고서를 올리게 됩니다.

여기서 완성된 보고서가 Authentication 객체 이며, 일꾼에게 지시를 내리는 동작이 Authentication 객체를 반환하는 authenticate() 메소드 입니다.

 

⑤ AuthenticationProvider 에서 UserDetailsService 호출: AuthenticationProvider는 UserDetailsService의 loadUserByUsername() 메소드를 통해 데이터베이스 등의 저장소에서 사용자 세부 정보를 가져옵니다.

 

⑥ 인증 검증: 가져온 사용자 세부 정보와 자격증명을 비교하여 인증이 성공하면, Authentication 객체에 주체(사용자 세부 정보)와 권한을 포함시킵니다.

여기서 인증된 정보를 담은 Authentication 객체는 AuthenticationManger 로 전달되고, 이를 다음 절차인 SecurityContextHolder 에 저장하게 됩니다.

 

⑦ SecurityContextHolder에 저장: 인증이 성공한 후 SecurityContextHolder의 setContext() 메소드를 호출하여 결과가 저장되고, 이 정보는 애플리케이션의 나머지 부분에서 SecurityContextHolder.getContext() 메소드를 통해 사용될 수 있습니다.

SecurityContextHolder 는 SecurityContext 라는 객체를 가지고 있으며, 해당 객체는 Authentication 객체를 반환합니다. 즉, setContext() 를 통해 SecurityContext 에 Authentication 객체를 저장하게 되면, getContext() 를 통해 저장된 Authentication 객체를 읽어올 수 있습니다.

 

나가는 말

오늘 간단하게 알아본, Spring Security의 인증 프로세스는 클라이언트의 요청을 처리하기 위해 잘 설계된 구조를 가지고 있었습니다.

 

앞서 알아본 흐름 중 AuthenticationManager는 인증 요청을 관리하는 주체로, 여러 AuthenticationProvider를 통해 사용자의 자격증명을 검증하고, 실제 인증 로직을 구현합니다. 사용자 정보를 확인하고 인증이 성공할 경우 Authentication 객체를 반환하고 있습니다.

 

사실 이 구조에서 ProviderManager 라는 AuthenticationManager의 구현체가 별도로 존재합니다. 스프링 시큐리티에는 이러한 숨겨진 구조가 연쇄적으로 무수히 많이 존재합니다(그래서 복잡하게 느껴지는 것이겠죠).


즉 앞서 알아본 인증 흐름은 일부일 뿐이고, 상세하게 파고들면 사이사이에 무수히 많은 흐름이 숨어 있습니다. 그 만큼 모든 흐름을 파악하는 것은 어렵기에 저 같이 이를 처음 접하는 사람의 입장에서는 처음에는 많은 혼란을 야기할 만큼 복잡하다는 생각도 듭니다. 

 

따라서 자세한 내용은 공식문서를 기반으로 하나씩 살펴보는 것을 매우 권장해드리며, 이만 글을 줄여볼까 합니다. 감사합니다( 버전 별로 흐름이 다를 수 있으니 사용하고자 하는 스프링 시큐리티 버전을 꼭 확인하세요).

참고자료

스프링 시큐리티 공식 문서: https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-authenticationprovider

 

반응형