2025-11-04 Spring security

Ckd gus·2026년 2월 2일

ThreadLocal 정리 (블로그용)

ThreadLocal이란?

ThreadLocal은 각 스레드(Thread)마다 독립적인 변수 저장소를 제공하는 Java 클래스이다.
즉, 같은 ThreadLocal 객체를 공유하더라도 스레드별로 서로 다른 값을 가진다.


동작 원리

  • 각 스레드는 ThreadLocal에 대해 자기만의 값을 가진다.
  • 스레드가 ThreadLocal.set(value)를 호출하면,
    • 내부적으로 현재 Thread 객체 내부에 있는 ThreadLocalMap에 값이 저장된다.
  • 다른 스레드는 자기 ThreadLocalMap을 보기 때문에 그 값을 볼 수 없다.

결론: ThreadLocal은 “공유 변수”처럼 보이지만, 실제 값 저장은 “스레드 내부”에서 분리되어 일어난다.


ThreadLocal의 장점

  • 스레드 안전성(Thread-Safety)
    스레드마다 독립 저장이므로 동기화(synchronized) 없이도 경쟁 조건을 피할 수 있다.

  • 간편한 상태 유지
    요청-응답 동안 필요한 상태(예: 인증된 사용자 정보)를 파라미터로 계속 전달하지 않고 유지할 수 있다.

  • 코드 간결성
    전역 변수/싱글톤에 상태를 저장하지 않고도 요청 스코프 데이터를 다룰 수 있어 구조가 깔끔해진다.


(중요) ThreadLocal의 위험 포인트

ThreadLocal 자체는 편하지만, 서버는 보통 스레드 풀(Thread Pool) 구조로 동작한다.

  • 요청 A 처리한 스레드가 풀로 반납됨
  • 요청 B가 들어오면 같은 스레드가 재사용될 수 있음
  • 이때 ThreadLocal 값이 남아 있으면,
    • 다른 사용자 정보가 섞이거나
    • 보안 사고가 날 수 있다.

그래서 요청이 끝나는 시점에 반드시 정리해야 한다.


Tomcat 스레드 풀 (Spring Boot 설정 개념)

Spring Boot 내장 Tomcat은 기본적으로 스레드 풀을 사용한다.
application.yml에서 조절할 수 있다.

예시(형태만 참고):

server:
  tomcat:
    max-threads: 200        # 최대 워커 스레드 수(기본값)
    min-spare-threads: 10   # 최소 여유 스레드 수(기본값)


Spring Boot 요청 처리 흐름에서 ThreadLocal의 위치

1) 요청이 들어오면 Tomcat 스레드 풀에서 스레드 하나를 할당
2) 하나의 HTTP 요청은 시작~끝까지 보통 같은 스레드에서 처리
3) 인증 필터(예: Spring Security Filter)에서 인증 성공 시 ThreadLocal에 사용자 정보 저장
4) Controller/Service에서 ThreadLocal.get()으로 사용자 정보 사용
5) 응답 반환
6) finally 등에서 반드시 ThreadLocal.remove()로 정리
7) 스레드가 풀로 반납


ThreadLocal.clear(remove)를 꼭 해야 하는 이유

  • 스레드가 재사용되기 때문에 이전 요청의 정보가 다음 요청에 누출될 수 있음
  • 결과:
    • 사용자 정보 오염
    • 권한 꼬임
    • 보안 취약점

따라서 remove()는 선택이 아니라 “필수”에 가깝다.


ThreadLocal 사용 예시

1) 사용자 클래스

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2) ThreadLocal 컨텍스트

public class UserContext {
    private static final ThreadLocal<User> userThreadLocal =
            ThreadLocal.withInitial(() -> null);

    public static void setUser(User user) {
        userThreadLocal.set(user);
    }

    public static User getUser() {
        return userThreadLocal.get();
    }

    public static void clear() {
        userThreadLocal.remove();
    }
}

메서드 요약

  • setUser() : 현재 스레드에 사용자 저장
  • getUser() : 현재 스레드의 사용자 조회
  • clear() : 현재 스레드의 ThreadLocal 값 제거 (요청 끝나면 반드시)

Spring Security Filter 개념

Spring Security Filter는 컨트롤러에 도달하기 전에 동작하는 보안 필터 체인(Filter Chain)의 일부다.
요청이 들어오면 “중간 관문”처럼 인증(Authentication)과 인가(Authorization)를 처리한다.


요청 처리 흐름 요약

Client → Servlet Container
→ Filter 단계
→ (Spring Security Filter Chain: 인증/인가, 세션 관리, CSRF/CORS 등)
→ DispatcherServlet(Front Controller)
→ Interceptor 단계(컨트롤러 전/후 공통 로직)
→ Controller / Service / Repository
→ Response


느낀점

Security를 본격적으로 들어가면 “요청 흐름(Filter → DispatcherServlet → Controller)”이 계속 핵심이 된다.
ThreadLocal은 인증된 사용자 상태를 “요청 단위로 유지”하는 데 유용하지만,
스레드 풀 재사용 때문에 remove() 정리 습관이 없으면 바로 사고 난다는 점이 가장 중요하다.

profile
백엔드 공부중입니다.

0개의 댓글