개발 기록

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

Spring

> [Spring Security]- 인증 정보는 어디에 저장되는 걸까?

1z 2024. 1. 10. 14:01

 

 

 

 

 

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) SecurityContextHolderFilter

1. Request요청이 들어오고 SecurityContextHolderFilter에 진입한다.

 
 
(2) SecurityContextHolderFilter: SecurityContextRepository.loadDeferredContext() 
 

* 현재 요청(사용 가능한 경우)에 대한 보안 컨텍스트를 가져와서 반환. 

세션이 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()

 
(3) SecurityContextHolderFilter: SecurityContextHolderStrategy.setDeferredContext() 
 

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);
    }
 
 
 
 
 
(3) SecurityContextHolderFilter: chain.doFilter()
 

1.  AuthenticationFilter.dofilterInternal

 
 

2. AuthenticationFilter.successfulAuthentication

 

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

2-2. AuthenticationFilter.SecurtyContextRepository.saveContext()

 
 
 

 

 
3 . 기타 
 

1. 각 사용자  Authentication 인증 객체를 어떻게 구분하는가?

SecurityContextHolder라는 전역 객체 안에 SecurityContext에 인증 객체를 저장하는데, 이 SecurityContextHolder는 ThreadLocal에 저장되기 때문에 각기 다른 쓰레드별로 다른 SecurityContextHolder 인스턴스를 가지고 있어서 사용자 별로 각기 다른 인증 객체를 가질 수 있다.

2. 동일 계정 다른 기기에서 로그인 되어 세션이 만료된 계정에서 자원요청(Request) 

- ConcurrentSessionFilter

3. 동일 계정 다른 기기 중복 로그인 - 최대 1개 세션 허용 정책

- ConcurrentSession 에서 정책 확인

-현재 사용자 인증 시도 차단 정책: SessionAuthenticationException 예외를 발생하여 인증 처리 실패

-이전 사용자 세션 만료 정책: session.expireNow 를 사용해 이전 사용자의 세션을 만료시킨 후 자신의 인증객체 저장.

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

참고

https://catsbi.oopy.io/f9b0d83c-4775-47da-9c81-2261851fe0d0

https://www.baeldung.com/spring-security-session

https://sas-study.tistory.com/410

https://tech-monster.tistory.com/206