작성
·
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은 건든 것이 없으니 변화가 없어야 한다고 생각했는데 로그아웃이 된 이유를 모르겠습니다. (어떤 부분에서 이런 작용이 된 것인지 궁금합니다.)