Back-end

[Spring Security] #1 기본 구조

100_g 2024. 6. 15. 15:04
"보안"
이라는 용어는 분야를 막론하고 쉽게 접해보았을 것이다.
그렇다면 우리가 보안을 신경 쓰는 이유가 무엇일까?

바로 우리의 소중한 자산 혹은 정보를 지키기 위해서다.

 

 

마찬가지로 웹 상에서도 애플리케이션 자체와 우리의 소중한 자산과 정보를 지킬 필요가 있는데, 그러기 위해서 웹 보안을 직접 구현하기에는 말도 안 되게 복잡한 로직을 직접 작성해야 하는 어려움이 있다. 하지만 우리는 Spring Security라는 프레임워크의 도움으로, 강력하고 편리하게 웹 애플리케이션을 보호할 수 있다.

 

웹 애플리케이션을 보호하는 데에는 여러 가지 방법이 있겠지만, 여기에서는 앞서 언급한 Spring Security 프레임워크를 이용하고자 한다. 그중에서도 이번에는 Spring Security의 전반적인 구조와 기본 흐름에 대해서 다루고자 한다.

 


 

Spring Security의 구조에 대해 알아보기에 앞서, 우선 Servlet과 Filter에 대해 알아볼 필요가 있다.

 

Servlets & Filters

 

  • 웹 애플리케이션에서의 전형적인 시나리오
Java 웹 애플리케이션에서 서블릿 컨테이너(웹 서버)는 Java 코드가 이해할 수 있도록 HTTP 메시지를 번역하는 역할을 하는데, 가장 많이 사용되는 서블릿 컨테이너 중 하나가 바로 Apache Tomcat이다. 서블릿 컨테이너는 HTTP 메시지를 ServletRequest로 변환하여 서블릿 메서드의 매개변수로 전달하고, 반대로 ServletResponse는 서블릿으로부터 서블릿 컨테이너로 반환된다. 따라서 Java 웹 애플리케이션 내에서 작성하는 모든 것은 서블릿에 의해 구동된다고 볼 수 있다.
  • Filter의 역할
Java 웹 애플리케이션 내에서 필터는 비즈니스 로직을 수행하기 이전에 각 요청/응답을 가로채고 일부 작업을 수행하는 데 사용된다. Spring Security에서도 이러한 필터를 사용하며, 웹 애플리케이션 내의 설정(configuration)에 따라 보안을 강화한다.

그럼 이제 이러한 배경지식을 바탕으로 Spring Security의 전반적인 내부 구조와 흐름에 대해서 알아보자.

Spring Security Internal Flow

 

보다시피 다양한 구성 요소들이 있다. 실제로는 각 인터페이스를 구현하는 구현 클래스들도 상당수 있기 때문에 더욱 많지만, 우선은 보이는 각 요소들에 대해서 알아보자.

 

  • Spring Security Filters
Spring Security Filters는 각 요청을 가로채고, 인증(authentication)이 필요한지 여부를 식별한다. 인증이 필요하다면, 사용자를 로그인 페이지로 이동시키거나 기존에 저장된 유저정보를 사용한다.
  • Authentication
UsernamePasswordAuthenticationFilter와 같은 필터는 HTTP 요청에서 username과 password를 추출하여 Authentication 타입의 객체를 만든다. 그리고 이 Authentication은 Spring Security 프레임워크 내에서 인증된 사용자 세부 정보(user details)를 저장하는 핵심 표준이 된다.
  • Authentication Manager
Authentication Manager(인증 관리자)는 Filter로부터 요청을 받은 후, 사용자 세부 정보(user details)를 검증하는 작업을 사용 가능한 인증 제공자들(authentication provider)에게 위임한다. 애플리케이션 내에는 여러 인증 제공자(authentication provider)가 있을 수 있고, 이러한 모든 인증 제공자(authentication provider)들은 Authentication Manager에 의해서 관리된다.
  • Authentication Provider
각 인증 제공자(AuthenticationProvider)는 사용자 세부 정보(user details)를 검증하는 모든 핵심 로직을 가지고 있다.
  • UserDetailsManager / UserDetailsService
UserDetailsManager / UserDetailsService는 데이터베이스/저장 시스템으로부터 사용자 세부 정보(user details)를 검색, 생성, 업데이트 및 삭제하는 데에 도움을 준다.
  • PasswordEncoder
PasswordEncoder는 비밀번호를 인코딩하고 해싱하는 데 도움을 주는 서비스 인터페이스다. 이 인터페이스를 사용하지 않으면 비밀번호를 암호화되지 않은 일반 텍스트로 사용해야 한다.
  • SecurityContext
요청이 인증되면, Authentication은 보통 SecurityContextHolder가 관리하는 thread-local SecurityContext에 저장된다.

thread-local은?
:각 스레드마다 독립적인 보안 컨텍스트를 유지하는 데 사용된다. 이를 통해 동일한 사용자가 여러 요청을 보낼 때, 각각의 요청이 올바른 인증 정보를 유지할 수 있게 된다.

이미지 출처: https://vanillacreamdonut.tistory.com/357

전반적인 내부 구조와 구성요소들에 대해서 알아보았으니, 이제는 흐름에 대해서 알아보자. 위의 그림(Spring Security Internal Flow) 번호를 참고하여 살펴보자.

 

1. 사용자가 보안 페이지를 최초에 접근(요청)하고자 하면 AuthenticationFilter(UsernamePasswordAuthenticationFilter)에 의해 가로채진다.
2. 필터는 사용자 요청으로부터 username과 password를 추출하고, Authentication 인터페이스의 구현체인 UsernamePasswordAuthenticationToken 객체를 생성한다.
3. 생성된 UsernamePasswordAuthenticationToken 객체를 AuthenticationManager의 구현체인 ProviderManager의 authenticate() 메서드에 넘겨준다.
4. ProviderManager는 주어진 인증 객체를 지원하는 사용 가능한 인증 제공자(Authentication Provider) 목록을 찾는다. 기본 동작으로는 ProviderManager가 DaoAuthenticationProvider의 authenticate() 메서드를 호출한다.
5. AuthenticationProvider는 UserDetailsManager를 호출하여 메모리/저장소/데이터베이스 등에서 UserDetails(사용자 세부 정보)를 불러온다.
6. 사용자 세부 정보를 불러온 후, PasswordEncoder 구현을 사용하여 비밀번호를 비교하고 사용자가 인증된 사용자인지 여부를 검증한다.
7. 인증이 성공했는지 여부에 대한 세부 정보를 포함한 Authentication 객체를 ProviderManager에 반환한다.
8. ProviderManager는 인증이 성공했는지 여부를 확인한다. 성공하지 않은 경우에는 다른 사용 가능한 AuthenticationProvider로 인증을 재시도하고, 성공한 경우에는 인증 세부 정보를 필터에 반환한다.
9. Authentication 객체는 향후 사용할 수 있도록 필터에 의해 SecurityContext 객체에 저장된다.
10. 엔드 유저에게 응답이 반환된다.

본 게시글의 내용과 자료는 Eazy Bytes의 "Spring Security 6 초보에서 마스터 되기 최산강의! (JWT, OAUTH2 포함)" 강의를 인용하여 작성되었습니다.
https://www.udemy.com/course/spring-security-6-jwt-oauth2-korean/?couponCode=ST21MT61124