CustomUserDetails 역할 및 JWT 인증 흐름 가이드 <spring security>

Cori1304·2025년 8월 23일
0

JAVA Spring 이론

목록 보기
6/8

개요

이 문서는 CustomUserDetails 클래스의 역할과 JWT 기반의 stateless 인증 시스템에서의 사용 시점을 명확히 설명합니다.

1. CustomUserDetails이란?

CustomUserDetails은 Spring Security의 UserDetails 인터페이스를 구현한 클래스입니다.

  • 역할: 인증이 완료된 사용자의 핵심 정보(ID, 이메일, 권한 등)를 담는 객체입니다.
  • 목적: Spring Security가 현`재 요청을 보낸 사용자가 누구인지 식별하고, 권한을 확인하는 표준적인 방법을 제공합니다.
  • 특징: Member 엔티티 전체가 아닌, 인증에 필수적인 최소한의 데이터만 포함하여 보안성과 효율성을 높입니다.
// CustomUserDetails의 주요 필드
private final Long id;
private final String email;
private final Collection<? extends GrantedAuthority> authorities;

2. 로그인 과정 vs 로그인 이후 API 요청

CustomUserDetails의 사용 시점을 이해하기 위해 두 과정을 구분해야 합니다.

로그인 (Authentication)

  • 과정: 사용자가 이메일과 비밀번호로 로그인을 시도하는 최초의 인증 단계입니다.
  • CustomUserDetails 사용 여부: 사용하지 않습니다.
  • 핵심 로직: AuthService에서 전달받은 비밀번호와 DB에 저장된 해시된 비밀번호를 비교하여 사용자를 검증합니다. 검증 성공 시, JWT(Access Token, Refresh Token)를 생성하여 클라이언트에게 반환합니다.

로그인 이후 API 요청 (Authorization)

  • 과정: 클라이언트가 로그인 시 발급받은 JWT를 HTTP 헤더에 담아 서버 자원을 요청하는 모든 후속 단계입니다.
  • CustomUserDetails 사용 여부: 매우 중요하게 사용됩니다.
  • 핵심 로D직:
    1. JWT 필터 실행: SecurityConfig에 등록된 커스텀 JWT 필터가 요청을 가로챕니다.
    2. 토큰 검증: 필터는 JWT의 유효성(서명, 만료 시간 등)을 검증합니다.
    3. CustomUserDetails 생성: 토큰이 유효하면, 토큰의 claims에 담긴 사용자 정보(ID, 이메일, 권한 등)를 추출하여 메모리 상에 CustomUserDetails 객체를 생성합니다.
    4. SecurityContext 등록: 생성된 CustomUserDetailsAuthentication 객체로 감싸 SecurityContextHolder에 등록합니다. 이로써 Spring Security는 현재 요청의 주체를 인식하게 됩니다.
    5. 컨트롤러/서비스 로직 실행: @AuthenticationPrincipal 어노테이션을 통해 컨트롤러에서 현재 사용자 정보를 직접 주입받거나, @PreAuthorize 등으로 권한 검사를 수행할 수 있습니다.

3. 왜 Stateless 환경에서 CustomUserDetails이 필요한가?

Stateless 원칙은 서버에 사용자 세션을 저장하지 않는다는 의미이지, 요청을 처리하는 메모리 상의 일시적인 정보까지 사용하지 않는다는 의미가 아닙니다.

CustomUserDetails은 서버에 저장되는 "상태(State)"가 아니라, 매 요청마다 JWT를 기반으로 생성되는 일회성 "신분증"입니다. 이 신분증이 있어야 Spring Security의 강력한 보안 기능들을 원활하게 활용할 수 있습니다.

4. 인증 흐름 다이어그램 (Mermaid)

sequenceDiagram
    participant Client
    participant "JWT Filter" as Filter
    participant "AuthService/DB" as Service
    participant Controller
    participant "Spring Security" as Security

    Note over Client, Service: 1. 최초 로그인 (Authentication) - CustomUserDetails 사용 안함
    Client->>Service: 로그인 요청 (이메일, 비밀번호)
    activate Service
    Service->>Service: DB 정보로 사용자 검증
    Service-->>Client: JWT 발급 (Access/Refresh Token)
    deactivate Service

    Note over Client, Security: 2. API 요청 (Authorization) - CustomUserDetails 사용
    Client->>Filter: API 요청 (with JWT)
    activate Filter
    Filter->>Filter: 1. JWT 유효성 검증
    Note right of Filter: 2. 토큰 정보로 CustomUserDetails 생성
    Filter->>Security: 3. SecurityContext에 Authentication 등록
    Filter->>Controller: 4. 요청 전달
    deactivate Filter

    activate Controller
    Controller->>Security: @AuthenticationPrincipal 요청
    activate Security
    Security-->>Controller: SecurityContext에서 CustomUserDetails 제공
    deactivate Security
    Controller-->>Client: API 응답
    deactivate Controller

5 CustomUserDetails 코드

package eightbit.moyeohaeng.global.security;

import java.util.Collection;
import java.util.Collections;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import eightbit.moyeohaeng.domain.member.entity.member.Member;
import lombok.Getter;

@Getter
public class CustomUserDetails implements UserDetails {

	private final Long id;
	private final String email;
	private final Collection<? extends GrantedAuthority> authorities;

	private CustomUserDetails(Long id, String email, Collection<? extends GrantedAuthority> authorities) {
		this.id = id;
		this.email = email;
		this.authorities = authorities;
	}

	public static CustomUserDetails from(Member member) {
		return new CustomUserDetails(
			member.getId(),
			member.getEmail(),
			Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
		);
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}

	@Override
	public String getPassword() {
		return null;
	}

	@Override
	public String getUsername() {
		return email;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}
}

6. 요약

시점CustomUserDetails 사용 여부설명
최초 로그인이메일/비밀번호로 DB와 직접 비교하여 인증.
로그인 후 API 요청JWT 검증 후, 토큰 정보로 CustomUserDetails을 생성하여 SecurityContext에 등록. 이후 권한 검사 및 사용자 정보 참조에 사용.
profile
개발 공부 기록

0개의 댓글