인프런 커뮤니티 질문&답변

hbh님의 프로필 이미지
hbh

작성한 질문수

스프링 시큐리티

5) 인증 저장소 필터 - SecurityContextPersistenceFilter 강의에서 clearContext() 로 remove 하는 데 어떻게 컨트롤러에서 조회가 가능한가요?

작성

·

2.4K

1

SecurityContextPersistenceFilter  클래스 finally 구문에서 SecurityContext 를 remove 하는 데,

그러면 인증된 사용자 또한 SecurityContext 가 null 인데

어떻게 컨트롤러 단에서 SecurityContextHolder.getContext().getAuthentication(); 로 했을 때

null 이 아닌 이유가 궁금해서 여쭤봅니다

어디에서든 참조가능한 ThreadLocal 영역에 저장되기 때문에 SecurityContext 는 null 되도 상관없고

SecurityContextHolder.getContext() 는 ThreadLocal 에서 가져오기 때문인가요?

답변 1

3

정수원님의 프로필 이미지
정수원
지식공유자

해당 강의를 보시면 아시겠지만 SecurityContext 는 ThreadLocall 에 저장됩니다.

ThreadLocal 은 스레드마다 고유하게 할당되어 있는 저장소개념의 클래스입니다.

그리고 ThreadLocal 은 전략에 따라 SecurityContextHolder 클래스의 멤버 변수로 할당되어 있습니다.

Default 는 아래의 첫번째 전략으로 실행이 됩니다

public class SecurityContextHolder {

-- 중략

private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
// Set default
strategyName = MODE_THREADLOCAL;
}

if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
}

--중략
}

그래서 SecurityContextHolder.clearContext() 는 ThreadLocal 에 있는 SecurityContext 를 제거하는 역할입니다.

/**
* Explicitly clears the context value from the current thread.
*/
public static void clearContext() {
strategy.clearContext();
}

final class ThreadLocalSecurityContextHolderStrategy implements  SecurityContextHolderStrategy {

private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

// ThreadLocal 에서 SecurityContext 제거
public void clearContext() {
contextHolder.remove();
}

이 상태에서 SecurityContextPersistenceFilter 가 하는 주된 역할은 사용자가 인증을 시도하게 되면 처음에 Authentication 이 null  상태인 SecurityContext 객체를 SecurityContextHolder 에 담아서 다음 필터로 전달하는 것입니다.

그리고 인증을 처리하는 필터인 UsernamePasswordAuthenticationFilter 에서 인증에 성공한 이후의 과정 중에 SecurityContextHolder 에 있는 SecurityContext 객체를 꺼내어 Authentication 객체를 저장하게 됩니다.

이후 클라이언트로 응답하는 시점에 SecurityContextPersistenceFilter 의 finally 구문을 거치게 되는데 이때 SecurityContextHolder.clearContext() 를 하게 됩니다.

여기서 중요한 포인트는 SecurityContextHolder.clearContext() 가 실행되기 전에 어떤 시점에서 SecurityContext 객체를 HttpSession 에 저장하는 처리를 먼저 하게 됩니다.

@Override
protected void saveContext(SecurityContext context) {

final Authentication authentication = context.getAuthentication();

HttpSession httpSession = request.getSession(false);

--중략


if (httpSession == null) {
httpSession = createNewSessionIfAllowed(context)
;
}

if (httpSession != null) {

// 세션에 SecurityContext 객체를 저장함

if (contextChanged(context)
|| httpSession.getAttribute(
springSecurityContextKey) == null) {
httpSession.setAttribute(
springSecurityContextKey, context);

if (logger.isDebugEnabled()) {
logger.debug("SecurityContext '" + context
+
"' stored to HttpSession: '" + httpSession);
}
}
}
}

그 처리는 HttpSessionSecurityContextRepository 클래스가 하고 있습니다.

그렇게 되면 SecurityContextPersistenceFilter 는 사용자가 인증 이후의 어떤 자원에 접근을 할 때 마다 SecurityContext 객체를 SecurityContextHolder 에 담는 역할을 하게 되는데 이 때 HttpSessioin 에 SecurityContext 가 저장되어 있는지를 조회하게 됩니다

private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
final boolean debug = logger.isDebugEnabled();

if (httpSession == null) {
if (debug) {
logger.debug("No HttpSession currently exists");
}

return null;
}

// 세션에 존재한다면 세션으로 부터 SecurityContext 객체를얻어옴

Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);

if (contextFromSession == null) {
if (debug) {
logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
}

return null;
}

--중략

return (SecurityContext) contextFromSession;
}

그래서 세션안에 SecurityContext 가 존재하면 이 객체를 SecurityContextHolder 에 담고 세션에 존재하지 않으면 새롭게 생성해서 다음 필터로 전달하게 되는데 만약 이전에 인증을 성공했을 경우에는 세션에 SecurityContext 를 이미 저장했으므로 향후 모든 요청 시에는 세션에서 SecurityContext 를 꺼내어 SecurityContextHolder 에 담게 됩니다.

또한 이 SecurityContext 객체 안에는 인증 당시 저장했던 Authentication 객체도 저장되어 있습니다.

그래서 매 요청마다 세션에 저장된 SecurityContext 객체를 꺼내어서 새롭게 주어지는 요청 스레드의 ThreadLocal 에 다시 담기 때문에 응답하기 전에 SecurityContextHolder.clearContext() 를 실행시키는 것은 정상적인 과정이며 문제가 되지 않습니다.

그리고 기억하실 점은 사용자가 요청을 할 때 마다 요청 스레드 객체는 매번 달라질 것이므로 메모리 누수가 생기지 않도록 현재 요청 스레드 객체의 ThreadLocal 에 저장된 SecurityContext 객체는 최종 응답전에 remove 해 주어야 합니다. 

즉 SecurityContextHolder.clearContext() 처리를 해 주는 것이 맞습니다.

제가 강의에 이 부분에 대해서 설명하고 있으니 다시 한번 해당 강좌를 참고해 주시기 바랍니다

감사합니다.

제가 컨트롤러에 임의로 SecurityContextHolder에 새로운 authentication을 넣었더니 로그인 된 세션이 없어졌습니다. (로그아웃된 상태가 되었다.) 이건 어떻게 된 일인지 모르겠습니다.

응답이 다 끝나면 SecurityContextHolder를 clear 해줄태고 Session은 건든 것이 없으니 변화가 없어야 한다고 생각했는데 로그아웃이 된 이유를 모르겠습니다. (어떤 부분에서 이런 작용이 된 것인지 궁금합니다.)

hbh님의 프로필 이미지
hbh

작성한 질문수

질문하기