Servlet Authentication Architecture
Spring Security에서 가장 중요한 인증 아키텍쳐를 보도록 하겠습니다.
Spring Security에서 인증된 사용자의 정보를 저장하는 곳입니다.
가장 간단한 방법은 이 SecurityContextHolder
에 직접 Authentication을 넣은 SecurityContext
를 넣어주는 것입니다.
// 1. SecurityContext생성
SecurityContext context = SecurityContextHolder.createEmptyContext();
// 2. Authentication생성
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
// 3. SecurityContextHolder에 넣기
SecurityContextHolder.setContext(context);
2번에서는 어떤 Authentication구현체로 생성하든 상관없지만 UsernamePasswordAuthenticationToken
(userDetails, password, authorities)이 가장 많이 쓰입니다.
이렇게 인증된 유저를 SecurityContextHolder
에 넣어주었다면 인증된 사용자의 정보가 필요하면 다시 SecurityContextHolder
에 접근해서 가져오면 됩니다.
// 1. SecurityContext 꺼내오기
SecurityContext context = SecurityContextHolder.getContext();
// 2. Authentication 꺼내오기
Authentication authentication = context.getAuthentication();
// 3. 사용자 정보 꺼내오기
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
근데 여기서 하나 의문은 꼭 SecurityContextHolder
에 하나의 Context만 있는 것처럼 작성되어있다는 것입니다. 만약 여러 인증된 사용자들이 담겨있다면 아무 구분없이 어떻게 해당 사용자가 들어있는 Context를 가져오는 것일까요?
이에 대한 답은 ThreadLocal에서 찾을 수 있습니다.
SecurityContextHolder
의 기본방식이 ThreadLocal
인데요. 즉 스레드마다 독립적으로 SecurityContext를 갖게됩니다. 따라서 지금과 같은 코드에서는 같은 thread에서 넣은 인증된 유저정보를 가져오게 되는 것입니다.
이런 방식은 필요에따라 system property나 static method를 통해서 바꿀 수 있습니다.
다른전략들
SecurityContextHolder
를 통해서 얻을 수 있으며 Authentication객체를 담고있습니다.
Spring Security에서 두가지 목적으로 쓰입니다
사용자가 인증받기위해 제공한 credentials를 AuthenticationManager
에게 제공
-> 위와 같은 코드는 ContextHolder
에 직접주입한경우 즉, isAuthentication()이 False반환
SecurityContext에서 꺼내볼 수 있음
또한 구성요소는 세가지 입니다
사용자를 식별합니다. username/password를 사용할 경우 UserDetails객체가 됩니다.
대부분 비밀번호이며 인증된 뒤에는 유출을 막기위해 지웁니다.
GrantedAuthority는 높은 수준의 permission이며 roles와 scopes가 있습니다.
Authentication.getAuthorities()
를 통해서 얻을 수 있으며 GrantedAuthority들의 Collections가 반환됩니다. 주로 ROLE_ADMINISTRATOR, ROLE_USerroles같은 roles를 사용합니다. 이런 roles는 후에 Authorization
(인가)에 판단기준이 됩니다.
Spring Security의 Filters가 authentication을 처리하는 API입니다. AuthenticationManager
에서 반환된 Authentication은 SecurityContextHolder
에 저장됩니다. AuthenticationManger
의 도움없이 직접 ContextHolder
에 Authentication을 주입할 수 있습니다.(처음 코드와 같이)
가장 자주 사용되는 AuthenticationManager의 구현체입니다. ProviderManger는 AuthenticationProvider의 리스트에 할일들을 위임합니다.
이때 적절한 AuthenticationProvider를 찾지못하면 인증은 실패하고 ProviderNotFoundException를 발생시킵니다.
아래와 같이 둘이상의 ProviderManager
가 부모를 공유할 수 있습니다. 따라서 이 자식 ProviderManger
는 겹치는 부분(Parent)과 다른부분(각각)을 갖고있게 됩니다. 또 ProviderManger
는 성공적인 authentication request에 대해서는 credentials(주로 비밀번호)를 지웁니다. 유출의 위험을 막기위해서죠! 물론 필요에 따라 eraseCredentialsAfterAuthentication
옵션을 disabled시킬 수도있습니다.
여러 AuthenticationProvider
가 ProviderManger
에 주입될 수 있으며 각자 다르게 행동합니다. 예를들어 DaoAuthenticationProvider
는 username/password를 기반으로 인증하지만 JwtAuthenticationProvider
는 JWT 토큰을 기반으로 인증을 합니다.
이 Filter는 user를 인증하기 위한 기본적은 Filter입니다.
1) 먼저 사용자가 입력한 credentials를 가지고 Authentication을 생성합니다. 이는 어떤 subclass Filter냐에 따라 달라집니다. 예를 들어 UsernamePasswordAuthenticationFilter
는 UsernamePasswordAuthenticationToken를 만듭니다.
2) Authentication은 AuthenticationManger
를 통해 인증과정을 거칩니다.
3) 만약 인증이 실패된다면
SecurityContextHolder
를 비웁니다RememberMeService
를 사용한다면 loginFail을 발생시킵니다AuthenticationFailureHandler
가 동작합니다4) 인증이 성공하면
SecurityContextHolder
에 넣습니다RememberMeServices
를 사용한다면 loginSuccess가 발생합니다.ApplicationEventPublisher
가 InteractiveAuthenticationSuccessEvent
발생시킵니다.AuthenticationSuccessHandler
가 발생합니다.출처