<모여행>Spring Security, UserDetails는 어디에 저장하나?

Cori1304·2025년 10월 21일
0

JAVA Spring 이론

목록 보기
8/8

배경

Spring security를 다시 공부하다가 <모영행> 프로젝트의 인증/인가 로직에 문제가 없는지 확인하는 중에 UserDetails를 Authentication 객체 저장하고 @AuthenticationPrincipal를 이용해서 사용했는데 이 방법이 표준에 부합하고 효율적인 방법이었는지 의문이 들게 되어 글을 작성하게 되었다.
(JWT를 사용하여 세션/폼을 사용하지 않았다.)

  • ㄹㅇㄱㅇ
  • ㅇㄹㅇㄹㅇㄹㄹ
  • ㅗ호홓

분석

Spring Security 인증 흐름 (표준 Ver.)

먼저, 정석적인 Spring Security의 인증 흐름이 무엇인지 보겠다. (JWT가 아닌 세션/폼 로그인 기준)

  1. 사용자가 아이디/비밀번호를 Form으로 전송
  2. AuthenticationFilterUsernamePasswordAuthenticationToken을 생성해 AuthenticationManager에게 전달 (이때 토큰의 principal은 사용자가 입력한 아이디(String))
  3. AuthenticationManager는 등록된 AuthenticationProvider에게 인증 위임
  4. AuthenticationProviderUserDetailsService를 통해 DB에서 사용자 정보 조회 (loadUserByUsername())
  5. UserDetailsService는 조회된 정보를 바탕으로 UserDetails 객체를 생성후 반환
  6. AuthenticationProvider는 반환된 UserDetails와 입력된 비밀번호를 비교하여 인증에 성공하면, '인증된' UsernamePasswordAuthenticationToken을 새로 생성 AuthenticationManager에게 돌려줌
  7. AuthenticationManager는 이 토큰을 AuthenticationFilter로 반환
  8. AuthenticationFilter는 최종적으로 인증된 토큰을 SecurityContextHolder에 저장

🤔사실 이 부분에서 궁금증이 시작되었다. 5번에서 UserDetails를 만들었는데, 8번에서는 Authentication 토큰을 저장합니다. UserDetails를 저장하는 방식은 잘못된건가?

의문의 답: Authentication 객체의 구조

답은 6번 단계에서 새로 생성되는 '인증된' Authentication 객체의 구조에 있었다. Authentication 인터페이스의 정의를 보면 내부에 인증의 주체, 즉 사용자 정보(Principal)를 담을 수 있는 getPrincipal() 메서드를 가지고 있고 Spring Security는 인증에 성공하면, UserDetailsService가 반환한 바로 그 UserDetails 객체를 이 principal 필드에 담아서 새로운 Authentication 객체를 만드는 것이었다.

public interface Authentication extends Principal, Serializable {
    // ... 다른 메서드들 ...

    // ⭐⭐⭐ 바로 이 녀석! ⭐⭐⭐
    // 이 인증의 주체(Principal)는 누구인가?
    Object getPrincipal();
}
// 6번 단계에서 일어나는 일 (개념적인 코드)
UserDetails userDetails = userDetailsService.loadUserByUsername(username);

// 비밀번호 검증 후...

// '인증된' 토큰을 새로 생성한다!
// 첫 번째 인자(principal)로 userDetails 객체를 넣어준다.
Authentication authenticatedToken = new UsernamePasswordAuthenticationToken(
    userDetails, // ⭐ 여기에 UserDetails가 쏙!
    null,        // 더 이상 비밀번호는 필요 없으므로 null 처리
    userDetails.getAuthorities() // 권한 정보
);

결국 SecurityContextHolder에 저장되는 것은 Authentication 객체가 맞지만, 그 객체는 principal이라는 이름으로 UserDetails를 품고 있는 것이다.

@AuthenticationPrincipal은 어떻게 동작하는가?

컨트롤러에서 사용한 @AuthenticationPrincipal은 어떻게 UserDetails를 바로 꺼내주는 걸까? ❓

@AuthenticationPrincipal CustomUserDetails userDetails

이 어노테이션은 Spring이 자동으로 처리해주는 '편의 기능(Syntactic Sugar)' 이다.

  1. SecurityContextHolder에서 현재 스레드의 SecurityContext를 가져온다. (.getContext())
  2. SecurityContext에서 Authentication 객체를 꺼낸다. (.getAuthentication())
  3. Authentication 객체에서 Principal 객체를 꺼낸다. (.getPrincipal())
  4. 꺼내온 Principal 객체를 파라미터에 선언된 CustomUserDetails 타입으로 안전하게 형변환하여 주입해준다.

이 모든 과정을 어노테이션 하나로 압축했기 때문에, 마치 UserDetailsSecurityContext에 직접 저장된 것처럼 느꼈고 의문을 제기하게 되었다.

결론

  • SecurityContextHolderAuthentication 객체를 저장한다.
  • 인증된 Authentication 객체는 principal 필드에 UserDetails 객체를 품고 있다.
  • @AuthenticationPrincipalAuthentication 객체 속 UserDetails를 편리하게 꺼내주는 마법 같은 어노테이션이다.

참고자료

profile
개발 공부 기록

0개의 댓글