| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- 로그인
- API
- js
- MockAPI
- 그림
- AJAX
- 카카오
- 낙서
- IntelliJ
- 분리 개발
- 시
- @AuthenticationPrincipal
- 공부
- ES6+
- SpringBoot
- JavqScript
- 자바 스크립트
- Security 설정
- 보안
- 백틱
- security
- 취미
- findBy
- VS Code
- 웹
- 웹개발
- login
- HttpOnly
- Method 방식
- 유효성 검사
- Today
- Total
개발가능구역
🤔Spring Security 핵심: @AuthenticationPrincipal 원리와 사용 방법 본문

백엔드 개발자를 노리며 로그인 기능. 즉, Security를 엄청 보는데도 모르는 게 한 두개가 아닙니다!
차근차근 알아가는 와중에 좋은 기능을 알게 되어 글로 적어 정보를 알리고, 나중에 찾아볼 수 있게끔 되는 게 목표네요!
이번 글에 주제는 @AuthenticationPrincipal 입니다!
이 어노테이션이 왜 필요하고, 내부적으로 어떻게 동작하며, 어떻게 사용해야 하는지와
Security에 쓰이는 UserDetails에 대하여 알아봅시다.
⭕@AuthenticationPrincipal 이란 무엇인가?
@AuthenticationPrincipal은 Spring Security에서 현재 로그인한 사용자의 정보를 컨트롤러나 서비스 메서드의 매개변수로 바로 주입받을 수 있게 해주는 어노테이션입니다.
쉽게 말해, 웹사이트에 접속한 사용자의 이름, ID, 권한 등 "인증이 완료된 주체(Principal)"의 상세 정보를 꺼내오는 마법의 지팡이라고 생각하시면 됩니다.
❓왜 필요한가?
사용자가 로그인하면 Spring Security는 해당 사용자의 인증 정보를 어딘가에 보관합니다. 이 보관된 정보를 가져오
기 위해 일반적으로는 복잡한 Security Context를 직접 뒤져야 합니다.
| 일반적인 방법 (복잡) | @AuthenticationPrincipal (간단) |
| SecurityContextHolder.getContext().getAuthentication().getPrincipal() | @AuthenticationPrincipal UserDetails userDetails |
이 어노테이션을 사용하면, 복잡한 과정 없이 단 한 줄로 깔끔하게 사용자 객체를 받아볼 수 있습니다.
⭕ Spring Security의 인증 원리 (3단계)
@AuthenticationPrincipal이 동작하는 원리를 이해하기 위해서는 Spring Security의 인증 과정 3단계를 알아야 합니다.
1️⃣ UserDetailsService (사용자 정보 로드)
- 사용자가 로그인 시도 시, Spring Security는 개발자가 구현한 UserDetailsService의 loadUserByUsername() 메서드를 호출합니다.
- 이 메서드는 데이터베이스에서 username에 해당하는 사용자 정보를 찾아 UserDetails 타입의 객체로 반환합니다.
2️⃣ Authentication 객체 생성 및 저장
- loadUserByUsername()이 반환한 UserDetails 객체는 인증 성공 후, Spring Security 내부의 Authentication 객체 안에 저장됩니다.
- Authentication 객체는 다음 세 가지 주요 정보를 가집니다.
- Principal (주체): 바로 UserDetails 객체 (현재 로그인한 사용자 정보)
- Credentials (자격 증명): 비밀번호 (인증 후에는 삭제됨)
- Authorities (권한): 사용자의 역할 (ROLE_USER, ROLE_ADMIN 등)
3️⃣ SecurityContext 에 보관
- 생성된 Authentication 객체는 SecurityContext라는 저장소에 보관됩니다.
- 이 SecurityContext는 사용자의 세션 (혹은 JWT 토큰)에 연결되어, 사용자가 요청을 보낼 때마다 자동으로 로드됩니다. 즉, 현재 로그인한 사용자 정보를 언제든 꺼내볼 수 있게 됩니다.
⭕ @AuthenticationPrincipal 사용 방법
@AuthenticationPrincipal은 위 3️⃣에서 SecurityContext에 보관된 Authentication 객체 내의 Principal (즉, UserDetails 객체)를 추출하여 매개변수에 넣어줍니다.
1️⃣ 표준 UserDetails 사용 (기본)
가장 간단한 형태로, Spring Security 프레임워크가 제공하는 기본 UserDetails 인터페이스를 사용하여 정보를 받을 수 있습니다.
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyPageController {
@GetMapping("/api/v1/mypage/username")
public String getMyUsername(@AuthenticationPrincipal UserDetails userDetails) {
// UserDetails에서 username, password, authorities 등을 가져올 수 있습니다.
if (userDetails != null) {
return "현재 로그인 사용자: " + userDetails.getUsername();
}
return "로그인 정보 없음";
}
}
2️⃣ 커스텀 UserDetails 사용 (추천)
사용자 ID(member_id)와 같은 추가적인 정보가 필요할 경우, 표준 UserDetails를 상속 또는 구현하여 만든 커스텀 클래스를 지정해야 합니다.
// CustomUserDetails 클래스 정의
import com.example.demo.config.security.CustomUserDetails;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyInfoController {
// @AuthenticationPrincipal의 타입으로 CustomUserDetails를 지정합니다.
@GetMapping("/api/v1/user/info")
public String getUserInfo(@AuthenticationPrincipal CustomUserDetails userDetails) {
if (userDetails != null) {
// 커스텀 클래스에 추가한 memberId 필드에 바로 접근 가능함
Long memberId = userDetails.getMemberId();
String username = userDetails.getUsername();
return "로그인한 사용자의 ID(PK): " + memberId + ", 계정명: " + username;
}
return "인증된 사용자가 없습니다.";
}
}
⭕ Spring Security 핵심 구성 요소: UserDetails
| 구분 | UserDetails | UserDetailsService | CustomUserDetails |
| 타입 | 인터페이스 (Interface) | 인터페이스 (Interface) | 클래스 (Class) |
| 기능/역할 | 사용자 정보의 형태 정의 및 보관. Spring Security가 인증된 주체(Principal)의 정보를 꺼내볼 때 사용하는 데이터 모델 표준입니다. | 사용자 정보를 로드하는 서비스 로직 정의. DB 접근, 사용자 조회 등 인증에 필요한 데이터를 준비하는 계약입니다. | UserDetails를 구현(혹은 User를 상속)하여 개발자가 직접 만든 클래스. DB의 PK(memberId) 등 추가 정보를 담는 데 사용됩니다. |
| 주요 메서드 | getUsername(), getPassword(), getAuthorities() | loadUserByUsername(String username) | getMemberId() 등 추가된 getter 메서드 |
| 개발자가 하는 일 | 이 인터페이스를 구현하거나 상속하여 CustomUserDetails를 생성합니다. | 이 인터페이스를 구현하여 CustomUserService를 만들고 @Service로 등록합니다. | DB 엔티티를 기반으로 직접 구현하여 Principal 정보를 확장합니다. |
| 글에서의 사용처 | Authentication 객체에 저장되는 데이터 타입을 설명할 때 사용됩니다. | loadUserByUsername() 메서드를 호출하는 인증 로직을 설명할 때 사용됩니다. | @AuthenticationPrincipal 어노테이션의 타입으로 지정할 때 사용됩니다. |
1️⃣ UserDetails: 인증된 주체, 무엇이 담겨있나?
UserDetails는 Spring Security가 인증을 완료한 사용자(Principal)의 정보를 시스템 내에서 사용할 수 있도록 정의한 표준 인터페이스이자 데이터 모델입니다.
❓인증된 주체(Principal)란?
컴퓨터 보안 용어에서 주체(Principal)는 시스템에 접속하여 상호작용하는 개체(Entity)를 의미합니다.
웹 애플리케이션에서는 '로그인한 사용자 그 자체'를 Principal이라고 부릅니다. UserDetails는 이 Principal의 정보를 객체 형태로 담아주는 그릇입니다.
✅ 주요 메서드 역할
| 메서드 | 역할 | 상세 설명 |
| getUsername() | 사용자 계정 ID | 로그인 시 입력한 계정 ID (DB의 USERNAME 컬럼)를 반환합니다. 인증 과정에서 식별자로 사용됩니다. |
| getPassword() | 계정 비밀번호 | DB에 저장된 암호화된(해시된) 비밀번호를 반환합니다. 로그인 시 입력된 비밀번호와 비교하여 인증 여부를 판단합니다. |
| getAuthorities() | 사용자 권한 | 현재 사용자가 가진 권한 목록(Role)을 반환합니다. ROLE_USER, ROLE_ADMIN 등으로 표현되며, 인가(Authorization), 즉 접근 제어에 사용됩니다. |
2️⃣ UserDetailsService: 사용자 정보를 어떻게 로드하는가?
❓
로드하는 사용자 정보는 무엇?
이 서비스가 로드하는 사용자 정보는 바로 DB에 저장된 사용자의 계정 ID, 암호화된 비밀번호, 그리고 권한 정보입니다. 이 정보를 DB에서 가져와 UserDetails 객체 형태로 반환하는 것이 핵심 목적입니다.
✅ loadUserByUsername() 메서드의 역할
UserDetailsService가 가진 유일한 핵심 메서드입니다.
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
- 역할: 로그인 시도 시, 사용자가 입력한 username (계정 ID)을 받아서 DB에서 해당 사용자 정보를 조회합니다.
- 반환: 조회된 DB 정보를 UserDetails 객체로 변환하여 Spring Security에게 반환합니다. 만약 DB에서 사용자를 찾지 못하면 UsernameNotFoundException을 발생시킵니다.
- 결과: Spring Security는 이 반환된 UserDetails 객체의 비밀번호와 사용자가 입력한 비밀번호를 비교하여 인증을 완료합니다.
3️⃣ CustomUserDetails: 왜 User와 UserDetails를 함께 쓰는가?
CustomUserDetails는 개발자가 UserDetails 인터페이스를 구현하거나 확장하여 만든 클래스입니다. 이는 DB의 고유 ID(MEMBER_ID)처럼, Spring Security 표준에는 없지만 애플리케이션 비즈니스 로직에 필요한 추가 정보를 담기 위해 사용됩니다.
✅ User와 UserDetails의 관계 및 용도
| 구분 | UserDetails (인터페이스) | User (클래스) |
| 용도 | 표준 계약만 정의 | UserDetails를 구현한 기본 클래스 |
| 개발 활용 | 직접 구현하면 모든 필수 메서드를 만들어야 함 | User를 상속하면 getUsername(), getPassword(), getAuthorities() 등의 구현을 자동으로 가져올 수 있음 |
| 차이점 | 데이터 모델의 규칙 | 규칙을 따르는 실제 구현체 |
대부분의 개발자가 CustomUserDetails를 만들 때는 UserDetails 인터페이스를 직접 구현하기보다, 이미 필수 메서드가 구현되어 있는 Spring Security의 기본 클래스인 User를 상속하고, 필요한 필드(memberId 등)만 추가하여 사용합니다. 이는 개발 효율성을 높여줍니다.
✅ DB와의 연관성: 정보 확장
CustomUserDetails는 DB의 MEMBER_ENTITY와 직접적으로 연관됩니다.
- UserDetailsService가 DB에서 MEMBER_ENTITY를 조회합니다.
- 이 MEMBER_ENTITY의 필수 정보(Username, Password, Role)는 User 클래스에 전달하고, 추가 정보(memberId)는 CustomUserDetails의 필드에 저장합니다.
- 최종적으로 이 확장된 CustomUserDetails 객체가 SecurityContext의 Principal로 저장됩니다.
이러한 과정을 통해, 여러분이 @AuthenticationPrincipal을 사용하기 전에도 로그인이 잘 작동했던 것은 CustomUserDetails를 통해 이미 인증에 필수적인 정보(ID, PW, 권한)를
성공적으로 Spring Security에게 제공했기 때문입니다.
다만, 이 정보를 컨트롤러에서 편리하게 꺼내 쓰는 방법을 몰랐을 뿐입니다.
이렇게 @AuthenticationPrincipal을 사용하면,
인증된 사용자의 핵심 정보를 Spring의 도움을 받아 아주 쉽게 가져와 비즈니스 로직에 활용할 수 있습니다.
'개발자 공부' 카테고리의 다른 글
| 🛡️ 현대 웹 서비스 인증: 토큰 vs 세션, 그리고 HttpOnly 쿠키의 비밀 (0) | 2025.10.29 |
|---|---|
| 🤔프로그래밍의 기본 변수의 유효 범위: 전역, 지역, 그리고 매개변수 (ft. 회의실 비유) (0) | 2025.10.22 |
| 🤔프로그래밍의 핵심! 동기와 비동기 방식 이해해보기: 식당 비유 (0) | 2025.10.21 |
| VS Code와 IntelliJ의 협업 (feat. 백엔드-프론트엔드 분리 개발 핵심) (0) | 2025.10.12 |