ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 현재 Spring Security 구조
    이커머스 devops 2026. 1. 7. 15:07

    로그인 전체 흐름

    1단계 : 로그인 요청

    클라이언트
        ↓
    POST /api/auth/login
    Content-Type: application/json
    {
      "email": "user@example.com",
      "password": "1234"
    }

     

     

    2단계: RestAuthenticationFilter 실행

    ┌────────────────────────────────────────────────┐
    │  RestAuthenticationFilter                      │
    │  (AbstractAuthenticationProcessingFilter)      │
    └────────────────────────────────────────────────┘
                        ↓
        attemptAuthentication() 호출
    1. HTTP Method 검증 : POST가 아니면 예외
    2. JSON 파싱
    3. 빈 값 검증
    4. 인증 토큰 생성 (인증 전)
    AuthenticationToken token = new AuthenticationToken(
           loginRequest.getEmail(),      // principal
           loginRequest.getPassword()    // credentials
    );
    └─ setAuthenticated(false)
    5. AuthenticationManager에게 인증 위임

     

     

    3단계 : AuthenticationManger -> AuthenticationProvider

    ┌────────────────────────────────────────────────┐
    │        AuthenticationManager                   │
    └────────────────────────────────────────────────┘
                        ↓
        "어느 Provider가 처리할 수 있나?"
                        ↓
    ┌────────────────────────────────────────────────┐
    │      RestAuthenticationProvider                │
    │      (AuthenticationProvider 구현)              │
    └────────────────────────────────────────────────┘
                        ↓
        authenticate() 호출
    1. supports() 확인 : AuthenticationToken 타입인가? 
    2. CustomUserDetailsService 호출 :
       UserDetails userDetails = userDetailsService.loadUserByUsername(email);
    3. DB 사용자 조회
    4. Account 객체 생성
    5. 비밀번호 검증
    6. 인증 토큰 생성 (인증 후)
    AuthenticationToken authenticated = new AuthenticationToken(
       account,                    // principal
       null,                       // credentials (지움)
       account.getAuthorities()    // 권한 (ROLE_USER, ROLE_ADMIN)
    );
    └─ setAuthenticated(true)  // 인증 완료
    7. 반환 : return authenticated;

     

     

    4단계 : 인증 성공 처리

    ┌────────────────────────────────────────────────┐
    │  RestAuthenticationSuccessHandler              │
    │  (AuthenticationSuccessHandler)                │
    └────────────────────────────────────────────────┘
                        ↓
        onAuthenticationSuccess() 호출
     1. Account 추출
     2. LoginResponse 생성
     3. ApiResponse 래핑
     4. JSON 응답
     5. 세션에 Authenticaiton 저장
     - spring security가 자동으로 처리
     - SecurityContext -> HttpSession -> Redis
     6. Redis 저장 (자동)
     Redis:
    ┌──────────────────────────────────────────────┐
    │ Key: spring:session:abc-123-def-456          │
    │                                              │
    │ Value: {                                     │
    │   "SPRING_SECURITY_CONTEXT": {               │
    │     "authentication": {                      │
    │       "principal": {                         │
    │         "userId": 100,                       │
    │         "email": "user@example.com",         │
    │         "username": "홍길동",                  │
    │         "gradeName": "GOLD",                 │
    │         "authorities": ["ROLE_USER"]         │
    │       }                                      │
    │     }                                        │
    │   },                                         │
    │   "creationTime": 1704614400000,             │
    │   "lastAccessedTime": 1704614400000          │
    │ }                                            │
    └──────────────────────────────────────────────┘
    7. 클라이언트에게 전송

     

     

    5단계 : 이후 요청 (인증 필요한 API)

    // 프론트엔드
    axios.get('/api/cart', {
      withCredentials: true  // 쿠키 자동 포함
    });
    
    ┌────────────────────────────────────────────────┐
    │           이후 모든 요청                           │
    └────────────────────────────────────────────────┘
                        ↓
        GET /api/cart
        Cookie: JSESSIONID=abc-123-def-456
    
    1. SecurityContextPersistenceFilter
       └─ 쿠키에서 세션 ID 추출: abc-123-def-456
    
    2. Redis에서 세션 조회
       GET spring:session:abc-123-def-456
       └─ SecurityContext 복원
       └─ Authentication 객체 로드
    
    3. SecurityContextHolder에 저장
       SecurityContextHolder
           .getContext()
           .setAuthentication(authentication);
    
    4. Controller 실행
       @GetMapping("/api/cart")
       public ResponseEntity<?> getCart(
           @AuthenticationPrincipal Account account  // ← 자동 주입
       ) {
           // account.getUserId() 사용 가능 
           // account.getEmail() 사용 가능
       }
    
    5. FilterSecurityInterceptor (권한 검사)
       └─ .authenticated() 설정 확인
       └─ Authentication 있음?
       └─ 통과

     

     

    인증 실패 시나리오

    [ 시나리오 1 : 비밀번호 틀림 ]

    RestAuthenticationProvider
        ↓
    비밀번호 검증 실패
        ↓
    throw new BadCredentialsException()
        ↓
    RestAuthenticationFailureHandler
        ↓
    onAuthenticationFailure() 호출

     

    [ 시나리오 2: 존재하지 않는 이메일 ]

    CustomUserDetailsService
        ↓
    userRepository.findByEmail(email)
        ↓
    없음
        ↓
    throw new ServiceException(CANNOT_FOUND_USER)
        ↓
    RestAuthenticationFailureHandler

     

    [ 시나리오 3: 로그인 안 하고 인증 필요한 API 호출 ]

    클라이언트
        ↓
    GET /api/orders
    Cookie 없음 (또는 잘못된 세션)
        ↓
    SecurityContextPersistenceFilter
        ↓
    세션에서 SecurityContext 못 찾음
        ↓
    FilterSecurityInterceptor
        ↓
    .authenticated() 설정인데 인증 안 됨
        ↓
    AccessDeniedException
        ↓
    ExceptionTranslationFilter
        ↓
    RestAuthenticationEntryPoint
        ↓
    commence() 호출

     

    [ 시나리오 4: 권한 부족 (ADMIN 전용 API에 USER가 접근) ]

    클라이언트 (ROLE_USER)
        ↓
    GET /api/admin/users
    Cookie: JSESSIONID=abc-123-def-456
        ↓
    SecurityContextPersistenceFilter
        ↓
    세션에서 Authentication 복원
    └─ authorities: [ROLE_USER]
        ↓
    FilterSecurityInterceptor
        ↓
    .hasRole("ADMIN") 설정 확인
    └─ 현재 권한: ROLE_USER
        ↓
    AccessDeniedException
        ↓
    RestAccessDeniedHandler
        ↓
    handle() 호출

     

     

    전체 컴포넌트 관계도

    ┌─────────────────────────────────────────────────────────┐
    │                    SecurityConfig                       │
    │  ┌───────────────────────────────────────────────────┐  │
    │  │ - 권한 설정 (permitAll, authenticated, hasRole)     │  │
    │  │ - CSRF 비활성화                                     │  │
    │  │ - 세션 관리 (IF_REQUIRED, 중복 로그인)                 │  │
    │  │ - 예외 핸들러 등록                                    │  │
    │  │ - 로그인 URL: /api/auth/login                       │  │
    │  └───────────────────────────────────────────────────┘  │
    └─────────────────────────────────────────────────────────┘
                        │
                        │ 사용
                        ↓
    ┌──────────────────────────────────────────────────────────┐
    │              Filter Chain (순서대로 실행)                   │
    ├──────────────────────────────────────────────────────────┤
    │                                                          │
    │  1️. SecurityContextPersistenceFilter                    │
    │     └─ 세션 → SecurityContext 복원                         │
    │                                                          │
    │  2️. RestAuthenticationFilter                            │
    │     └─ JSON 로그인 처리                                     │
    │     └─ /api/auth/login만 처리                              │
    │                                                          │
    │  3️. SessionManagementFilter                             │
    │     └─ 중복 로그인 체크                                      │
    │                                                          │
    │  4️. ExceptionTranslationFilter                          │
    │     └─ 예외 → 핸들러 연결                                    │
    │                                                          │
    │  5️. FilterSecurityInterceptor                           │
    │     └─ 권한 검사                                           │
    │                                                          │
    └──────────────────────────────────────────────────────────┘
                        │
                        ├─ 인증 필요 ───────────────┐
                        │                         │
                        ↓                         ↓
    ┌──────────────────────────────┐  ┌──────────────────────────────┐
    │  AuthenticationManager       │  │  CustomUserDetailsService    │
    │                              │  │                              │
    │  - Provider 선택              │→ │  - DB에서 사용자 조회            │
    │  - authenticate() 실행        │  │  - Account 객체 생성           │
    └──────────────────────────────┘  └──────────────────────────────┘
                        │
                        ↓
    ┌──────────────────────────────┐
    │  RestAuthenticationProvider  │
    │                              │
    │  - 비밀번호 검증                │
    │  - 인증 토큰 생성 (인증 후)       │
    └──────────────────────────────┘
                        │
            ┌───────────┴───────────┐
            │                       │
        성공 ↓                   실패 ↓
    ┌──────────────────┐  ┌──────────────────────┐
    │  SuccessHandler  │  │  FailureHandler      │
    │                  │  │                      │
    │  - LoginResponse │  │  - ErrorResponse     │
    │  - JSON 응답      │  │  - JSON 응답          │
    │  - 세션 저장       │  │  - 401 Unauthorized  │
    └──────────────────┘  └──────────────────────┘

     

     

    주요 컴포넌트

    RestAuthenticationFilter:
    └─ JSON 로그인 처리
    └─ LoginRequest → AuthenticationToken
    
    RestAuthenticationProvider:
    └─ 비밀번호 검증
    └─ DB 조회
    └─ 인증 토큰 생성
    
    CustomUserDetailsService:
    └─ UserRepository로 사용자 조회
    └─ User → Account 변환
    
    SuccessHandler:
    └─ LoginResponse 생성
    └─ JSON 응답
    └─ 세션 자동 저장 (Redis)
    
    FailureHandler:
    └─ ErrorResponse 생성
    └─ 401 응답

     

     

    세션 흐름

    로그인 성공:
    └─ Authentication → SecurityContext
    └─ SecurityContext → HttpSession
    └─ HttpSession → Redis
    └─ 세션 ID → 쿠키 (JSESSIONID)
    
    이후 요청:
    └─ 쿠키 (JSESSIONID) → Redis
    └─ Redis → HttpSession
    └─ HttpSession → SecurityContext
    └─ SecurityContext → Authentication
    └─ @AuthenticationPrincipal로 사용
    728x90

    '이커머스 devops' 카테고리의 다른 글

    로그인/로그아웃 인증 개선  (0) 2026.01.07
    동시성문제 - 비관적 락  (0) 2026.01.03
    스프링부트게시판 (4)  (0) 2025.11.29
    스프링부트 게시판 (3)  (0) 2025.11.28
    스프링부트 게시판 (2)  (0) 2025.11.27
Designed by Tistory.