Spring Security 정복하기(3)
Spring Security Authentication
이번장에서는 스프링 시큐리티에서 인증과 관련된 요소들을 알아볼것이다. 여기서는 Servlet 인증에서 사용되는 스프링 시큐리티 주요 구성 요소들을 쭉 나열하여 설명하고 이 구성요소들이 시큐리티 아키텍처에서 어떻게 적용되는지 하나씩 알아보는 방식으로 살펴볼 것이다.
SecurityContextHolder
SecurityContextHolder : 인증된 사람에 대한 세부 정보를 저장하는 곳
SecurityContextHolder는 공식문서에서 ‘At the Heart of Spring Security’s authentication’이라고 표현할 만큼 인증에 있어서 핵심적인 것 같다. 구조를 한번살펴보자.

- 어떻게
SecurityContextHolder가 채워지는지에 대한건 신경쓰지 않는다. - 이 안에 값이 들어있다면, 현재 인증된 사용자로 사용된다.
가장 간단하게 인증된 유저를 나타내는 방법은 그냥 직접 SecurityContextHolder를 채우는 것이다.
아래는 공식문서 샘플 코드이다.
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
- 위 사진처럼 빈
SecurityContext객체를 생성하고 그 안에Authentication객체를 만들어 넣는다. - 마지막으로
SecurityContextHolder에SecurityContext를 설정한다. - 샘플코드에서는 간단한
TestingAuthenticationToken을 사용하지만 실제 운영 환경에서는UsernamePasswordAuthenticationToken(userDetails, password, authorities)같은 객체를 사용하는 것이 일반적이다.
기존에는
SecurityContextHolder.getContext().setAuthentication(authentication)로 컨텍스트를 불러와 사용했지만 이는 여러 스레드의race condition문제가 발생할 수 있기 때문에 새로운SecurityContext인스턴스를 생성하여 사용한다.
이렇게하여 인증된 사람의 정보를 SecurityContextHolder에 저장할 수 있다.
이후 인증된 Principal의 정보를 얻기 위해서는 SecurityContextHolder에 접근하면 된다.
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
SecurityContextHolder는ThreadLocal을 사용하여 세부정보를 저장하므로 SecurityContext가 해당 메서드에 인수로 명시적으로 전달되지 않더라도 항상 동일한 스레드의 메서드에서 사용할 수 있다.- 현재 요청이 처리된 후 스레드를 지우는 데 주의하면 이런식으로
ThreadLocal을 사용하는것이 안전하다. FilterChainProxy가SecurityContext가 항상 지워지도록 보장해준다.
ThreadLocal
쓰레드 로컬은 자바에서 각 스레드마다 독립된 변수 값을 저장할 수 있게 해주는 메커니즘이다.
즉, 동일한ThreadLocal객체를 여러 스레드가 사용해도 각 스레드는 서로 다른 값을 갖게된다.
ThreadLocal에 저장된 데이터는 해당 스레드가 재사용될 때까지 유지되는데 만약 그 스레드가 다른 요청을 처리한다면 이전 요청의 정보를 그대로 가지고 있어 보안 문제를 불러올 수 있다. 스레드를 지우는 데 주의하라는 말이 이 맥락인 것 같다.
SecurityContext
SecurityContext : SecurityContextHolder로부터 얻을 수 있으며 Authentication을 포함한다.
Authentication
Authentication은 인터페이스로 두가지 주요 목적이 있다.
- 사용자 인증을 위한
AuthenticationManager의 입력으로 사용 - 현재 인증된 사용자를 나타내며
SecurityContext에서 얻을 수 있다.
또한 Authentication은 3가지를 포함한다.
Principal: 접근 주체이다. 사용자의 이름과 암호로 인증할 때 이는 종종UserDetails의 인스턴스이다.Credentials: Password 개념이다. 사용자 인증이 완료된 후 지워져서 유출되지 않도록 사용된다.authorities: 권한을 나타내는 부분으로 대표적으로 역할, 권한 등이 있다.
GrantedAuthority
GrantedAuthority : Principal에 부여된 권한
Authentication.getAuthorities()로 가져올 수 있다. 이 메소드는GrantedAuthority객체의 컬렉션을 반환한다.- 웹 권한부여, 메서드 권한 부여 및 도메인 객체 권한 부여를 위해 구성된다.(보통 ‘ROLE’로 권한 부여)
- ID/Password 기반의 인증을 사용하는 경우
GrantedAuthority인스턴스는 일반적으로UserDetailsService에서 불러온다.
AuthenticationManager
AuthenticationManager : 필터가 인증을 수행하는 방식을 정의하는 API
AuthenticationManager를 호출한 필터 인스턴스에 의해Authentication이SecurityContextHolder에 설정된다.AuthenticationManager는 인터페이스로써 구현방식을 정할 수 있지만 일반적으로ProviderManager가 사용된다.
ProviderManager
ProviderManager : 가장 흔히 사용되는 AuthenticationManager의 구현체
ProviderManager는AuthenticationProvider리스트를 가지며 이에 인증을 위임한다.AuthenticationProvider인스턴스 중 어느 것도 인증할 수 없는 경우ProviderNotFoundException이 발생한다. 이는ProviderManager가 전달된 인증 유형을 지원하도록 구성되지 않았음을 나타내는 특수AuthenticationException이다.

