개발 기록
> [Spring Security]- 인증 정보는 어디에 저장되는 걸까? 본문

ThreadLocal에 저장되어 아무 곳에서나 참조가 가능하도록 설계함
→ ThreadLocal: Thread마다 할당된 고유 공간( Thread 공유X) ->다른 Thread로부터 안전하다.
인증이 완료되면 HttpSession에 저장되어 어플리케이션 전반에 걸쳐 전역적인 참조가 가능하다.
■
1. 전체 흐름


☞ 1 사용자가 Request 요청
☞ 2 SecurityContextHolderFilter 수행
☞ 2-1 HttpSecurityContextRepository.loadDeferredContext() 실행
※ HttpSecurityContextRepository: SecurityContext 객체를 생성, 조회 하는 역할을 하는 클래스
☞ 2-1 HttpSecurityHolderStrategy.setDeferredContext() 실행
☞ 2-1 Chain.doFilter() 실행
★ (인증 전)
i.새 로운 SecurityContext 객체를 생성하여 SecurityContextHolder(ThreadLocal 할당)에 저장한다
ii. 그 다음 필터로 이동한다(chain.doFIlter)
iii. 인증 필터(AuthFilter)가 인증을 처리 한다.
iv. 인증이 완료되면 인증객체(Authentication)생성 후
SecurityContextHolder 내부 SecurityContextHolderStrategy(= SecurityContext) 에 인증객체(Authentication)저장
==> ThreadLocal.set(SecurityContext)
※ securityContextHolderStrategy.setContext()
V. 인증이 최종 완료되면 HttpSession 에 SecurityContext를 저장한다. : (SPRING_SECURITY_CONTEXT라는 이름으로 저장된다.)
※ securityContextRepository.saveContext()
★(인증 후)
i. Session에서 SecurityContext가 있는지 확인 → 인증이 된 이후이기에 존재한다.
ii. Session에서 SecurityContext 꺼내어 SecurityContextHolder에서 저장한다.
iii. 다음 필터 수행(chain.doFilter)
※ SecurityContext안에 Authentication 객체가 존재하면 계속 인증을 유지한다.
★ (인증 실패)
i. SecurityContextHolder.clearContext() 인증객체 초기화
■
2. Flow 확인
1. Request요청이 들어오고 SecurityContextHolderFilter에 진입한다.

* 현재 요청(사용 가능한 경우)에 대한 보안 컨텍스트를 가져와서 반환.
세션이 null인 경우 컨텍스트 개체는 null이거나 세션에 저장된 컨텍스트 개체.

1. HttpSessionSecurityContextRepository.loadContext()
public class HttpSessionSecurityContextRepository implements SecurityContextRepository { @Override
.
.
.
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
context = generateNewContext();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Created %s", context));
}
}
if (response != null) {
SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,
httpSession != null, context);
wrappedResponse.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
requestResponseHolder.setResponse(wrappedResponse);
requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));
}
return context;
}
.
.
}
2. HttpSessionSecurityContextRepository.readSecurityContextFromSession()

SecurityContext객체를 바로 저장하는 것은 아니고, 저장 방식(3가지 MODE)에 따라 저장.
- MODE_THREADLOCAL: 스레드당 SecurityContext객체를 할당. default
- MODE_INHERITABLETHREADLOCAL: 메인 스레드와 자식 스레드에 관하여 동일한 SecurityContext를 유지 → Parent, Child thread에서 동일한 SecurityContext를 가진다.
- MODE_GLOBAL: 응용 프로그램에서 단 하나의 SecurityContext를 저장한다. → Memory에서 단 하나의 SecurityContext를 참조
1. ThreadLocalSecurityContextHolderStategy
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
private static final ThreadLocal<Supplier<SecurityContext>> contextHolder = new ThreadLocal<>();
.
.
.
@Override
public void setDeferredContext(Supplier<SecurityContext> deferredContext) {
Assert.notNull(deferredContext, "Only non-null Supplier instances are permitted");
Supplier<SecurityContext> notNullDeferredContext = () -> {
SecurityContext result = deferredContext.get();
Assert.notNull(result, "A Supplier<SecurityContext> returned null and is not allowed.");
return result;
};
contextHolder.set(notNullDeferredContext);
}
1. AuthenticationFilter.dofilterInternal

2. AuthenticationFilter.successfulAuthentication

2-1. AuthenticationFilter.: SecurityContexetHolerStrategy.saveContext()

2-2. AuthenticationFilter.SecurtyContextRepository.saveContext()

■
1. 각 사용자 Authentication 인증 객체를 어떻게 구분하는가?
SecurityContextHolder라는 전역 객체 안에 SecurityContext에 인증 객체를 저장하는데, 이 SecurityContextHolder는 ThreadLocal에 저장되기 때문에 각기 다른 쓰레드별로 다른 SecurityContextHolder 인스턴스를 가지고 있어서 사용자 별로 각기 다른 인증 객체를 가질 수 있다.
2. 동일 계정 다른 기기에서 로그인 되어 세션이 만료된 계정에서 자원요청(Request)
- ConcurrentSessionFilter
3. 동일 계정 다른 기기 중복 로그인 - 최대 1개 세션 허용 정책
- ConcurrentSession 에서 정책 확인
-현재 사용자 인증 시도 차단 정책: SessionAuthenticationException 예외를 발생하여 인증 처리 실패
-이전 사용자 세션 만료 정책: session.expireNow 를 사용해 이전 사용자의 세션을 만료시킨 후 자신의 인증객체 저장.
참고
https://www.baeldung.com/spring-security-session
https://sas-study.tistory.com/410
https://tech-monster.tistory.com/206