KKanging

[SpringSecurity] SecurityContextHolder ,Security Context, Authentication 본문

백엔드/SpringSecurity

[SpringSecurity] SecurityContextHolder ,Security Context, Authentication

천방지축 개발자 2024. 5. 19. 04:52

Security에서 상태를 저장해야하는 이유

SpringSecurity에서 구현된 핵심 로직은 SecurityFilterChain 의 Filter들을 거치면서 수행된다.

현재 요청을 보낸 상태를 저장해야 다음 Filter들이 요청의 상태를 확인하여 로직을 수행한다.

SecurityContextHolder

SecurityContextHolder가 현재 요청을 보낸 사용자들의 상태를 관리한다. 그리고 SecurityContext에 해당하는 정보인 Authentication 객체를 담는다.

Authentication 객체는 SecurityContext에 포함되어 관리되며 SecurityContext는 0개 이상 존재할 수 있다.

그리고 이 N개의 SecurityContext는 하나의 SecurityContextHolder에 의해서 관리된다.

  • Authentication 객체
    • Principal : 유저에 대한 정보 → 식별할 값
    • Credentials : 증명 (비밀번호, 토큰) → 종종 비밀번호이다. 대부분의 경우 유출되지 않도록 하기 위해 사용자가 인증된 후 지워진다.
    • Authorities : 유저의 권한(ROLE) 목록

SecurityContext Holder 접근

SecurityContextHolder.getContext().getAuthentication().getAuthorities();
  • SecurityContextHolder 클래스 내부

static 메서드인 getContext를 통해서 SecurityContext에 접근한다

→ 그 다음에 Authentication 객체에 접근한다.

근데 코드를 보면 SecurityContextHolder에서 바로 Context 를 반환하는게 아니라 strategy를 통해서 가져온다.

Strategy 클래스

SecurityContextHolder 클래스에 SecurityContext 클래스는 없고 이상한  SecurityContextHolderStrategy 클래스가 있는것을 확인할 수 있다.

- **특이 사항**

다수의 사용자인 멀티 쓰레드 환경에서 SecurityContextHolder를 통해 SecurityContext를 부여하는  관리 전략은 위임하여 다른 클래스에게 맡긴다.

(사용자별로 다른 저장소를 제공해야 인증 정보가 겹치는 일이 발생하지 않는다.)

즉, SecurityContextHolder는 SecurityContext들을 관리하는 메소드를 제공하지만 
실제로 등록, 초기화, 읽기와 같은 작업은 SecurityContextHolderStrategy 인터페이스를 활용한다.

 

  • SecurityContextHolder 내부
private static void initializeStrategy() {

	if (MODE_PRE_INITIALIZED.equals(strategyName)) {
		Assert.state(strategy != null, "When using " + MODE_PRE_INITIALIZED
				+ ", setContextHolderStrategy must be called with the fully constructed strategy");
		return;
	}
	if (!StringUtils.hasText(strategyName)) {
		// Set default
		strategyName = MODE_THREADLOCAL;
	}
	if (strategyName.equals(MODE_THREADLOCAL)) {
		strategy = new ThreadLocalSecurityContextHolderStrategy();
		return;
	}
	if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
		strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
		return;
	}
	if (strategyName.equals(MODE_GLOBAL)) {
		strategy = new GlobalSecurityContextHolderStrategy();
		return;
	}
	// Try to load a custom strategy
	try {
		Class<?> clazz = Class.forName(strategyName);
		Constructor<?> customStrategy = clazz.getConstructor();
		strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
	}
	catch (Exception ex) {
		ReflectionUtils.handleReflectionException(ex);
	}
}

ThreadLocal 방식에서 SecurityContext

  • ThreadLocalSecurityContextHolderStrategy

ThreadLocalSecurityContextHolderStrategy 의 내부 코드를 보면 SecurityContext를 ThreadLocal 로 감싼 형태로 가지고 있다 ThreadLocal은 다음 링크를 통해 확인할 수 있다.

 

[JAVA] ThreadLocal (tistory.com)

SecurityContext 의 생명주기

ThreadLocal 은 Thread-Safe 하지만 쓰레드 풀을 사용할 시에는 항상 요청이 끝나면 TheadLocal을 비워줘야하는데 FilterChainProxy는 이를 보장한다.

→ 항상 요청이 끝나는 시점에 Context를 비운다.

요약

Authentication : 사용자 인증 관련 주체( 인증 정보) , creditial은 인증이 끝나면 지워짐

SecurityContext : 사용자의 인증 주체를 담는 그릇 , 요청마다 생성

SecurityContextHolder : SecurityContext를 간접적으로 가지고있는 계층 N개의 사용자가 요청을 보내면 N개의 SecurityContext를 가지고 있음

참고 문헌

스프링 시큐리티 내부 구조 6 : SecurityContextHolder (youtube.com)

Servlet Authentication Architecture :: Spring Security