Servlet Authentication Architecture
Authentication과 AuthenticationManager
Servlet Authentication Architecture에 대해서는 공식 도큐먼트의 마지막에 있는 그림을 먼저 보고 시작해야 이해가 잘 됩니다. Servlet Authentication Architecture의 시작은 Spring Security Architecture에서 보았던 SecurityFilterChain에서부터 시작합니다.
SecurityFilterChain 안에 AbstractAuthenticationProcessingFilter가 포함되어 있고, 많이 사용하는 UsernamePasswordAuthenticationFilter는 이 추상 클래스를 상속받은 클래스입니다.
- AbstractAuthenticationProcessingFilter : 사용자가 credentials 정보를 제출하면 Authentication 객체를 생성합니다. 이 과정에서 HttpServletRequest 정보가 사용됩니다. 어떤 타입의 Authentication을 생성할지는 AbstractAuthenticationProcessingFilter를 상속한 클래스에 따라서 달라집니다.
- AbstractAuthenticationProcessingFilter를 상속한 UsernamePasswordAuthenticationFilter는 UsernamePasswordAuthenticationToken 타입의 Authentication을 생성합니다. 이 과정에서 HttpServletRequest에서 username, password를 가져와서 사용합니다.
- AuthenticationManager : 전달받은 Authentication를 어떻게 사용할지는 정의하는 인터페이스입니다. AuthenticationManager에서 리턴된 Authentication는 SecurityFilterChain에 포함된 SecurityFilter에 의해서 SecurityContextHolder에 저장됩니다. SecurityFilterChain를 사용하지 않고 있다면 AuthenticationManager를 사용하지 않고 바로 SecurityContextHolder에 저장할 수도 있습니다.
ProviderManager
ProviderManager는 가장 많이 사용되는 AuthenticationManager의 구현체입니다. ProviderManager는 여러 개의 AuthenticationProvider를 가지고 있습니다. 각각의 AuthenticationProvider는 인증 실패, 인증 성공, 모름 (다음 AuthenticationProvider에 위임)을 판단하는 역할을 합니다. 모든 AuthenticationProvider에서 인증이 성공되었다고 판단되지 않으면 ProviderNotFoundException를 상속한 AuthenticationException이 발생합니다. AuthenticationException는 ProviderManager가 해당 요청을 인증하기 위해 적합하지 않다는 뜻이 됩니다.
ProviderManager의 결과 처리하기 :: 인증 성공(SecurityContextHolder)
ProviderManager가 가지고 있는 여러 개의 AuthenticationProvider 중 하나에 의해서 Authentication이 생성되고 이 객체가 SecurityContext에 저장됩니다.
SecurityContextHolder는 누가 인증됐는지에 대한 정보를 저장하는 공간입니다.
- Spring Security는 SecurityContextHolder에 값이 들어있으면 현재 사용자의 인증 정보로 사용한다. 이 인증 정보가 어떻게 populate 되었는지는 확인하지 않습니다.
// 누가 인증됐는지 컨텍스트에 직접 추가하는 방법 (예시)
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
항상 새로운 컨텍스트를 생성하는 것이 중요합니다. 이미 존재하는 컨텍스트에서 조회해서 authentication을 사용하면 동시성 문제가 발생할 수 있습니다.
Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER");
// Good
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
// Bad
SecurityContextHolder.getContext().setAuthentication(authentication)
위와 같은 방식으로 컨텍스트에 추가되어있는 인증 정보는 아래와 같이 가져올 수 있습니다.
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
SecurityContextHolder는 ThreadLocal에 관련된 정보를 저장하기 때문에 항상 같은 쓰레드에서만 해당 정보에 접근할 수 있다. 게다가 ThreadLocal을 사용하기 때문에 명시적으로 파라미터로 인증 정보가 전달되지 않아도 이 정보를 사용할 수 있습니다.
다만, 모든 요청마다 매번 새로운 쓰레드가 생성되고 삭제되는 것이 아니라 쓰레드 풀에 있는 쓰레드를 사용하고 요청을 처리한 뒤 쓰레드 풀에 다시 반납하는 것을 주의해야 한다. 즉, 사용자의 요청을 처리한 뒤에 ThreadLocal에 저장된 인증 정보를 반드시 주의해서 삭제해야 한다. 이러한 과정은 FilterChainProxy에 의해서 SecurityContext가 Clear 되기 때문에 개발자가 명시적으로 처리해주어야 하는 것은 아닙니다.
이처럼 ThreadLocal을 사용하는 SecurityContext의 정책이 애플리케이션의 목적과 맞지 않으면SecurityContextHolder.MODE_GLOBAL 등을 찾아보면 됩니다.
- SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
- SecurityContextHolder.MODE_THREADLOCAL
ProviderManager의 결과 처리하기 :: 인증 실패 (ExceptionTranslationFilter)
ProviderManager(아래 그림에서는 AuthenticationManager)에서 AuthenticationException이 발생하면 (인증 실패) 이 Exception에 대한 처리는 ExceptionTranslationFilter에서 처리됩니다. (Spring Security 아키텍처의 영역)
'DEV > Spring Security' 카테고리의 다른 글
Spring Security 아키텍처 (0) | 2024.01.30 |
---|