Thread Per Request 모델ThreadLocalSecurityContextHolderSecurityContextHolderStrategySecurityContextAuthentication(구현체가 Token)포함정보
Thread Per Request 모델
- Thread Per Request 모델 개요(Spring MVC는 이 모델을 기반으로 함)
- WAS와 같은 ServletContainer에서 ThreadPool을 생성함 (Tomcat 기본값 200)
- HTTP 요청이 들어오면 Queue에 적재되고, ThreadPool 내의 특정 Thread가 Queue에서 요청을 가져와 처리하게됨
- HTTP 요청은 처음부터 끝까지 동일한 Thread에서 처리됨
- HTTP 요청 처리가 끝나면 Thread는 다시 ThreadPool에 반납됨
- 즉, WAS의 최대 동시 처리 HTTP 요청의 갯수는 ThreadPool의 갯수와 같음
- Thread 갯수를 늘리면 동시 처리 갯수가 늘어나지만, Thread Context 스위칭에 의한 오버헤드도 커지기 때문에 성능이 선형적으로 증가 하지는 않음
- 최근 소개된 WebFlux 같은 기술은 Thread 갯수를 작은 갯수로 유지하며 HTTP 요청을 동시 처리 할 수 있도록 함
- HTTP 요청은 하나 이상의 Thread에 바인딩되어 처리될 수 있음
ThreadLocal
- 테스트 코드
package com.prgms.devcourse.springsecuritymasterclass; import java.util.concurrent.CompletableFuture; public class ThreadLocalApp { final static ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>(); public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + " ### main set value = 1"); threadLocalValue.set(1); a(); b(); CompletableFuture<Void> task = CompletableFuture.runAsync(() -> { a(); b(); }); task.join(); } public static void a(){ Integer value = threadLocalValue.get(); System.out.println(Thread.currentThread().getName() + " ### a() get value = " +value); } public static void b(){ Integer value = threadLocalValue.get(); System.out.println(Thread.currentThread().getName() + " ### b() get value = " +value); } } main ### main set value = 1 main ### a() get value = 1 main ### b() get value = 1 ForkJoinPool.commonPool-worker-1 ### a() get value = null ForkJoinPool.commonPool-worker-1 ### b() get value = null
- Thread 범위 변수 — 동일 Thread 내에서는 언제든 ThreadLocal 변수에 접근할 수 있음
// TheadLocal 변수를 생성 ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>(); // ThreadLocal 변수에 값 쓰기 threadLocalValue.set(1); // ThradLocal 변수에서 값 읽기 Integer result = threadLocalValue.get(); // ThreadLocal 변수 값 제거 threadLocal.remove();
- 즉, 동일 Thread내에서 실행되는 Controller, Service, Repository, 도메인 모델 어디에서든 명시적인 파라미터 전달 필요없이 ThreadLocal 변수에 접근할 수 있음
- ThreadPool과 함께 사용하는 경우 Thread가 ThreadPool에 반환되기 직전 ThreadLocal 변수 값을 반드시 제거해야함
- 그렇지 않을 경우 아래와 같은 상황이 발생하고, 미묘한 버그가 생겨날 수 있음
- 요청을 처리하기 위해 ThreadPool에서 Thread를 하나 가져옴
- 요청 처리에 필요한 변수를 ThreadLocal에 set함
- 요청 처리가 완료되고 Thread는 ThreadPool에 반환됨
- 다른 요청을 처리하기 위해 ThreadPool에서 Thread를 하나 가져왔는데 이전 요청 처리에 사용된 ThreadLocal 변수가 남아있고, 이를 참조하여 잘못된 동작을 수행할 수 있음
SecurityContextHolder
- SecurityContextHolder는 SecurityContext 데이터를 쓰거나 읽을수 있는 API를 제공 (기본 구현은 ThreadLocal를 이용함)
At the heart of Spring Security’s authentication model is the SecurityContextHolder. It contains the SecurityContext.
public class SecurityContextHolder { // ... 생략 ... private static SecurityContextHolderStrategy strategy; public static void clearContext() { strategy.clearContext(); } public static SecurityContext getContext() { return strategy.getContext(); } public static void setContext(SecurityContext context) { strategy.setContext(context); } // ... 생략 ... } /** * SecurityContextHolderStrategy 전략패턴 인터페이스 */ public interface SecurityContextHolderStrategy { void clearContext(); SecurityContext getContext(); void setContext(SecurityContext context); SecurityContext createEmptyContext(); } /** * SecurityContextHolderStrategy 인터페이스 ThreadLocal 구현체 */ final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>(); @Override public void clearContext() { contextHolder.remove(); } @Override public SecurityContext getContext() { SecurityContext ctx = contextHolder.get(); if (ctx == null) { ctx = createEmptyContext(); contextHolder.set(ctx); } return ctx; } @Override public void setContext(SecurityContext context) { Assert.notNull(context, "Only non-null SecurityContext instances are permitted"); contextHolder.set(context); } @Override public SecurityContext createEmptyContext() { return new SecurityContextImpl(); } }
SecurityContextHolderStrategy

ThreadLocalSecurityContextHolderStrategy
을 기본 구현으로 사용한다는 것은 Thread Per Request 모델을 기본 고려했음을 의미함 (물론 Spring Security는 Webflux를 지원하기도 함)- FilterChainProxy 구현을 보면 finally 블록에서 SecurityContextHolder.clearContext() 메소드를 호출하는 확인할 수 있음
- 이것은 HTTP 요청 처리가 완료되고 Thread가 ThreadPool에 반환되기전 ThreadLocal 변수 값을 제거하기 위함
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean clearContext = request.getAttribute(FILTER_APPLIED) == null; if (!clearContext) { doFilterInternal(request, response, chain); return; } try { request.setAttribute(FILTER_APPLIED, Boolean.TRUE); doFilterInternal(request, response, chain); } catch (RequestRejectedException ex) { this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex); } finally { SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); } }
SecurityContext
- SecurityContextHolder 클래스를 통해 코드 어느 부분에서든 SecurityContext에 접근할 수 있음
- getContext( )가 내부적으로 contextHolder.get( )을 호출하는데 contextHolder가 ThreadLocal 타입이기 때문에 어디서든지 접근이 가능한 것임. ThreadPerRequest 모델!
SecurityContext context = SecurityContextHolder.getContext(); // ... 생략 ... SecurityContextHolder.setContext(context);
- SecurityContext 자체는 어떤 특별한 기능을 제공하지 않음
- 단순히 org.springframework.security.core.Authentication 객체(핵심)를 Wrapping 하고 있음
public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication authentication); }
Authentication(구현체가 Token)
- 사용자를 표현하는 인증 토큰 인터페이스이며, 인증주체를 표현하는 Principal 그리고 사용자의 권한을 의미하는 GrantedAuthority 목록을 포함
AnonymousAuthenticationToken
클래스는 익명 사용자를 표현하기 위한 Authentication 인터페이스 구현체UsernamePasswordAuthenticationToken
클래스는 로그인 아이디/비밀번호 기반 Authentication 인터페이스 구현체RememberMeAuthenticationToken
클래스는 remember-me 기반 Authentication 인터페이스 구현체
- 인증이 완료되거나 혹은 인증되지 않은 사용자 모두를 포괄적으로 표현하며, 인증 여부를 확인할 수 있음
- 사용자의 인증 완료 여부에 따라 Principal 값이 달라짐
- 로그인 전 Principal — 로그인 아이디 (String)
- 로그인 후 Principal — org.springframework.security.core.userdetails.User 객체

포함정보
- Set<GrantedAuthority> authorities : 사용자가 가지고 있는 권한 목록
- principal : 인증 대상에 관한 정보. 주로 UserDetails를 구현한 User 객체가 옴
- credentials : 인증 확인을 위한 정보. 주로 비밀번호가 오지만, 인증 후에는 보안을 위해 삭제함. (input)
- details : 그 밖에 필요한 정보. IP, 세션정보, 기타 인증요청에서 사용했던 정보들.(request에 대한 디테일 정보)
- principal의 UserDetail과는 다름
- controller 나 서비스 단에서 httpRequest에 대한 정보를 얻기 위해 Servlet까지 올라가야 하는데 이러한 것을 방지하기 위해 details가 이용되는 것임
- boolean authenticated : 인증이 되었는지를 체크함