- Spring Security에서 세션 관리의 중심이 되는 SecurityContextHolder 와 SecurityContext 의 역할
- 스레드 로컬(ThreadLocal) 을 통한 인증 정보 관리 방식
- SecurityContext 구조와 내부에 어떤 인증 정보(Authentication) 가 저장되는지 알기
- 실무 예제로 SecurityContext 가 어떻게 사용되는지 알기
- 동시 요청이나 비동기 상황에서 발생할 수 있는 문제점들을 이해하고, 올바른 사용법 알기
SecurityContextHolder 와 Security Context
1. SecurityContextHolder 의 역할

- SecurityContextHolder 는 Security에서 현재 인증(Authentication) 정보를 저장하고 조회하는 중앙 저장소 역할을 한다
그림에서 맨 위에 있는게 SecurityContextHolder 임 - 기본적으로 스레드 로컬(ThreadLocal) 방식을 사용하여 각 요청마다 인증 정보를 독립적으로 보관한다
로그인 시 생성된 Authentication 객체는 SecurityContextHolder 에 저장되고, 이후의 요청 처리 과정에서 언제든지 참조 가능
2. SecurityContext 의 구조
- SecurityContext 는 실제 인증 정보인 Authentication 객체를 담고 있는 컨테이너임
구조
package org.springframework.security.core.context;
import java.io.Serializable;
import org.springframework.security.core.Authentication;
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication); // 핵심
}
setAuthentication 메서드에 주입되는 Authentication 객체가 핵심이다
사용자 정보, 권한, 인증 여부 등이 담겨있음
| 구성 요소 | 설명 |
| Principal | 사용자 정보(보통 UserDetails 구현체) |
| Credentials | 비밀번호 또는 인증 토큰(보통 인증 후에는 제거됨) |
| Authorities | 사용자가 가진 권한(Role) 목록 |
| Details | 부가 정보(IP, 세션 ID 등) |
SecurityContextHolder 동작 방식
SecurityContextHolder 의 일부
package org.springframework.security.core.context;
import java.lang.reflect.Constructor;
import java.util.function.Supplier;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
public class SecurityContextHolder {
// 실제 저장 방식을 얘가 결정함
private static SecurityContextHolderStrategy strategy;
private static String strategyName = System.getProperty("spring.security.strategy");
private static void initializeStrategy() {
if ("MODE_PRE_INITIALIZED".equals(strategyName)) {
Assert.state(strategy != null,
"When using MODE_PRE_INITIALIZED,
setContextHolderStrategy must be called with the fully constructed strategy");
} else {
if (!StringUtils.hasText(strategyName)) {
strategyName = "MODE_THREADLOCAL";
}
// 각 요청 스레드마다 별도의 SecurityContext를 저장한다
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();
} else {
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
} catch (Exception var2) {
Exception ex = var2;
ReflectionUtils.handleReflectionException(ex);
}
}
}
}
public static void clearContext() { // 얘
strategy.clearContext();
}
public static SecurityContext getContext() { // 얘
return strategy.getContext();
}
public static void setContext(SecurityContext context) { // 얘
strategy.setContext(context);
}
}
Spring Security는 기본적으로 ThreadLocal 을 사용하여 각 요청 스레드마다 별도의 SecurityContext 를 저장한다
이렇게 하면 멀티스레드 환경에서 인증 정보가 뒤섞이지 않고, 요청 단위로 분리된다고 함
요청이 끝나면 clearContext() 메서드가 호출되어 메모리 누수를 방지한다
2. 전략 모드(Strategy) 변경
- SecurityContextHolder 는 기본적으로 MODE_THREADLOCAL 전략을 사용한다
if (strategyName.equals("MODE_THREADLOCAL")) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
- 필요하다면 다른 전략도 사용할 수 있다
| 전략 | 설명 |
| MODE_THREADLOCAL | 기본값 요청 스레드마다 독립된 SecurityContext 저장 |
| MODE_INHERITABLETHREADLOCAL | 자식 스레드가 부모 스레드의 SecurityContext를 상속함 |
| MODE_GLOBAL | 모든 스레드가 하나의 SecurityContext를 공유함 (거의 사용하지 않음) |
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
예제
1. SecurityContext 에서 사용자 조회하기
UserController.java
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/me")
public String me() {
// SecurityContextHolder는 Authentication 정보를 저장하고 조회하는 기능
// 실제로는 SecurityContext에 저장된다
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return "현재 사용자는 " + authentication.getName() + "이고, 권한은 " + authentication.getAuthorities();
}
}
로그인한 사용자가 /me API를 호출하면,
SecurityContextHolder 에 저장된 인증 정보를 통해 사용자의 이름과 권한을 확인할 수 있다
/me 를 호출할 시점에 SecurityFilterChain 에 anyRequest.permitAll() 와 같이 뚫어놨다면 anonymousUser 라고 나온다
2. 커스텀 서비스에서 인증 정보 활용하기
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public void placeOrder() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
System.out.println(username + "님의 주문이 접수되었습니다.");
}
}
별도의 인증 객체 전달 없이도, 전역에서 SecurityContextHolder 를 통해 사용자의 정보를 확인할 수 있어서 매우 편리하다
여기에 사용된 FilterChain
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
@Order(0)
public SecurityFilterChain h2FilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher(PathRequest.toH2Console())
.authorizeHttpRequests(auth -> auth
.requestMatchers(PathRequest.toH2Console()).permitAll()
.anyRequest().authenticated())
.csrf(csrf ->csrf.disable())
.headers(headers -> headers
.frameOptions(frame -> frame.sameOrigin()))
.formLogin(login -> login
.loginPage("/login").permitAll());
return http.build();
}
@Bean
@Order(1)
public SecurityFilterChain meFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/me/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/me/**").authenticated()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults()); // Basic Auth
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password("{noop}pass")
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password("{noop}pass")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
팁
- 항상 요청이 끝나면 SecurityContext 가 정리(clearContext)되는지 확인해야한다. 그렇지 않으면 메모리 누수가 발생할 수 있다
- 비동기처리(@Async) 에서는 기본적으로 ThreadLocal 이 전파되지 않기 때문에,
DelegatingSecurityContextRunnable 등을 사용해야한다고 한다 - Authentication 객체에는 비밀번호와 같은 민감한 정보들이 포함될 수 있으므로
인증 후 즉시 제거(eraseCredentials) 하거나, 민감한 정보를 포함시키지 말아야 한다
| 개념 | 핵심 포인트 |
| SecurityContextHolder | 인증 정보를 전역적으로 보관 / 조회하는 저장소 |
| SecurityContext | 실제 Authentication 을 담고 있는 컨테이너 |
| Authentication | Principal, Credentials, Authorities, Details 포함 |
| ThreadLocal 전략 | 요청 단위 분리, 필요시 상속 모드로 변경 가능 |
'Spring Boot > Security 쿠키,세션 기반 인증,인가' 카테고리의 다른 글
| 세션 관리 설정과 커스터마이징 : 세션 만료 후 처리 전략 (0) | 2026.05.08 |
|---|---|
| 세션 관리 설정과 커스터마이징 : 세션 타임아웃 설정 (0) | 2026.05.08 |
| 세션 관리 설정, 커스터마이징 : 세션 생성 정책 설정 (0) | 2026.05.07 |
| 세션 관리 핵심 컴포넌트 : 세션 이벤트와 리스너 (0) | 2026.05.07 |
| 세션 관리 핵심 컴포넌트 : 세션 관리 필터 (0) | 2026.05.06 |