SecurityContext
SecurityContextHolder
SecurityContext을 감싸고 있는 클래스로 SecurityContext를 저장하는 방식이 4가지가 있다.
- MODE_THREADLOCAL : 스레드당 SecurityContext 객체를 할당, 기본값
- MODE_INHERITABLETHREADLOCAL : 메인 스레드와 자식 스레드에 관하여 동일한 SecurityContext 를 유지
- MODE_GLOBAL : 응용 프로그램에서 단 하나의 SecurityContext를 저장한다
- MODE_PRE_INITIALIZED : (찾아봐야함)
그림으로 보는 플로우
유저가 Login을 시도하면 ThreadLocal에 할당이 되고 인증객체를 만들어 인증필터가 인증을 시도한다.
만약 인증에 실패하면 SecurityContextHolder.clearContext() 로 SecurityContext 기존 정보 초기화한다.
성공하게 된다면 ThreadLocal이 SecurityContext를 담은 형태로 SecurityContextHolder에 저장하게 되고 최종적으로 HttpSession 에 저장되어 어플리케이션 전반에 걸쳐 전역적인 참조가 가능하다
코드로 보는 플로우
로그인을 시도한다.
로그인 후 인증이 성공하면 SecurityContextHolder에 인증객체가 저장되고 자원에 접근하게 된다. 인증객체를 얻는 방법은 2가지가 있는데 첫번째 방법은 SecurityContextHolder.getContext().getAuthentication(); 이용해 인증객체를 얻는 것이고
두번째 방법은 HttpSession 인자로 받고 세션에 "SPRING_SECURITY_CONTEXT" 키값으로 저장된 인증객체를 가져오는 방법이다.
만약 SecurityContext 가 MODE_THREADLOCAL 모드라면 자식쓰레드와 메인쓰레드간에 SecurityContext를 공유하지 않기 때문에 아래 코드에서의 authentication는 NULL 이 나오게 된다.
@GetMapping("/thread")
public String thread() {
new Thread(
new Runnable() {
@Override
public void run() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
}
}
).start();
return "thread";
}
아래 처럼 MODE_INHERITABLETHREADLOCAL 모드로 설정해주면 자식쓰레드와 메인 쓰레드간의 SecurityContext를 공유하게 된다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.permitAll();
http
.formLogin();
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
return http.build();
}