1. 주요 용어 정리
- Authentication : 인증
- 요청 단위로 처리하며, 해당하는 요청이 들어왔을 때 가능한 접근인지 확인하는 자격 증명 단계
- 일반적으로 말하는 ID / Password 기반의 확인 작업
- Authorization : 인가
- 요청과 같거나 작은 단위(ex. 메서드)로 처리하며,
인증된 사용자에 대해 권한/역할 기반으로 세부적인 접근 제어를 위한 확인 작업 - 일반적으로 말하는 user, admin과 같이 역할 별 접근 가능한 수준이 다름을 생각하면 된다.
- 요청과 같거나 작은 단위(ex. 메서드)로 처리하며,
- Filter
- Tomcat(Servlet Container)에서 DispatcherServlet의 앞단에 위치
- 요청이 들어오면, DispatcherServlet에 접근하기 전에 이를 가로채서 인증을 처리
- DispatcherServlet에서 반환 직후 작업에도 관여.
- 따라서 정적 리소스 반환의 처리에도 수행된다.
- 그렇지만 Servlet Container에 존재하기에 Bean으로 관리하지는 못한다.
차후 이를 보완한 것이 SecurityFilterChain(요약하자면, Spring이 Bean으로 관리 가능한 특수한 Filter)
- Interceptor
- Spring MVC의 Controller 앞단에 위치
- Controller 호출 전, 반환 후에 관여
- Role : 역할; 여러 Authority의 집합체. 가령 admin은 읽기, 쓰기, 삭제, 변경의 권한을 전부 다 갖고 있다.
- Authority : 말 그대로 권한. 읽기 가능/ 변경 가능과 같은 세부 작업 액세스 단위
2. Spring Security의 요약, 그리고 핵심 기술
Spring Security는 요청의 최전방인 Filter 레벨에서 사용자 인증(Authentication)을 처리하며,
Filter 레벨에서 인가(Authorization)까지 수행한 뒤, Spring MVC로 안전하게 요청을 전달한다.
이를 위해서 사용되는 Bean이 SecurityFilterChain이다.
그런데 Servlet Container의 컴포넌트는 ServletContext를 사용하기에 ServletFilter는 ServletContext에서 동작한다.
그리고 Spring Container(IoC Container)의 컴포넌트들은 ApplicationContext를 사용한다.
따라서 Spring의 모든 Bean은 RootApplicationContext로 부터 만들어져서 ApplicationContext에서 동작한다.
그러면 상황이 맞지 않는다. Spring Security는 ServletContext에 속한 Filter 레벨에서 작동하고,
그 핵심 기술이 ApplicationContext에 속한 Bean을 사용한다니. 이게 가능할까?
그게 가능하다. 그리고 이를 도와주는 것이 바로 DelegatingFilterProxy이다.
SecurityFilterChain은 DelegatingFilterProxy를 통해서 Filter에서도 사용이 가능한 Bean이 된다.
반대로, Filter의 과정 속에서 ApplicationContext의 Bean을 사용하는 것도 가능하다.
이를 통해서 Bean 등록과 사용을 통해 Spring에서 Filter의 접근을 자유롭게 설정이 가능한 것이 된다.
3. Spring Security의 전반적인 과정 요약
- 클라이언트의 HTTP 요청이 들어오면 Spring Security의 Filter Chain에 의해 요청이 가로채진다.
- SecurityFilterChain이 DelegatingFilterProxy로 위임, 다양한 Filter를 호출
(폼 기반 로그인 요청이 들어왔다는 가정) - 사용자의 username, password를 추출하여 Authentication 객체를 생성
- AuthenticationManager가 Authentication 객체를 받아 인증
- AuthenticationProvider가 실제 인증 로직을 수행
- AuthenticationProvider에서 UserDetails 객체를 다루는 UserDetailsService를 통해
username, password를 획득 (이때의 UserDetailsService는 여러 구현방식이 가능.
InMemoryUserDetailsManager, DB, LDAP, DAO와 같은 방식으로 username/pw를 획득) - username과 password기반으로 인증 후 인가 정보까지 가지고 있는 UsernamePasswordAuthenticationToken을 반환
- 이때 UsernamePasswordAuthenticationToken 또한 Authentication이다.
다시 말해, Authentication을 입력후 Authentication을 출력하는 셈
- 이때 UsernamePasswordAuthenticationToken 또한 Authentication이다.
- 이 반환된 Authentication (여기서는 UsernamePasswordAuthenticationToken 구현체)를
Authentication Filter가 SecurityContextHolder라는 ThreadLocal에 저장한다.
- AuthenticationProvider에서 UserDetails 객체를 다루는 UserDetailsService를 통해
- AuthenticationProvider가 실제 인증 로직을 수행
- 이후 controller 요청 이전, 반환 이후 (또는 @EnableMethodSecurity 설정 시 Spring MVC 내에서)
인가 단계에서 인가 정보가 필요할 때는 AccessDecisionManager가 SecurityContextHolder에 접근하여
인가 정보 (GrantedAuthority)를 기반으로 접근 여부를 결정 - Spring MVC의 컨트롤러, 비즈니스 로직에 요청이 전달 됨
- 요청 후 응답 반환시 왔던 그대로 컨트롤러 -> 필터 체인을 거쳐 클라이언트로 반환
4. 그러면 Session은 어떤 식으로 이루어지는 것인가?
기본적으로 Spring Security는 매 요청마다 인증과 인가의 과정을 거친다.
매 요청마다 위와 같은 과정을 반복한다는 것이다.
이는 SecurityContextHolder의 성질에 기인한다. ThreadLocal 기반이기 때문에 요청이 종료 될 경우
스레드에서는 스레드풀로 반환되기 전 clear()를 통해서 Authentication 정보를 모두 제거한다.
그렇기에 세션과 같은 stateful한 정보는 Server측의 Session Storage나
외부 DB(성능이 좋은 인메모리 기반 DB - Redis)에 저장한다.
Session에 관한 정보가 필요 시 인증을 마치고 난 후 AuthenticationProvider로부터 반환받는
인증이 완료된 Authentication을 통해 Session측으로부터 원하는 정보를 가져오고,
이를 사용해서 Session을 유지, 폐기하는 방식으로 Session의 관리가 이루어진다.
https://findmypiece.tistory.com/62
DelegatingFilterProxy
Servlet은 ServletContext를 사용하고 Spring은 ApplicationContext를 사용한다. 또한 ServletFilter는 ServletContext에서 동작하기 때문에 기본적으로 ApplicationContext 에 있는 Spring Bean을 참조하지 못한다. 하지만 Servl
findmypiece.tistory.com