멋사 조직 블로그 개발기록: 인증/인가 시스템, 컨트롤러 vs 서비스에서 유저 정보 꺼내기
/ 7분 분량
서비스 레이어에서 유저 정보를 어떻게 꺼내야 하는지에 대한 깊이 있는 논의를 진행했습니다. 결론적으로 컨트롤러에서 인증 정보를 추출하여 서비스로 전달하는 방식(A)이 여러 장점을 가지며, 특히 모듈 간 통신(포트)에서 필수적임을 이해할 수 있었습니다.
멋사 조직 블로그 개발기록: 인증/인가 시스템, 컨트롤러 vs 서비스에서 유저 정보 꺼내기
서비스 레이어에서 유저 정보를 어떻게 꺼내야 하는지에 대한 깊이 있는 논의를 진행했습니다. 결론적으로 컨트롤러에서 인증 정보를 추출하여 서비스로 전달하는 방식(A)이 여러 장점을 가지며, 특히 모듈 간 통신(포트)에서 필수적임을 이해할 수 있었습니다.
학습 주제
- 공부 주제: Spring Security 인증/인가 시스템에서 유저 정보 추출 방식
- 대화 제목: 멋사 조직 블로그 개발기록, 인증은 어느 레이어에서 처리해야 할까
- 학습 날짜: 2026년 4월 13일
질문과 탐구
개발 과정에서 "유저 정보를 어느 레이어에서 꺼내야 하는가?"에 대한 질문으로 시작되었습니다. 주요 논점은 다음과 같습니다.
- A 방식 (컨트롤러에서 꺼내서 서비스로 전달): 필터에서
SecurityContext에 유저 정보를 저장하면, 컨트롤러가@CurrentUser와 같은 어노테이션을 통해 이를 명시적으로 추출하여 서비스에 파라미터로 전달합니다. - B 방식 (서비스에서 직접 꺼내기): 서비스 레이어 내부에서
SecurityUtils와 같은 유틸리티를 사용하여SecurityContext에서 유저 정보를 직접 꺼내 사용합니다.
이 두 방식의 장단점, 테스트 용이성, 코드 가독성, 일관성, 확장성, 책임 분리, 숨겨진 의존성, PR 리뷰 용이성 등을 다각도로 탐구했습니다.
핵심 학습 내용
- 레이어 역할 분리:
- A 방식: 서비스는
SecurityContext에 대한 의존 없이 순수한 비즈니스 로직만 처리합니다. 컨트롤러가 HTTP 맥락(인증 정보 포함)을 처리하는 역할을 명확히 합니다. - B 방식: 서비스가
SecurityContext에 암묵적으로 의존하게 되어, 순수 비즈니스 로직 레이어의 책임을 흐리게 할 수 있습니다.
- A 방식: 서비스는
- 테스트 용이성:
- A 방식: 서비스 메서드에
userId등 필요한 값만 파라미터로 주입하면 되므로 단위 테스트 작성이 간편합니다. - B 방식: 서비스 단위 테스트 시
SecurityContext를 직접 Mocking 해야 하는 번거로움이 있으며, 테스트 간 의존성 오염 가능성이 있습니다.
- A 방식: 서비스 메서드에
- 재사용성:
- A 방식: 배치 작업, 스케줄러 등 HTTP 요청 컨텍스트가 없는 환경에서도
userId와 같은 값을 직접 주입하여 서비스를 재사용할 수 있습니다. - B 방식:
SecurityContext가 비어있는 환경에서 서비스가SecurityContext를 직접 참조하면NullPointerException이 발생할 수 있습니다.
- A 방식: 배치 작업, 스케줄러 등 HTTP 요청 컨텍스트가 없는 환경에서도
- 명시성 vs 편의성:
- A 방식:
userId가 컨트롤러에서 서비스로 넘어오는 과정이 시그니처에 명시되어, 서비스의 의존성을 명확히 파악할 수 있습니다. - B 방식:
userId에 대한 의존성이 서비스 메서드 내부에 숨겨져 있어, 시그니처만으로는 서비스가 무엇을 필요로 하는지 알기 어렵습니다.
- A 방식:
- 포트(Port) 설계 원칙:
- 모듈 간 통신에서 포트 인터페이스는 다른 모듈과의 계약서 역할을 합니다.
- 포트 인터페이스에는 메서드가 필요로 하는 파라미터가 명시되어야 합니다.
countByMember(Long memberId)처럼memberId가 필요한 경우, 이를 파라미터로 명시하는 것이 계약상 올바른 방식입니다. SecurityUtils를 사용하여 서비스 내부에서userId를 직접 꺼내는 방식(B)은 포트 인터페이스에 의존성이 숨겨져 계약 위반으로 이어질 수 있습니다.
이해한 내용
SecurityContext는 요청 스레드에 붙어 있는 전역 저장소와 같아서, 어느 레이어에서든 접근할 수 있습니다.- 컨트롤러의
@CurrentUser와 서비스의SecurityUtils모두 내부적으로는SecurityContextHolder를 통해 동일한 저장소에서 정보를 꺼내옵니다. - 두 방식의 차이는 "누가, 어디서 정보를 꺼내느냐"에 있으며, 이는 곧 "서비스의 책임"과 "설계의 명시성"에 대한 문제임을 명확히 이해했습니다.
- 팀 프로젝트에서는 일관성을 위해 기본적인 원칙(A 방식)을 따르되, 특정 상황(깊은 서비스 체이닝 등)에서는
SecurityUtils사용(B 방식)을 예외적으로 허용하는 유연한 접근이 필요합니다. - 하지만 모듈 간 통신에서는 포트 인터페이스가 '계약서'이므로, 요구되는 정보(예:
userId)는 반드시 파라미터로 명시해야 합니다. 이는 다른 팀과의 명확한 소통과 코드의 가독성을 높이기 위함입니다.
실전 적용
- 블로그 프로젝트 적용:
- 일반적인 API 요청 처리 시에는 컨트롤러에서
@CurrentUser를 사용하여userId를 추출하고, 이를 서비스 메서드의 파라미터로 전달하는 A 방식을 따릅니다. - 포트 인터페이스를 통해 다른 모듈과 통신할 때도, 컨트롤러에서
userId를 꺼내 포트 메서드의 파라미터로 명시적으로 전달합니다.
- 일반적인 API 요청 처리 시에는 컨트롤러에서
- 향후 계획:
SecurityUtils는 단일 모듈 내에서 서비스 호출 체인이 깊어질 때 파라미터 전달의 번거로움을 줄이기 위한 예외적인 경우에만 사용할 수 있도록 팀 내 컨벤션을 명확히 합니다.- 새로운 기능 개발 시, 인증 정보가 필요한 경우 항상 컨트롤러에서 추출하여 전달하는 것을 기본 원칙으로 삼습니다.
추가 학습 계획
- Spring Security의
SecurityContext동작 방식에 대해 더 깊이 있게 이해하고 싶습니다. - 레거시 코드에서 B 방식이 사용된 경우, 어떻게 A 방식으로 점진적으로 리팩토링할 수 있을지에 대한 전략을 찾아보고 싶습니다.
- 헥사고날 아키텍처의 포트/어댑터 패턴과 관련된 추가적인 설계 원칙들을 학습하여 모듈 간 의존성 관리에 대한 이해를 넓히겠습니다.
참고 자료
- PR #15: feat: JWT 인증/인가 + Refresh Token Rotation 구현 (GitHub Repository) (예시 링크)
com.study.common.security.CurrentUsercom.study.common.security.CustomUserDetailscom.study.common.security.SecurityUtils@PreAuthorize("hasRole('ADMIN')")AUTH_GUIDE.md(팀 내부 문서)