- 각
AuthenticationProvider는 인증을 수행하는 주체이며 여러개가 있을수 있어 여러 유형의 인증을 지원한다.
AuthenticationProvider
AuthenticationProvider : 특정 유형의 인증을 수행
DaoAuthenticationProvider: 사용자 이름/비밀번호 기반의 인증 지원JwtAuthenticationProvider: JWT 토큰 인증 지원
AuthenticationEntryPoint
AuthenticationEntryPoint : 클라이언트에게 Credentials를 요청하는데 사용
(앞서 살펴본 AuthenticationException에 등장)
- 클라이언트가 credential을 같이 보내면 관여 X
- 만약 리소스에 인증되지 않으면 관여
- 로그인 페이지로 리다이렉트,
WWW-Authenticate헤더로 응답 등 구현
- 로그인 페이지로 리다이렉트,
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter : 인증에 사용되는 기본 필터
아래는 인증수행 과정 다이어그램이다.

- 사용자가 자격 증명을 제출하면,
AbstractAuthenticationProcessingFilter는HttpServletRequest로부터 인증을 위한Authentication객체를 생성한다.- 생성되는
Authentication객체의 유형은 필터의 하위클래스에 따라 다르다. 예를들어UsernamePasswordAuthenticationFilter는UsernamePasswordAuthenticationToken을 생성한다.
- 생성되는
- 생성된
Authentication객체는 인증을 위해AuthenticationManager로 전달된다. - 인증에 실패할 경우
SecurityContextHolder가 지워지고RememberMeServices.loginFail이 호출된다.
(만약 Remember me가 설정되지 않았다면, 아무런 효과가 없다)AuthenticationFailureHandler가 호출된다.
- 인증에 성공할 경우
SessionAuthenticationStrategy가 알림을 받고,Authentication객체가SecurityContextHolder에 설정되며,RememberMeServices.loginSuccess가 호출되고,AuthenticationSuccessHandler가 실행된다.
정리
이번장에서는 스프링시큐리티 내부 인증에 대한 구성요소들을 쭉 알아봤다.
SecurityContextHolder가 인증된 사람의 세부정보를 저장하며 SecurityContext 내부 Authentication객체에서 가져올 수 있다.
인증은 AuthenticationManager가 진행하며 이는 인터페이스로 AuthenticationProvider 리스트를 가지고 있는 ProviderManager가 주요 구현체이다. 따라서 인증은 AuthenticationProvider를 쭉 지나며 진행된다.
마지막으로 이러한 인증 과정은 시큐리티 필터체인 내부 AbstractAuthenticationProcessingFilter에서 시작된다.
공식문서 다음부분에는 흔히사용되는 Username/Password 기반의 인증방식에서 사용되는 내용들이 나온다. 이부분과 인가방식까지 쭉쭉 이어나가보자.