A 요청에서 B의 정보가 나왔다 — ThreadLocal을 안 지운 대가

요청마다 사용자 정보를 ThreadLocal에 담아 두고 어디서든 꺼내 쓰는 구조를 만들었습니다. 편했습니다. 그런데 가끔 A 사용자의 요청에서 B 사용자의 정보가 튀어나오는 아찔한 버그가 보고됐습니다.

스레드는 재사용된다

톰캣이든 스프링이든 요청은 스레드 풀에서 스레드를 빌려 처리하고, 끝나면 그 스레드를 반납합니다. 반납된 스레드는 다음 요청에 다시 쓰이죠. 그런데 ThreadLocal에 담아 둔 값을 지우지 않으면, 그 값이 다음 요청까지 그대로 남아 있습니다. B의 요청이 A가 쓰던 스레드를 물려받으면서 A의 값을 본 겁니다. 값 오염이자 사용자 정보 유출로 이어질 수 있는 위험한 상황이었습니다.

반드시 지운다

try {
    UserContext.set(user);
    chain.doFilter(req, res);
} finally {
    UserContext.clear(); // remove() 호출
}

필터 끝의 finally에서 무조건 remove()를 부르도록 바꿨습니다. ThreadLocal은 담는 것보다 지우는 게 핵심이라는 걸 이때 배웠습니다.