Spring Security에서 SecurityContextHolder : 개념, 동작 방식, 그리고 사용법

YuJun Oh·2024년 11월 4일
0

Spring Security는 Java 애플리케이션에서 가장 많이 사용되는 보안 프레임워크 중 하나입니다. 이 프레임워크에서 사용자 인증과 권한 관리를 할 때 가장 중요한 구성 요소 중 하나가 바로 SecurityContextHolder입니다. 이번 글에서는 SecurityContextHolder의 개념, 작동 방식, 사용 이유, 그리고 예제 코드를 통해 이해를 돕고자 합니다.

1. SecurityContextHolder란 무엇인가?

SecurityContextHolder는 Spring Security의 핵심 클래스 중 하나로, 현재 인증된 사용자와 관련된 보안 정보를 저장하고 제공합니다. 이 클래스는 애플리케이션의 현재 스레드에서 SecurityContext 객체를 유지하고, 이를 통해 인증 정보에 접근할 수 있습니다.

SecurityContext와 Authentication 객체

SecurityContext는 현재 인증된 사용자와 관련된 정보를 담고 있으며, 주로 Authentication 객체를 포함합니다. 이 Authentication 객체에는 다음과 같은 정보가 포함됩니다:

  • Principal: 인증된 사용자(예: UserDetails 객체)
  • Credentials: 사용자 자격 증명(예: 암호)
  • Authorities: 사용자가 가진 권한 목록
  • Authenticated 상태: 인증 여부를 나타내는 Boolean 값

2. 왜 SecurityContextHolder를 사용해야 할까?

이유 1: 현재 사용자 인증 정보 관리

SecurityContextHolder는 현재 요청의 컨텍스트에서 인증 정보를 쉽게 가져올 수 있도록 해줍니다. 예를 들어, 애플리케이션에서 사용자의 ID나 권한 정보를 확인하려면 이 객체를 통해 접근할 수 있습니다.

이유 2: 요청 스레드 기반의 보안 처리

SecurityContextHolder는 요청 스레드의 메모리에 인증 정보를 저장하기 때문에, 무상태 환경에서도 사용할 수 있습니다. 이로 인해 JWT 기반 인증과 같은 Stateless 인증 방식에서도 요청마다 독립적인 인증 처리가 가능합니다.

이유 3: 스프링과의 통합

Spring Security는 SecurityContextHolder를 사용하여 다양한 보안 작업을 수행하며, 이를 통해 인증 및 권한 관리를 일관되게 처리할 수 있습니다.

3. SecurityContextHolder의 동작 방식

기본 저장 전략: ThreadLocal

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("인증된 사용자가 없습니다.");
        }
    }
}

4. 예제 코드: SecurityContextHolder 사용법

4.1 컨트롤러에서 인증된 사용자 정보 가져오기

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("인증된 사용자가 없습니다.");
        }
    }
}

4.2 서비스 계층에서 사용자 정보 사용하기

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("인증된 사용자가 없습니다.");
        }
    }
}

5. SecurityContextHolder의 저장 전략 변경

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 사용 가능 (기본값)
    }
}

6. 무상태 인증과 SecurityContextHolder

JWT 기반의 무상태 환경에서는 세션을 사용하지 않고 각 요청에 포함된 JWT를 통해 사용자 인증을 수행합니다. 이 경우에도 SecurityContextHolder는 각 요청마다 인증 정보를 메모리에서 유지하고, 요청이 완료되면 메모리에서 삭제됩니다. 이렇게 하면 서버는 상태를 유지할 필요가 없어 확장성이 높아집니다.

무상태 인증의 이점

  • 확장성: 서버가 클라이언트의 상태를 유지하지 않으므로 여러 서버 인스턴스에서 동일한 인증 로직을 사용할 수 있습니다.
  • 보안: 각 요청에 대해 독립적인 인증이 이루어지기 때문에 중간 공격에 대한 노출이 줄어듭니다.

7. SecurityContextHolder의 장단점

장점

  • 요청별 독립성: 각 요청은 스레드의 메모리에 독립적으로 인증 정보를 유지하여 요청 간에 정보를 공유하지 않습니다.
  • 확장성과 무상태성: JWT 기반의 무상태 인증에서 사용하면 서버의 확장성이 높아지고 상태 유지의 부담이 줄어듭니다.
  • 일관된 인증 관리: Spring Security와 잘 통합되어 인증 및 권한 관리를 일관되게 처리할 수 있습니다.

단점

  • 스레드 재사용 문제: 스레드 풀을 사용하는 경우, 요청이 끝난 후 인증 정보가 남아 있으면 보안 문제가 발생할 수 있습니다. 다행히 Spring Security는 요청이 끝날 때 SecurityContextHolder를 비워 이를 방지합니다.
  • 메모리 누수: 잘못된 코드 작성으로 인해 SecurityContext가 제대로 초기화되지 않으면 메모리 누수가 발생할 수 있습니다.

8. 결론

SecurityContextHolder는 Spring Security의 인증 및 권한 관리에서 중요한 역할을 합니다. 각 요청에 대해 인증 정보를 스레드의 메모리에 유지하고, 요청이 완료되면 자동으로 제거됩니다. 이를 통해 서버가 무상태 인증 방식을 구현할 수 있으며, 확장성과 보안성도 향상됩니다. 컨트롤러나 서비스 계층에서 인증된 사용자 정보를 손쉽게 가져와 사용할 수 있어 애플리케이션의 보안 관리가 더욱 간단하고 효과적입니다.

0개의 댓글

관련 채용 정보