-
DelegatingFilterProxy
- 서블릿 필터는 스프링에서 정의된 빈을 주입해서 사용할 수 없음 → 서블릿 컨테이너와 스프링 컨테이너는 서로 접근 불가능
- 특정한 이름을 가진 스프링 빈을 찾아 그 빈에게 요청을 위임하는 역할
- springSecurityFilterChain 이름으로 생성된 빈(FilterChainProxy 타입)을 ApplicationContext에서 찾아 요청을 위임
- 실제 보안처리를 수행하지 않음(보안처리는 각 요청에 따른 Filter에서 수행)
- 즉 예시는 Servlet Filter에서 요청을 받고 DelegatingFilterProxy로 포워딩되고 여기서 스프링 빈으로 등록된 Filter를 요청에 맞게 적용
- 스프링 빈을 Servlet Filter에서 바로 사용할 수 없기 때문에 이런 방식을 이용(Servlet, Spring Container가 다르기 때문)
-
FilterChainProxy
- SpringSecurityFilterChain 이름으로 생성되는 필터 빈
- DelegatingFilterProxy로부터 요청을 위임 받고 필터들을 통해 실제 보안 처리
- 스프링 시큐리티 초기화 시 생성되는 필터들을 관리/제어
- 스프링 시큐리티가 기본 제공하는 필터
- SecurityConfig에서 API로 설정으로 생성한 필터
- 전체적으로는 사용자의 요청을 필터 순서대로 호출하여 전달 → 정확히는 순서대로 실행하면서 조건에 맞지 않는 경우 다음 필터 작동
- 사용자는 CustomFilter를 정의하여 기존의 필터 전/후로 설정 가능 → 순서에 유의
- 마지막 필터까지 예외가 발생하지 않는다면 보안을 통과
-
DelegatingFilterProxy, FilterChainProxy의 전체적인 흐름
- 기본적으로 Servlet/Spring Container로 영역이 구분되어 Servlet 요청에서 스프링 빈을 활용하지 못하기 때문에 이런 방식을 활용
- DelegatingFilterProxy 앞/뒤 필터는 Servlet Container Filter
- 요청의 보안이 이루어지는 과정
- 요청 발생
- ServletContainer의 DelegatingFilterProxy가 요청 확인
- DelegatingFilterProxy에서 스프링 빈으로 생성된 Filter를 활용할 수 없으므로 스프링 빈을 활용할 수 있는 FilterChainProxy로 요청 위임
- FilterChainProxy는 springSecurityFilterChain이라는 이름의 빈으로 SpringSecurityFilter 빈들을 관리
- 순서에 맞게 필터링 처리를 수행하고 마지막까지 인증/인가 예외가 발생하지 않는다면 정상적인 보안처리 완료 및 DispatcherServlet으로 이동하여 요청에 맞는 스프링 빈에게 위임
-
필터 초기화와 다중 보안 설정
SecurityConfig를 여러개 작성하는 경우
SecurityConfig를 여러개 작성하는 경우 요청 진행 과정
- 설정 클래스 별로 보안 기능 및 RequestMatcher 설정
- 설정 클래스 별로 필터가 생성
- FilterChainProxy가 각 설정 클래스들의 필터들을 클래스별로 관리 → FilterChainProxy의 SecurityFilterChains에서 관리
- 요청에 따라 RequestMatcher와 매칭되는 필터가 작동되도록 함
- 하지만 같은 클래스를 상속한 빈이므로 unique 에러가 발생하고 실행할 수 없음 →
@Order
어노테이션을 사용해서 빈 중복 해결
@Order
어노테이션을 사용할 때 주의해야 하는 점은 어떤 클래스를 우선적으로 빈으로 등록하냐에 따라 원하는 path에 대해 보안처리가 원하는 대로 안될 수도 있음(주의)
-
Authentication(인증, 인증 객체)
-
사용자가 누구인지 증명하는 것
-
사용자의 인증 정보를 저장하는 토큰 개념 → 인증 시도할 때 username, password를 담고 인증 검증을 위해 전달되어 사용되며 인증 후에는 인증 객체를 담기 위해 사용함
-
인증 후 최종 인증 결과(사용자 객체, 비밀번호, 권한 목록)를 담고 SecurityContext에 저장되어 전역에서 참조 가능
// SpringSecurity 인증 객체 확인 코드
Authentication auth = SecurityContextHolder
.getContext().getAuthentication();
-
인증 객체 구조(Authentication 객체)
- principal = 사용자 아이디 혹은 객체를 저장
- credentials = 사용자 비밀번호
- authorities = 인증된 사용자의 권한 목록
- details = 인증 부가 정보
- Authenticated = 인증 여부
-
인증 과정
AuthenticationManager가 인증을 처리하고 예외도 발생
-
SecurityContextHolder, SecurityContext
-
SecurityContext
-
Authentication 객체가 저장되는 보관소로 필요 시 언제든지 Authentication 객체를 꺼내 쓸 수 있어야 함
-
ThreadLocal에 저장되어 같은 Thread에서 참조 가능하도록 함(ThreadLocal은 각 Thread의 저장소)
@GetMapping("")
public String index() {
// 아래는 새로운 Thread를 만들어 진행하는 코드(자식 Thread 생성 -> 모드 변경 시 접근 가능)
// 즉, Thread가 다르므로 서로 다른 ThreadLocal 참조
// 다른 ThreadLocal이므로 Authentication 객체 접근 불가능
new Thread(() -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
}).start();
return "index";
}
-
인증이 완료되면 HttpSession에 저장되어 전역에서 참조 가능
@GetMapping("")
public String index(HttpSession session) {
SecurityContext ctx = (SecurityContext) session
.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
Authentication auth = ctx.getAuthentication();
return "index";
}
-
SecurityContextHolder
-
SecurityContextPersistenceFilter
요약하면 인증을 하기 전에는 새로운 SecurityContext 객체를 생성하고
인증이 완료된 경우에는 session에서 SecurityContext 객체를 가져와서 인증을 유지하는 필터
- SecurityContext 객체의 생성, 저장, 조회 역할
- 익명 사용자
- 새로운 SecurityContext 객체를 생성하여 SecurityContextHolder에 저장
- AnonymousAuthenticationFilter에서 AnonymousAuthenticationToken 객체를 SecurityContext에 저장
- 인증 사용자
- 새로운 SecurityContext 객체를 생성하여 SecurityContextHolder에 저장
- UsernamePasswordAuthenticationFilter에서 인증을 성공하면 SecurityContext에 UsernamePasswordAuthenticationToken 객체를 SecurityContext에 저장
- 인증이 완료되면 Session에 SecurityContext 저장
- 인증 이후
- Session에서 SecurityContext 꺼내어 SecurityContextHolder에 저장
- SecurityContext 안에 Authentication 객체가 존재하면 인증 유지
- 즉, 어떤 경우에도 SecurityContext 객체의 생성 주기 관리
-
Authentication Flow
-
AuthenticationManager
- AuthenticationProvider 목록 중에서 인증 처리 요건에 맞는 AuthenticationProvider를 찾아 인증처리를 위임(AuthenticationProvider가 실제 인증을 처리)
- 부모 ProviderManager를 설정하여 AuthenticationProvider를 계속 탐색할 수 있다.(OauthAuthenticationProvider의 경우) → 기존 ProviderManager에 인증할 수 있는 Provider가 존재하지 않는 경우 탐색
- 이처럼 Linked 형태로 부모와 자식 관계를 형성할 수 있음
- 자식에서 적절한 AuthenticationProvider를 찾지 못한 경우 부모로 올라가면서 Provider를 탐색
- AuthenticationManagerBuilder를 사용해서 스프링 시큐리티가 설정한 기본 부모관계 설정을 변경해야 모든 ProviderManager의 Provider 탐색이 가능
-
AuthenticationProvider
authenticate()
는 실제 인증을 처리하는 메서드(UserDetailsService 인터페이스 구현체 이용) → 인증된 객체 반환
supports()
는 현재 인증 객체(UsernamePasswordAuthenticationToken, AnonymousAuthenticationToken)의 인증을 지원하는 AuthenticationProvider인지 boolean
타입으로 반환해서 AuthenticationManager에서 적절한 AuthenticationProvider를 탐색할 수 있는 메서드 → boolean 반환
-
Authorization(인가)
-
인증된 사용자에 대해 이뤄지며 특정 자원에 대해 인증된 사용자가 접근 가능한 지 확인하는 절차
-
스프링 시큐리티가 지원하는 권한 계층
- 웹 계층
- URL 요청에 따른 메뉴 혹은 화면단위의 레벨 보안
- 서비스 계층
- 화면 단위가 아닌 메서드 같은 기능 단위의 레벨 보안
- 도메인 계층(Access Control List, 접근 제어 목록)
-
FilterSecurityIntercepter
SecurityMetadataSource는 URL에 따른 권한 정보를 가지고 있으며 해당 권한에 따라 처리
- FilterProxyChain에서 가장 마지막에 위치한 Filter로 인증된 사용자에 대하여 특정 요청의 승인/거부 여부를 최종적으로 결정
- 인증 객체 없이 자원에 접근하는 경우 AuthenticationException 발생
- 권한 없이 자원에 접근하는 경우 AccessDeniedException 발생
- 해당 예외들에 대해서는 ExceptionTranslationFilter에 위임하여 예외 종류별로 처리
- 권한 제어 방식 중 Http 자원의 보안을 처리하는 필터 → URL에 따라 구분
- 권한 처리는 AccessDecisionManager에 맡김(내부적으로는 AccessDecisionVoter 클래스 활용)
- 메소드 인가 처리 클래스는 MethodSecurityIntercepter에서 처리(필터는 아님, AOP기반 동작)
-
AccessDecisionManger, AccessDecisionVoter
인가 처리 과정
- AccessDecisionManager
- 인증/요청/권한 정보를 이용해 사용자의 자원 접근을 허용/거부할 것인지 최종 결정
- 여러 개의 Voter 가질 수 있으며 Voter들로부터 접근허용/거부/보류에 해당하는 값을 리턴받고 판단 및 결정
- 최종적으로 접근 거부 시 예외 발생
- 접근 결정의 3가지 유형
- AccessDecisionVoter
-
스프링 시큐리티 주요 아키텍처 이해