-
현재 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