쿠키.. 세션..그리고 백엔드 개발자로서의 JWT 기반 인증 흐름 정리

Soohyeok Kim·2025년 6월 4일

앱 백엔드 개발을 해오다가 스프링부트 입문 후, 오랜만에 웹 개발을 하니까 쿠키와 세션쪽이 갑자기 좀 헷갈렸어서 정리해둔 내용입니다

쿠키란?

일단 딱딱한 정의부터 ㅋㅋ

쿠키는 브라우저(클라이언트)에 저장되는 데이터고 서버와 클라이언트가 상태를 유지하거나 인증 정보를 기억하기 위해 사용됨

쿠키의 동작 원리

  1. 사용자가 로그인 같은 요청을 보냄
  2. 서버는 응답하면서 Set-Cookie 헤더에 쿠키를 담아서 내려줌 (보통 accessToken, refreshToken 같은 인증 관련 JWT 토큰을 담거나, 혹은 JSON으로 내려주는 경우도 있음)
  3. 브라우저는 받은 쿠키를 저장함 (쿠키 스토리지)
  4. 이후 모든 요청마다 프론트가 Cookie 헤더에 자동으로 포함시켜서 보냄
  5. 서버는 그 Cookie 헤더를 읽고 파싱해서 로그인 되어있는지 판단함

쿠키의 주요 속성들

속성설명
name=value쿠키의 이름과 값 (기본)
Expires, Max-Age유효 기간 설정 (없으면 세션)
Path어느 경로 요청에서만 쿠키를 포함시킬지
Domain어느 특정 서브도메인까지 전송할지
SecureHTTPS에서만 전송됨 (사실상 필수)
SameSiteCSRF 공격 방어용 옵션 (Lax: 기본, Strict, None), 실무에선 필수로 챙기는듯 함
HttpOnly자바스크립트에서 쿠키 접근 불가. 보안 강화용 (사실상 필수)

사람들 하는거 보면 HttpOnly 옵션 많이 쓰던데….. 왜 씀?

쿠키에 HttpOnly 옵션을 붙이면 자바스크립트가 못 읽는데

못된 놈들이 XSS 공격을 통해 쿠키를 읽어갈 수 있는걸 방지할 수 있다 (보조수단 느낌) (인증토큰 탈취당하면…… ㄱ-)

그래서 보안이 강해진다 ㅋㅋ

쿠키 vs 세션 vs JWT

항목쿠키세션JWT
저장 위치브라우저 (쿠키 스토리지)서버 + 브라우저 (세션 ID 쿠키)브라우저 (로컬/세션스토리지, 메모리, 쿠키)
상태 저장쿠키에 직접 id 값 저장, jwt를 저장하기도 함서버가 세션에 사용자 정보 저장토큰 안에 사용자 정보를 포함 (stateless)(보통 subject는(sub) → userId값임),
DB에 RefreshToken을 저장해서 상태를 부분적으로 관리하는 경우도 많음
전송 방식쿠키 자동 전송sessionId 같은 값이 포함된 쿠키 자동 전송Authorization: Bearer 헤더로 수동 전송
인증 방식쿠키만 있으면 서버가 적절히 판단함서버가 id 보고 날 기억함토큰 자체가 신분증 역할을 함

JWT 인증 방식을 쓸 때, 프론트는 인증 토큰을 어떻게 저장하고 서버한테 보내며 서버는 그걸 어떻게 받고 인증처리를 어떻게 할까?????

  • 프론트는 딱히 뭘 안 해도 됨
  • 브라우저가 자동으로 쿠키 저장소에 넣음
  • 이후 요청마다 Cookie 헤더에 자동으로 붙여서 요청 보내짐
  • 백엔드는 @CookieValue 또는 필터에서 쿠키 파싱으로 읽음 → 자동 또는 커스텀 가능

애초에 HttpOnly 쿠키면 자바스크립트에서 못 꺼내기 때문에 프론트 개발자가 쿠키 컨트롤을 할 수 없다.

어라랍쇼 그럼 스프링 시큐리티 쓸 때 쿠키헤더에 담긴 쿠키... 어떻게 꺼내서 사용자 인증을 하지????

스프링 시큐리티를 쓸 때 JWT 방식 인증을 하려면 커스텀 Jwt 필터를 구현해 HttpSecurity 체인에 추가해줘야 함

그러면 구현한 커스텀Jwt필터 내부에 request.getCookies() 같은 코드를 통해 쿠키를 직접 파싱하고 sub(userId)가 담긴 accessToken을 꺼내서 유저 검증을 하게 됨
(스프링 시큐리티 필터 체인이 기본적으로 UsernamePasswordAuthenticationFilter 앞단이나 SecurityContextPersistenceFilter 앞단에서 JWT 인증 필터를 넣는 방식)

그 과정속에서 accessToken속 sub를 뽑아내기위해 jjwt 라이브러리의 JwtParser같은 게 쓰이고

accessToken 속에서 뽑은 userId로 OAuth2User(Principal임)를 뽑는 직접 구현한 서비스의 loadUser 메서드를 통해 인증된 유저 Principal을 생성하고

SecurityContextHolder에 인증된 유저 Principal을 set하면 사용자 인증이 완료된다.

추후 개발할 때는 적절히 SecurityContextHolder에 저장돼 있을 유저 도메인을
컨트롤러에서 @AuthenticationPrincipal CustomOAuth2User authentication처럼 꺼내서 쓰면 됨.

	@Override
    @PostMapping
    public ResponseEntity<ReservationResponse> reserve(
            @RequestBody @Valid ReservationRequest request,
            @AuthenticationPrincipal CustomOAuth2User authentication // 저장된 유저 principal
    ) {
        ReservationResponse result = seatReservationService.reserve(
                request.performanceId(),
                request.scheduleId(),
                authentication.getUser().getId(),
                request.quantity()
        );

        return ResponseEntity.status(HttpStatus.CREATED).body(result);
    }

2. 백엔드가 로그인 처리 후 생성한 JWT를 Response Body에 JSON으로 내려줄 때 (Bearer 토큰 방식 인증)

  • 프론트가 body에서 accessToken이나 refreshToken을 직접 꺼냄
  • 꺼낸 jwt토큰을 로컬스토리지 / 세션스토리지 / 메모리 등 원하는 데 저장
  • 이후 요청마다 Authorization: Bearer {token} 헤더에 붙여서 수동으로 전송

프론트가 토큰 저장, 토큰 전송을 직접 컨트롤해야 함

앱 개발 계획이 있다면 JWT로 처음부터 인증을 구현하는 편이고, SPA 환경에서는 주로 사용됨.

필터에서 쿠키를 뽑았던 부분만 request.getHeader("Authorization")로 바뀌고 적절히 파싱해서 accessToken을 뽑아내면 된다

profile
백엔드 개발자

0개의 댓글