12 KiB
Authentication Object Access 기준
1. 목적
이 문서는 인증된 사용자 정보, 주체(principal), 인증 컨텍스트를 어디서 어떻게 접근할지 정의한다.
이 문서의 목표는 다음과 같다.
- controller에서 현재 사용자 접근 방식을 일관되게 만든다
- Spring Security 저수준 타입이 application/domain으로 번지는 것을 막는다
- 인증 객체 접근과 권한 검사 책임을 구분한다
- SecurityContextHolder 직접 접근을 최소화한다
2. 근거 수준
- Official: Spring Framework / Spring Security 공식 문서에서 직접 확인되는 내용
- Official + Practice: 공식 확장 지점 위에 일반적인 실무 구조를 결합한 내용
- Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙
3. 기본 원칙
3.1 인증 객체 접근은 web boundary concern이다
Spring Security에서 현재 인증 정보는 SecurityContextHolder의 SecurityContext 안 Authentication으로 관리됩니다. Spring MVC는 Principal을 controller method argument로 지원하고, Spring Security는 @AuthenticationPrincipal과 @CurrentSecurityContext로 그 접근을 더 직접적으로 노출합니다. 이 프로젝트에서는 이를 web boundary concern 으로 본다.
프로젝트 규칙:
- 현재 사용자 접근은 기본적으로 controller/web adapter 경계에서 끝낸다
- application/domain은 “현재 인증 컨텍스트를 조회하는 법”을 몰라야 한다
- 내부 로직에는 필요한 최소 actor 정보만 전달한다
3.2 기본 선호는 @AuthenticationPrincipal 기반 전용 현재 사용자 객체다
Spring Security 문서는 @AuthenticationPrincipal을 쓰면 MVC 레이어를 SecurityContextHolder 직접 접근에서 분리할 수 있다고 설명하고, 더 나아가 @CurrentUser 같은 메타 애노테이션으로 Spring Security 의존을 한 파일로 격리하는 방식을 권장 예시로 보여 줍니다.
프로젝트 규칙:
- controller의 기본 인증 객체 접근 방식은 @AuthenticationPrincipal 또는 그 위에 올린 프로젝트 전용 애노테이션
- 프로젝트 기본 애노테이션은 @CurrentUser 또는 이에 준하는 이름을 권장
- controller가 매번 SecurityContextHolder를 직접 읽지 않는다
3.3 인가 규칙은 인증 객체 접근 방식과 별개로 다룬다
Spring Security 문서는 요청 매칭 기반 보안 규칙을 일찍 적용하고, 동시에 method security를 함께 두는 defense in depth 를 권장합니다. 따라서 인증 객체를 꺼내는 문제와 권한 검사를 어디서 할지는 분리해서 설계해야 한다.
프로젝트 규칙:
- 인증 객체 접근은 “현재 사용자가 누구인가”의 문제
- 인가 규칙은 “이 사용자가 이 동작을 할 수 있는가”의 문제
- controller 안에서 if (role == ...) 식으로 인가를 기본 구현하지 않는다
- 인가는 security config + method security + application/domain 정책으로 나눈다
4. 접근 방식별 규칙
4.1 Principal
Spring MVC는 java.security.Principal을 controller method argument로 지원하며, 현재 인증된 사용자를 나타냅니다. Spring Security 환경에서는 Authentication이 Principal이므로 HttpServletRequest#getUserPrincipal() 경유로 주입될 수 있습니다.
프로젝트 규칙:
- 단순히 현재 사용자 이름/식별자 정도만 필요하면 Principal 사용 가능
- 하지만 principal 구현 타입 캐스팅을 기대하는 기본 스타일로는 쓰지 않는다
- Principal은 가장 단순한 읽기 전용 접근에만 쓴다
권장 예:
- /me 같은 endpoint에서 현재 username만 필요한 경우
4.2 Authentication
Spring Security의 Authentication은 현재 사용자와 권한 정보를 담는 핵심 타입입니다. @CurrentSecurityContext(expression = "authentication")로도 controller 인자로 받을 수 있습니다.
프로젝트 규칙:
- Authentication은 예외적으로만 controller에서 직접 받는다
- 권한 목록, credentials, details 같은 Security framework 세부정보가 정말 필요할 때만 허용
- 일반 비즈니스 endpoint의 기본 시그니처로 사용하지 않는다
즉, Authentication은 가능하지만 기본값은 아니다.
4.3 @AuthenticationPrincipal
Spring Security는 AuthenticationPrincipalArgumentResolver를 제공하고, @EnableWebSecurity를 쓰면 이를 MVC에 자동 추가합니다. 이 애노테이션은 Authentication.getPrincipal()을 controller method argument로 직접 받게 해 줍니다.
프로젝트 규칙:
- 현재 사용자 객체 접근의 기본값은 @AuthenticationPrincipal
- 단, controller 시그니처가 Spring Security 애노테이션에 직접 결합되는 것이 싫다면 메타 애노테이션으로 감싼다
- controller는 principal 내부 구조를 깊게 탐색하기보다 필요한 전용 타입을 주입받는다
4.4 프로젝트 전용 @CurrentUser 메타 애노테이션
Spring Security 문서는 @AuthenticationPrincipal을 감싼 @CurrentUser 메타 애노테이션 예시를 직접 제공하고, 이렇게 하면 MVC 레이어의 Spring Security 의존을 한 파일로 격리할 수 있다고 설명합니다. 또한 expression을 통해 JWT claim 같은 값만 바로 꺼내는 방식도 예시로 보여 줍니다.
프로젝트 규칙:
- 프로젝트 기본 방식은 @CurrentUser
- @CurrentUser는 @AuthenticationPrincipal의 메타 애노테이션으로 구현
- 필요하면 expression 기반으로 userId, subject, claims['sub'] 같은 값만 주입하는 파생 애노테이션도 허용
권장 방향:
- @CurrentUser AuthenticatedUser currentUser
- 또는 @CurrentUserId String userId
4.5 @CurrentSecurityContext
Spring Security는 @CurrentSecurityContext로 SecurityContext 또는 Authentication을 controller method argument로 직접 주입할 수 있게 지원합니다.
프로젝트 규칙:
- @CurrentSecurityContext는 예외적 escape hatch
- 일반 endpoint의 기본 접근 방식으로 사용하지 않는다
- security context 전체가 필요한 framework-adjacent endpoint에서만 제한적으로 허용한다
예:
- 디버그/진단 endpoint
- 보안 관련 내부 운영 endpoint
5. 계층별 규칙
5.1 Controller
프로젝트 규칙:
- controller는 인증 객체를 전용 현재 사용자 타입 또는 최소 식별자로 받는다
- controller가 SecurityContextHolder를 직접 조회하지 않는다
- controller는 principal에서 필요한 최소 정보만 추출해 application command/use case에 전달한다
- controller가 Authentication, SecurityContext, UserDetails를 그대로 내부로 넘기지 않는다
5.2 Application
프로젝트 규칙:
- application service/use case는 Spring Security 타입을 모른다
- 입력으로는 actorId, actorRoleSet, tenantId 같은 의미 있는 값만 받는다
- “현재 로그인 사용자 조회”를 application 내부에서 직접 하지 않는다
즉, application은 현재 사용자가 누구인지가 아니라, 호출 주체가 누구라고 전달받았는지만 다룬다.
5.3 Domain
프로젝트 규칙:
- domain은 Spring Security 의존을 가지지 않는다
- domain 객체/도메인 서비스/값 객체가 Authentication, Principal, UserDetails를 참조하지 않는다
- 도메인 규칙이 호출 주체를 필요로 하면 명시적 값(ActorId, ActorType)으로 전달한다
5.4 Infrastructure / Security Adapter
프로젝트 규칙:
- Spring Security principal 구성, claim 해석, JWT → 현재 사용자 변환은 infrastructure/security adapter에서 담당한다
- principal 구현체, converter, resolver, 인증 토큰 해석 로직은 이 계층에 모은다
- web/business 계층이 JWT claim 구조를 직접 파싱하지 않는다
6. 현재 사용자 타입 규칙
6.1 AuthenticatedUser 같은 전용 타입을 둔다
Spring Security는 principal 타입을 자유롭게 둘 수 있고, @AuthenticationPrincipal은 그 principal을 그대로 주입할 수 있습니다. 이 프로젝트에서는 controller용 인증 객체를 프로젝트 전용 타입 으로 두는 방식을 기본 권장한다.
프로젝트 규칙:
- 전용 타입 예: AuthenticatedUser
- 최소 권장 필드 예:
- userId
- authorities 또는 역할 집합
- tenantId(필요 시)
- password, credentials, provider-specific raw claim map을 기본 공개 필드로 두지 않는다
6.2 전용 타입은 “비즈니스에 필요한 최소 정보”만 담는다
프로젝트 규칙:
- 현재 사용자 타입은 보안 프레임워크 내부 표현이 아니다
- JWT 전체 claims map, raw token string, authentication details를 무비판적으로 싣지 않는다
- 컨트롤러/유스케이스가 자주 필요로 하는 값만 담는다
7. 전달 규칙
7.1 application에는 최소 actor 정보만 넘긴다
프로젝트 규칙:
- AuthenticatedUser 전체를 application에 넘기는 것도 기본적으로 지양
- 더 선호하는 것은 command/query 생성 시 필요한 최소 값만 복사하는 방식
예:
- CreateSessionCommand(actorId, email, password)
- ChangePasswordCommand(actorId, currentPassword, newPassword)
7.2 principal을 전역 static 접근으로 다시 조회하지 않는다
Spring Security에서 현재 인증은 SecurityContextHolder에 저장되지만, 그 저장소가 있다는 사실이 곧 아무 계층에서나 static 접근으로 꺼내 써도 된다는 뜻은 아닙니다.
프로젝트 규칙:
- service/domain/util에서 SecurityContextHolder.getContext() 직접 호출 금지
- “현재 사용자 필요”는 메서드 인자로 드러나야 한다
- 숨겨진 전역 의존성을 만들지 않는다
8. 권한 검사 규칙
8.1 권한 검사는 security rule + method security를 우선한다
Spring Security는 요청 매칭 기반 보안과 method security를 함께 두는 defense in depth를 권장합니다.
프로젝트 규칙:
- 역할/권한 검사는 기본적으로 security config 또는 method security에서 처리
- controller 안의 imperative role check를 기본 금지
- application/domain에서 추가 business authorization이 필요하면 명시적 정책으로 구현한다
8.2 “현재 사용자와 리소스 소유자 비교”는 business rule일 수 있다
프로젝트 규칙:
- 단순 권한(ROLE_ADMIN 등)은 security rule에 두는 쪽을 우선
- “현재 사용자 ID와 리소스 owner가 같은가” 같은 규칙은 application/domain 정책일 수 있다
- 이 경우에도 현재 사용자 정보는 최소 actor 값으로 전달한다
9. 테스트 규칙
9.1 controller 테스트는 프로젝트 전용 현재 사용자 접근을 기준으로 짠다
프로젝트 규칙:
- 테스트도 @CurrentUser 또는 프로젝트 principal 타입 기준으로 작성한다
- 테스트 때문에 production code가 raw Authentication에 과도하게 결합되지 않게 한다
- 보안 프레임워크 타입보다 프로젝트의 현재 사용자 계약을 검증한다
10. 금지 규칙
다음은 기본 금지다.
- controller에서 SecurityContextHolder 직접 조회
- controller 시그니처에 raw Authentication 남발
- application/domain/service에서 Spring Security 타입 직접 사용
- service/util에서 전역 static 방식으로 현재 사용자 조회
- JWT claims/raw token을 여러 계층에서 직접 파싱
- controller 안에서 if (role == ...) 식 인가 로직 구현
- principal 구현체를 persistence/domain 모델로 겸용 사용
11. 체크리스트
다음 질문에 “예”로 답할 수 있어야 한다.
- 현재 사용자 접근이 controller/web 경계에 머무르는가?
- 기본 접근 방식이 @CurrentUser 또는 이에 준하는 전용 애노테이션인가?
- Spring Security 타입이 application/domain으로 번지지 않는가?
- 유스케이스에는 필요한 최소 actor 정보만 전달되는가?
- 권한 검사가 controller imperative code가 아니라 보안 규칙/정책으로 표현되는가?