
핵심 질문 하나로 전부 이해하기
"로그인 상태를 누가 기억하나?"
| 세션 방식 | JWT 방식 | |
|---|---|---|
| 상태 보관 주체 | 서버 | 클라이언트 |
| 서버 특성 | Stateful (상태를 기억) | Stateless (상태를 기억 안 함) |
| 브라우저 저장 | 쿠키 (JSESSIONID) | 쿠키 또는 localStorage |
① 브라우저 → 서버 : ID/PW 전송
② 서버 : 세션 생성 후 세션 저장소(메모리/DB)에 저장
③ 서버 → 브라우저 : Set-Cookie: JSESSIONID=abc123
④ 브라우저 → 서버 : 이후 모든 요청에 쿠키 자동 첨부
⑤ 서버 : 세션 저장소에서 abc123 조회 → 사용자 확인
쿠키는 단순한 "열쇠(ID)" 만 전달
실제 사용자 정보는 서버 세션 저장소
// Spring Security + 세션 (기본 설정)
@Configuration
public class SecurityConfig {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); // 세션 사용
}
Cookie:)로 자동 전송되는 저장소JSESSIONID 를 쿠키에 담아 전달요약: 세션 로그인 = 서버 세션 + 쿠키(JSESSIONID) 조합입니다.
브라우저 세션 스토리지는 별개의 개념입니다.
① 브라우저 → 서버 : ID/PW 전송
② 서버 : JWT 생성·서명 (DB 저장 없음)
③ 서버 → 브라우저 : JWT 반환 (Header.Payload.Signature)
④ 브라우저 : localStorage 또는 HttpOnly 쿠키에 저장
⑤ 브라우저 → 서버 : Authorization: Bearer eyJ... 헤더에 첨부
⑥ 서버 : 서명 검증만으로 인증 완료 (DB 조회 없음)
JWT Payload 안에 사용자 ID, 권한, 만료시간 등이 이미 들어있습니다.
서버는 이 내용을 저장하지 않고, 서명(Signature)만 검증합니다.
eyJhbGciOiJIUzI1NiJ9 ← Header (알고리즘 정보)
.eyJ1c2VySWQiOjF9 ← Payload (사용자 정보, Base64 인코딩)
.SflKxwRJSMeKKF2QT4fw ← Signature (서버 비밀키로 서명)
// Spring Security + JWT 필터
public class JwtAuthFilter extends OncePerRequestFilter {
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = extractToken(request); // Authorization 헤더에서 꺼냄
if (token != null && jwtUtil.validate(token)) { // 서명 검증만 — DB 조회 없음
setAuthentication(token);
}
chain.doFilter(request, response);
}
}
세션 방식의 핵심은 쿠키가 자동으로 붙는 것인데,
React SPA는 보통 localhost:3000, Spring 서버는 localhost:8080 — Cross-Origin(다른 출처) 상황
Cross-Origin에서는 쿠키가 기본적으로 차단
React 쪽 (axios)
// withCredentials: true — 크로스 오리진에서도 쿠키 허용
axios.post('http://localhost:8080/login', data, {
withCredentials: true
})
// 또는 전역 설정
axios.defaults.withCredentials = true;
Spring 쪽
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000") // ← * 사용 불가, 정확한 origin 지정
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true); // ← 이 설정 필수
}
}
allowCredentials(true)와allowedOrigins("*")는 함께 쓸 수 없습니다.
반드시 정확한 origin을 명시해야 합니다.
| 항목 | 세션 방식 | JWT 방식 |
|---|---|---|
| 상태 보관 | 서버 (DB/메모리) | 클라이언트 (토큰) |
| 브라우저 저장 | 쿠키 (JSESSIONID) | 쿠키 또는 localStorage |
| 서버 부하 | 매 요청마다 세션 조회 | 서명 검증만 |
| 로그아웃 | 서버 세션 삭제 → 즉시 무효화 | 토큰 만료까지 대기 (블랙리스트 별도 관리) |
| 확장성 | 세션 저장소 공유 필요 (Redis 등) | 서버 어디서든 검증 가능 |
| React + Spring | CORS + withCredentials 설정 필요 | Authorization 헤더 수동 첨부 |
| 보안 | HttpOnly 쿠키로 XSS 방어 용이 | 토큰 탈취 시 만료 전까지 악용 가능 |