여기까지 본인이 어떤 방법으로 기본 정보들을 등록할지 선택하면 된다. 나는 설정파일에 작성했다.
이유는 민감정보가 들어가있고(client-id, cllient-secret) 정보를 한 곳에서 관리하는 게 편해서 yaml 파일로 관리하기로 했다.
SpringFilterChain
이게 Spring Security로 구글 로그인이 성공해다면 어떻게 동작하는지 알아보자
Spring Security의 핵심은 SpringFilterChain이다. SpringFilterChain을 기반으로 동작하기 때문이다.
SpringFilterChain은 스프링이 관리하는 "필터"이다. = Spring이 Bean으로 관리가 가능한 특수한 Filter
Spring Boot에는 필터와 인터셉터가 있는데 차이점은 아래와 같다.
Filter
Interceptor
톰캣이 관리, 빈 등록이 불가능
스프링이 관리, 빈 등록 가능, 스프링이 제공하는 기능 확용(AOP, DI 등)
DispatcherServlet 이전에 위치하여 요청을 처리한다.
DispaterServlet 뒤에 위치
MVC 상 공통 처리를 수행한다(토큰 유효성 검사 등, 서블릭 도달 전에 다 처리)
스프링 컨트롤러 요청, 반환에 대해서만 적용(서블릿 거쳐서 컨트롤러 가기 전에)
doFilter() 로 수행
preHandler(), postHandler(), afterCompletion()
톰캣이 관리하므로 WS처리도 수행한다.(정적 리소스 반환 등)
톰캣이 관리하는 영역에 대해서는 영향을 주지 못함
SpringFilterChain이 빈으로 등록된다고? 필터인데?
Spring에서 @EnableWebSecurity 이 어노테이션을 사용해서 (Application)FilterChain에 체결된다.
그래서 스프링이 관리하는 빈이지만 FilterChain에 연결되어 있으므로 DistacherServlet에 도달하기 전에 Filter 처럼 역할을 해준다.
DelegatigFilterProxy가 SecurityFilterChain을 거치도록 해준다.
DelegatingFilterProxy - DelegatingFilterProxy 등장 이전: 스프링 빈으로 등록 및 다른 빈 주입 불가능 - DelegatingFilterProxy 등장 이후: 스프링 빈으로 등록 및 다른 빈 주입 가능 - Spring Boot 등장 이후: 직접 내장 WAS 설정 -> DelegatingFilterProxy 설정 없어도 자동 설정 자세한 내용을 보고 싶다면 링크를 참조 추가: spring security가 빈 초기화하는 과정
Spring Security 버전별 필터 등록 방법
Spring Security 5.4(포함)이전 filter chain으로 등록할 클래스에 어노테이션 붙이고 , WebSecurityConfigurer 을 구현
또는
WebSecurityConfigurerAdapter 를 상속받은 SecurityConfig 클래스 구현
Spring Security 5.7+
Component based Security Confituartion설정을 사용
@EnableWebSecurity + @Configuration을 붙여서 설정 빈으로 등록
위에 처럼 설정 파일 빈으로 등록 + 필터 활성화 한 후 @EnableMethodSecurity를 통해 메서드 단위로 로그인 세션의 역할에 따른 보안 설정 가능
Spring Security를 사용한 인증 방식 과정
Authentication Filter만 잘 이해하자
SpringFilterChain에 정말 여러가지 필터가 있지만, 지금 구현에서는 Authentication Filter만 이해해도 충분하다.
큰 흐름은
AuthenticationFilter
AuthenticationManager
AuthenticationProvider
순이다.
위 흐름에 대한 설명을 할건데 모르는 단어가 나와도 그냥 넘어가자. 대충 이런 흐름으로 흘러간다만 이해하고 뒤에서 용어 설명을 보고 다시 흐름을 이해하자
1. AuthenticationFilter(미인증 토큰을 만든다)
AuthenticationToken(로그인 정보를 담고 있음)을 생성해서 로그인한 회원 정보를 SecurityContextHolder(세션 개념으로 생각)에 저장
2. AuthenticationManager(토큰을 인증하기 위한 Provider 탐색)
AutheticationManaer의 구현체인 ProviderManager를 사용해서 Filter에서 생성한 Token(AuhenticationToken)을 인증하기 위한 AuthenticationProvider 탐색
AuthenticationToken 인증에 사용될 클라이언트(요청자)가보낸 정보이다. -> Authentication 인터페이스 구현체 예) UsernamePasswordAuthenticationToken: usernamer과 Password 를 저장(username: principal역할, password → credential 역할)
Authentification 인터페이스 구조
Principal : 접근 주체의 아이디 혹은 User 객체를 저장합니다.
Credentials : 접근 주체의 비밀번호를 저장합니다.
Authorities : 인증된 접근 주체자의 권한 목록을 저장합니다.
Details : 인증에 대한 부가 정보를 저장합니다.
Authenticated : boolean 타입의 인증 여부를 저장합니다.
SecurityContextHolder
SecurityContextHolder는 기본적으로 ThreadLocal 사용, 따라서 동시에 요청해도 다른 객체를 생성하므로 동시 요청에 충돌이 발생하지 않도록 구현되어 있다.
= 한 스레드 내에서 쉐어하는 저장소
= 한 스레드에서 Authentication 공유 가능
다른 스레드 요청에서는 다시 인증해야함
여기서부터는 개인적인 생각 그럼 만약 한 사람이 여러 요청을 하면 요청마다 다른 스레드가 생성될 것이고, 요청할 때마다 인증, 인가 과정을 거쳐야한다? 비효율적이지 않은가 했는데 요청마다 새로운 SecurityContextHolder를 제공하고, 요청이 끝나면 정보를 초기화해서 개인정보가 유출되는 경우는 없을 거 같다. ThreadPool을 사용하고 있다면 무조건 정보를 초기화하는게 맞음. 다른 요청에 스레드를 재사용할텐데 여기서 초기화를 안하면 개인 정보가 유출될 수도 있다. 참고: TreadLoal에 대한 자세한 설명
지금까지 SpringFilterChain에서 인증방식이 어떻게 진행되는지 간단하게 살펴보았다.