Spring Security와 ThreadLocal

2025. 8. 29. 10:32·Java

도입

Spirng Security는 기본적으로 ThreadLocal을 사용한다. ThreadLocal이 무엇인지, 왜 사용하는지 좀 더 자세히 알아보고자 한다.

ThreadLocal이란?

각 스레드마다 독립적인 변수 공간을 제공하는 Java 클래스이다.
HTTP 요청당 하나의 스레드가 할당되어 요청 처리 동안 인증 정보를 유지할 수 있다.
따라서 같은 스레드 내에서만 SecurityContext에 접근 가능하다.

Spring Security Context가 ThreadLocal 기반인 이유

웹 애플리케이션의 멀티스레드 환경 특성과 보안 요구사항 때문!

  1. 스레드별 컨텍스트 분리
  • 각 HTTP 요청은 별도의 스레드에 의해 처리된다. SecurityContext는 각 요청의 인증정보를 담고 있기 때문에 각 스레드별로 다른 정보를 담아야 하고, 다른 스레드의 컨텍스트에 접근할 수 없어야 한다. 그리고 스레드별로 독립된 공간에서 관리되어야 한다.
  1. 접근의 용이성
  • 요청 처리 동안 여러 클래스에서 SecurityContext에 접근해서 인증 정보를 얻어올 수 있어야 한다. 따라서 매번 모든 메서드의 파라미터로 인증 정보를 넘기지 않아도 된다.
  1. 요청 후 정리
  • 요청 처리가 끝난 후 스레드는 스레드풀로 반환되고 다른 요청 처리를 위해 재사용된다. 따라서 요청이 끝날 때 보안 컨텍스트를 쉽게 지울 수 있어야 한다.

sse 구현 중 활용

    // 사용자별 heartbeat 작업을 관리하는 맵
    private final Map<Long, ScheduledFuture<?>> heartbeatTasks = new ConcurrentHashMap<>();
    (중략)

   // 연결 유지를 위한 heartbeat 메시지 전송 (ScheduledExecutorService 사용)
    private void startHeartbeat(Long userId, SseEmitter emitter) {
        // 현재 SecurityContext 캡처 (HTTP 요청 스레드에서)
        SecurityContext securityContext = SecurityContextHolder.getContext();

        // 초기 연결 메시지를 즉시 전송 (연결 안정화)
        ScheduledFuture<?> initialTask = heartbeatScheduler.schedule(() -> {
            SecurityContextHolder.setContext(securityContext);
            try {
                if (emitters.containsKey(userId) && emitter != null) {
                    emitter.send(SseEmitter.event()
                            .name("connection")
                            .data("connected"));
                }
            } catch (Exception e) {
            } finally {
                SecurityContextHolder.clearContext();
            }
        }, 100, TimeUnit.MILLISECONDS);

        // 15초마다 heartbeat 메시지 전송 (연결 끊김 즉시 감지)
        ScheduledFuture<?> heartbeatTask = heartbeatScheduler.scheduleAtFixedRate(() -> {
            // SecurityContext를 스케줄러 스레드에 설정
            SecurityContextHolder.setContext(securityContext);

            try {
                if (emitters.containsKey(userId) && emitter != null) {
                    // Emitter 상태 체크
                    try {
                        emitter.send(SseEmitter.event()
                                .name("ping")
                                .data("ping"));

                    } catch (IllegalStateException e) {
                        // ResponseBodyEmitter has already completed
                        cleanupConnection(userId);
                    }
                } else {
                    cleanupConnection(userId);
                }
            } catch (IOException e) {
                cleanupConnection(userId);
            } catch (Exception e) {
                cleanupConnection(userId);
            } finally {
                // SecurityContext 정리
                SecurityContextHolder.clearContext();
            }
        }, 15, 15, TimeUnit.SECONDS); // 15초 후 시작, 15초마다 반복

        // heartbeat 작업 저장
        heartbeatTasks.put(userId, heartbeatTask);
    }

sse 연결 유지를 위해 ScheduledExecutor를 사용해야하는데, 이것은 백그라운드에서 주기적인 작업을 실행하는 별도의 스레드풀이다.
ScheduledExecutor의 주요 특징은 HTTP 요청을 처리하는 스레드와는 완전히 다른 스레드에서 동작한다는 것이며, 따라서 HTTP 요청 스레드의 ThreadLocal 정보에 접근할 수 없다.

하지만 heartbeat 작업이 ScheduledExecutor에서 실행되는데, 이때 SecurityContext가 필요한 상황이다.

따라서 HTTP 요청 스레드에서 SecurityContext를 캡처하고, SecurityContextHolder.setContext(securityContext)을 이용해 수동으로 전설정한다. 마지막에는 clearContext를 해줌으로써 안전하게 멀티스레드 환경에서 Spring Security Context 전파 문제를 해결할 수 있다.

마무리

Spring Security의 ThreadLocal 사용은 멀티스레드 웹 환경에서 요청별 인증 정보를 안전하고 편리하게 관리하기 위한 필수적 설계이다. 스레드별 독립성, 파라미터 전달 불필요, 자동 라이프사이클 관리라는 3가지 이점을 제공하며, 이는 현대 웹 애플리케이션의 보안 아키텍처에서 핵심적인 역할을 한다.

저작자표시 비영리 변경금지 (새창열림)

'Java' 카테고리의 다른 글

[Spring] 의존성 주입(Dependency Injection, DI)  (0) 2025.06.04
'Java' 카테고리의 다른 글
  • [Spring] 의존성 주입(Dependency Injection, DI)
버그잡는고양이발
버그잡는고양이발
주니어 개발자입니다!
  • 버그잡는고양이발
    지극히평범한개발블로그
    버그잡는고양이발
  • 전체
    오늘
    어제
    • 분류 전체보기 (382)
      • React (16)
      • Next.js (5)
      • Javascript (5)
      • Typescript (4)
      • Node.js (2)
      • Cs (16)
      • 트러블 슈팅 (5)
      • Html (1)
      • Css (3)
      • Django (0)
      • vue (0)
      • Java (2)
      • Python (0)
      • 독서 (1)
      • 기타 (3)
      • 백준 (192)
      • swea (31)
      • 프로그래머스 (30)
      • 이코테 (4)
      • 99클럽 코테 스터디 (30)
      • ssafy (31)
      • IT기사 (1)
  • 블로그 메뉴

    • 홈
    • 태그
  • 인기 글

  • 태그

    개발자취업
    항해99
    99클럽
    코딩테스트준비
    Til
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
버그잡는고양이발
Spring Security와 ThreadLocal
상단으로

티스토리툴바