Spring Security는 Java 애플리케이션에서 가장 많이 사용되는 보안 프레임워크 중 하나입니다. 이 프레임워크에서 사용자 인증과 권한 관리를 할 때 가장 중요한 구성 요소 중 하나가 바로 SecurityContextHolder
입니다. 이번 글에서는 SecurityContextHolder
의 개념, 작동 방식, 사용 이유, 그리고 예제 코드를 통해 이해를 돕고자 합니다.
SecurityContextHolder
는 Spring Security의 핵심 클래스 중 하나로, 현재 인증된 사용자와 관련된 보안 정보를 저장하고 제공합니다. 이 클래스는 애플리케이션의 현재 스레드에서 SecurityContext
객체를 유지하고, 이를 통해 인증 정보에 접근할 수 있습니다.
SecurityContext
는 현재 인증된 사용자와 관련된 정보를 담고 있으며, 주로 Authentication
객체를 포함합니다. 이 Authentication
객체에는 다음과 같은 정보가 포함됩니다:
UserDetails
객체)SecurityContextHolder
는 현재 요청의 컨텍스트에서 인증 정보를 쉽게 가져올 수 있도록 해줍니다. 예를 들어, 애플리케이션에서 사용자의 ID나 권한 정보를 확인하려면 이 객체를 통해 접근할 수 있습니다.
SecurityContextHolder
는 요청 스레드의 메모리에 인증 정보를 저장하기 때문에, 무상태 환경에서도 사용할 수 있습니다. 이로 인해 JWT 기반 인증과 같은 Stateless 인증 방식에서도 요청마다 독립적인 인증 처리가 가능합니다.
Spring Security는 SecurityContextHolder
를 사용하여 다양한 보안 작업을 수행하며, 이를 통해 인증 및 권한 관리를 일관되게 처리할 수 있습니다.
SecurityContextHolder
는 기본적으로 ThreadLocal
을 사용하여 현재 스레드의 메모리에 인증 정보를 저장합니다. 이를 통해 각 요청은 독립적인 인증 정보를 가질 수 있으며, 요청이 끝나면 메모리에서 자동으로 제거됩니다. 이렇게 하면 스레드 풀을 사용할 때 이전 요청의 인증 정보가 남아 있는 것을 방지할 수 있습니다.
// 인증된 사용자 정보 가져오기 예제
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityUtil {
public static String getCurrentUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
return authentication.getName(); // 사용자 ID 반환
} else {
throw new RuntimeException("인증된 사용자가 없습니다.");
}
}
}
Spring Security의 SecurityContextHolder
를 사용하여 컨트롤러에서 현재 인증된 사용자의 정보를 가져오는 방법을 예제로 설명하겠습니다.
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;
import com.puzzle.deadlift.adminapi.dto.TblUserDTO;
@RestController
public class UserController {
@GetMapping("/api/current-user")
public TblUserDTO getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof TblUserDTO) {
return (TblUserDTO) authentication.getPrincipal(); // 사용자 정보 반환
} else {
throw new RuntimeException("인증된 사용자가 없습니다.");
}
}
}
SecurityContextHolder
는 서비스 계층에서도 유용하게 사용할 수 있습니다. 인증된 사용자 정보를 가져와 비즈니스 로직에 활용할 수 있습니다.
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import com.puzzle.deadlift.adminapi.dto.TblUserDTO;
@Service
public class UserService {
public TblUserDTO getAuthenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof TblUserDTO) {
return (TblUserDTO) authentication.getPrincipal();
} else {
throw new RuntimeException("인증된 사용자가 없습니다.");
}
}
}
SecurityContextHolder
는 기본적으로 ThreadLocal
전략을 사용하지만, 다른 전략으로 변경할 수도 있습니다. 예를 들어, InheritableThreadLocal
을 사용하여 부모 스레드와 자식 스레드 간에 인증 정보를 공유할 수 있습니다.
// 저장 전략 변경
import org.springframework.security.core.context.SecurityContextHolder;
public class Main {
public static void main(String[] args) {
// InheritableThreadLocal 사용
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
// SecurityContextHolder.MODE_GLOBAL 사용 가능
// SecurityContextHolder.MODE_THREADLOCAL 사용 가능 (기본값)
}
}
JWT 기반의 무상태 환경에서는 세션을 사용하지 않고 각 요청에 포함된 JWT를 통해 사용자 인증을 수행합니다. 이 경우에도 SecurityContextHolder
는 각 요청마다 인증 정보를 메모리에서 유지하고, 요청이 완료되면 메모리에서 삭제됩니다. 이렇게 하면 서버는 상태를 유지할 필요가 없어 확장성이 높아집니다.
SecurityContextHolder
를 비워 이를 방지합니다.SecurityContext
가 제대로 초기화되지 않으면 메모리 누수가 발생할 수 있습니다.SecurityContextHolder
는 Spring Security의 인증 및 권한 관리에서 중요한 역할을 합니다. 각 요청에 대해 인증 정보를 스레드의 메모리에 유지하고, 요청이 완료되면 자동으로 제거됩니다. 이를 통해 서버가 무상태 인증 방식을 구현할 수 있으며, 확장성과 보안성도 향상됩니다. 컨트롤러나 서비스 계층에서 인증된 사용자 정보를 손쉽게 가져와 사용할 수 있어 애플리케이션의 보안 관리가 더욱 간단하고 효과적입니다.