Phoenix 백엔드 트러블슈팅 정리

윤슬·2025년 11월 4일

Phoenix 백엔드 트러블슈팅 정리

“Spring Security, 세션, CSRF, CORS, OAuth2, STT/TTS까지 —
Phoenix 프로젝트의 문제 해결 기록”


1. JWT → 세션 기반 인증 전환 이슈

문제 상황

초기 Phoenix는 JWT 기반 인증을 사용했지만,
React 프론트와의 통신 과정에서 토큰 만료, 재발급, 로그아웃 동기화 문제 발생.
특히 JWT가 LocalStorage에 저장되면서 보안 위험이 커졌음.

원인 분석

  • JWT는 서버에 상태가 없기 때문에(refresh 토큰 관리 포함) 로그아웃 시 즉시 무효화가 어려움.
  • LocalStorage에 저장된 토큰은 XSS 공격 시 노출될 수 있음.

해결 방법

  • 세션 기반 인증으로 구조 전환.
  • 로그인 성공 시 SecurityContextHolder에 인증 정보 저장 → 세션에 등록.
  • React에서는 쿠키(JSESSIONID)를 자동 전송하도록 axios.withCredentials=true 설정.
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());

→ 브라우저 쿠키를 통한 자동 인증 유지 + 즉시 로그아웃 가능 구조 완성.

2. CSRF 비활성화 (.csrf().disable()) 이유

문제 상황

Security 설정에서 CSRF 보호를 비활성화했는데,
“보안적으로 괜찮은가?”라는 의문 발생.

원인 분석

초기 구조는 JWT 기반 (Stateless) 이었음.

브라우저가 쿠키를 자동 전송하지 않기 때문에 CSRF 공격이 불가능한 상태였음.

해결 방법

현재는 세션 기반 쿠키 인증으로 전환되었지만,
Spring Boot 3.x의 SameSite=Lax 기본 정책 덕분에 기본 수준의 보호 가능.

개발 단계에서는 .csrf().disable() 유지,
배포 시에는 SameSite 정책 명시 또는 CSRF 토큰 활성화 권장.

server.servlet.session.cookie.same-site=Lax
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true

정리:

JWT → 세션으로 바뀌면서 CSRF 가능성이 생겼지만,
SameSite와 CORS 정책 덕분에 개발 환경에서는 안전함.

3. CORS 설정 문제

문제 상황

React(5173)에서 Spring(8080)으로 요청 시 CORS 오류 발생
(Access-Control-Allow-Origin 에러).

원인 분석

브라우저 보안 정책상 Origin이 다르면 쿠키 포함 요청 불가능.

CORS 미설정 시 서버가 “이 출처에서 오는 요청”을 신뢰하지 않음.

해결 방법

SecurityConfig에서 CORS 설정 명시:

.cors(cors -> cors.configurationSource(request -> {
    var config = new org.springframework.web.cors.CorsConfiguration();
    config.setAllowedOriginPatterns(List.of("http://localhost:5173"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
    config.setAllowCredentials(true);
    return config;
}))

결과:

React ↔ Spring 간 세션 쿠키 통신 정상 작동.

4. SameSite 정책과 CSRF의 관계

문제 상황

“왜 CSRF.disable()인데도 공격이 안 되지?”라는 의문 발생.

원인 분석

Chrome, Edge, Firefox 등 최신 브라우저에서
SameSite=Lax가 기본값으로 적용됨.

외부 사이트에서 자동으로 폼 요청을 보내도
쿠키(JSESSIONID)가 전송되지 않음 → CSRF 불가능.

해결 방법

SameSite 정책을 명시적으로 설정해 명확히 관리.

server.servlet.session.cookie.same-site=Lax
server.servlet.session.cookie.http-only=true

정리:

브라우저의 SameSite 정책이 기본적인 CSRF 방어막 역할을 함.

5. 인증/인가 구조 정리

문제 상황

Security 설정에서 authenticated()와 permitAll()을 구분했지만
“권한(Role) 체크는 어디서 하냐?”는 혼란 발생.

원인 분석

현재 Phoenix는 Role 기반 인가(hasRole()) 대신
서비스 단에서 권한 검증을 수행.

즉, Security는 “로그인 여부”까지만 담당.

해결 방법

Controller → Service 단계에서 로그인 사용자와 리소스 주인 비교.

if (!reservation.getMno().equals(currentMember.getMno())) {
    throw new AccessDeniedException("본인 예약만 취소할 수 있습니다.");
}

정리:

Security는 “인증”만 담당,
“인가(권한)”은 서비스 로직에서 직접 제어.

6. 세션과 SecurityContextHolder 동작 이해

문제 상황

로그인 후 다른 페이지에서 인증 정보가 안 불러와지는 현상 발생.

원인 분석

SecurityContextHolder는 스레드 로컬 저장소 기반.

요청이 바뀔 때마다 세션에 저장된 SecurityContext를 다시 로드해야 함.

해결 방법

Spring Security의 기본 설정(SessionCreationPolicy.IF_REQUIRED)을 유지.
→ 로그인 시 SecurityContext 자동 저장,
→ 세션이 존재하면 로그인 정보 자동 복구.

결과:

페이지 새로고침 시에도 로그인 상태 유지.

profile
우상향그래프

0개의 댓글