IT 성장기 (교육이수)/크래프톤정글 (2025.03-07)

[크래프톤정글] 인증 방식 : JWT vs 세션 & 쿠키

eezy 2025. 3. 13. 23:47

웹 서비스에서의 인증 방식과 보안 개념을 세션, 쿠키, JWT로 알아보자.

 

1. Stateless & Stateful

2. 세션 & 쿠키 기반 인증

3. JWT 기반 인증

4. 세션과 JWT의 차이점

5. 보안 개념


아래 다뤄볼 개념에 대한 미리보기로 개념에 대한 연관관계는 다음과 같이 볼 수 있다.

인증 방식
├── Stateful 인증 (서버가 상태를 유지)
│   ├── 세션 기반 인증 (Session)
│   └── 쿠키 기반 인증 (Cookie)
│
└── Stateless 인증 (서버가 상태를 유지하지 않음)
    └── JWT 기반 인증 (JSON Web Token)
        ├── Access Token (단기 인증, API 요청 시 포함)
        └── Refresh Token (장기 인증, Access Token 갱신용)

보안 개념
├── 암호화 (Encryption)
│   ├── 대칭 암호화 (AES, DES) → 하나의 키로 암호화/복호화
│   └── 비대칭 암호화 (RSA, ECC) → 공개키 암호화, 개인키 복호화
│
├── 복호화 (Decryption)
│   ├── 대칭키 복호화: 암호화와 같은 키 사용
│   └── 비대칭키 복호화: 개인키를 사용하여 복호화
│
├── 서명 (Signature)
│   ├── 전자 서명 방식 사용 (HMAC-SHA256, RSA, ECDSA)
│   └── 인증서 기반 서명 (X.509 인증서)
│
├── 해싱 (Hashing)
│   └── 데이터를 고정된 길이의 해시값으로 변환 (단방향)
│
└── 인코딩 (Encoding)
    ├── 데이터를 특정 형식으로 변환하는 과정 (보안 목적 없음)
    └── Base64, URL Encoding, UTF-8 등의 방식 존재

 

Stateless & Stateful

인증 방식에 대해 알아보기 전에, Stateless (무상태)와 Stateful (상태유지)의 개념에 대해 먼저 짚고 넘어가야 한다.

 

HTTP는 기본적으로 Stateless 프로토콜이다. Stateless란 서버가 클라이언트의 상태를 저장하지 않으며, 각 요청은 이전 요청과 독립적으로 처리된다. 즉 서버는 사용자가 누구인지 기억하지 못하기 때문에 매번 클라언트가 본인을 인증하는 정보를 보내야 한다.

 

Stateful은 서버가 클라이언트의 상태를 보존하며 이전 요청과 현재 요청이 연관된다. 이 경우 클라이언트는 매 요청마다 상태 정보를 보내지 않아도 된다. 이 두 개념의 차이는 쇼핑몰에서 제품을 구매하는 상황을 예시로 이해하면 쉽다.

 

개념 Stateful Stateless
역할 고객 = 클라이언트 / 점원 = 서버
상황 고객 : 이 티셔츠 얼마인가요?
점원: 1만원 입니다.

고객 : 2개 구매할게요.
점원: 2만원 입니다. 신용카드, 현금 중에 어떤 방식으로 구매하시겠어요?

고객 : 신용카드로 구매할게요.
점원 : 2만원 결제 완료 되었습니다.
고객 : 이 티셔츠 얼마인가요?
점원: 1만원 입니다.

고객 : 2개 구매할게요.
점원 : 무엇을 2개 구매하시나요?

고객: 신용카드로 구매할게요.
점원 : 무엇을 몇개 신용카드로 구매하시나요?
인증
방식
세션 & 쿠키 기반 인증 JWT

세션 & 쿠키 기반 인증

세션의 동작 방식

  1. 서버가 세션 ID를 생성하고 DB에 저장
  2. 클라이언트가 세션 ID를 받아 쿠키에 저장
  3. 클라이언트는 요청을 보낼 때 마다 쿠키에 세션 ID를 자동으로 포함하여 서버로 전송
  4. 서버는 세션 ID를 검증(verify) 후, DB에서 사용자 정보를 조회
  5. 조회된 userID로 원하는 정보를 제공

쿠키는 어디에 저장되는가?

쿠키도 HTTP 헤더에 포함되지만, 요청 시 자동으로 Cookie 헤더에 포함된다. 예시 코드는 다음과 같다.

GET /dashboard HTTP/1.1  
Host: example.com  
Cookie: session_id=abc123

 

세션 기반 인증의 문제점

  • 서버 확장성이 떨어진다 -> 서버는 세션 ID를 저장하므로, 모든 서버가 같은 세션 ID를 공유해야 함
  • DB 조회가 필요하다 -> 세션 ID로 사용자 정보를 알 수 없어, 사용자를 식별하는 추가 작업이 필요하다

JWT 기반 인증

JWT (JSON Web Token)의 동작 방식

  1. 서버가 Access Token을 생성하고 그 안에 사용자를 식별할 수 있는 최소의 정보를 넣음 (userID, 만료일자 등)
  2. 클라이언트가 Access Token을 저장
  3. 클라이언트는 API 요청 시, Authorization 헤더에 Access Token을 포함하여 전송 (자동 포함되지 않음)
  4. 서버는 Access Token을 Access_Token_Secret으로 복호화(decrypt)하여 userID를 추출함
  5. userID로 원하는 정보를 서버에서 찾음

JWT는 어디에 저장되는가?

Access Token은 Authorization 헤더에 포함되 전송된다. 세션 인증 방식에서는 쿠키는 요청이 발생하면 자동으로 헤더에 포함되지만, JWT의 경우 헤더에 포함하도록 설정이 필요하다. 예시 코드는 다음과 같다

GET /dashboard HTTP/1.1  
Host: example.com  
Authorization: Bearer eyJhbGciOiJIUzI1...

 

JWT 기반 인증의 장점

  • Stateless 인증으로 서버에 저장되는 정보가 없음
  • JWT에 사용자를 식별하는 정보가 포함되어 있어, DB 조회 없이 인증이 가능하다
  • 서버 확장성이 좋다. (DB 조회가 필요 없기 때문)

JWT 기반 인증의 단점

  • 토큰에 사용자 정보가 포함되어 유출 시 위험하다
  • 권한 변경 시 즉시 적용되지 않음
  • 서버에서 강제로 무효화 할 방법이 없다 (DB 조회가 없기 때문)

Access Token 과 Refresh Token

 

토큰에는 사용자 정보가 포함되어 있으며, 이는 Base64로 인코딩되어 전달된다. 따라서 토큰이 탈취되면 사용자 정보가 노출될 위험이 있으며, 유효 기간이 길수록 보안 위협이 커진다. 이를 해결하기 위해 Access Token과 Refresh Token이 도입되었다.

 

Access Token : 사용자 인증을 증명하기 위한 단기 유효 토큰

  • API 요청마다 포함되어야 한다
  • 서버는 Base64 인코딩된 정보를 디코딩하여 사용자 정보를 식별할 수 있다. 
  • 저장소 : 메모리, HttpOnly 쿠키
  • 유효 기간 : 약 15분 ~ 30분

Refresh Token : Access Token이 만료되었을 때 새로운 Access Token을 발급받기 위한 토큰

  • API 요청마다 보내지 않으며, 재발급 요청 시에만 사용
  • 사용자 정보를 포함하지 않음
  • 저장소 : HttpOnly 쿠키, DB (안전한 저장소에 보관)
  • 유효 기간 : 약 7일, 30일, 90일 등

여기서의 의문은, 그러면 Refresh Token은 사용자 정보를 포함하지 않는데 왜 비교적 안전한 저장소에 저장하는가? 이는 Refresh Token의 사용 용도 때문이다.

 

Refresh Token은 비교적 긴 유효 기간을 가지며, 이를 이용해 새로운 Access Token을 계속 발급받을 수 있기 때문에 안전한 저장소에 보관해야 한다. 만약 Refresh Token이 탈취되면, 공격자는 이를 이용해 사용자가 모르는 사이에 Access Token을 재발급받아 계정을 지속적으로 사용할 수 있다.

 

이러한 위험을 방지하려면 비밀번호 변경 시 기존 Refresh Token을 반드시 무효화해야 한다.
그 이유는, 만약 공격자가 이미 계정을 탈취하여 비밀번호를 본인 것으로 변경했다면 기존 계정 복구가 어렵지만, 아직 비밀번호를 바꾸지 못한 상태라면 피해를 막을 기회가 남아 있다.

 

예를 들어, 사용자가 본인 계정이 해킹된 것 같아 비밀번호를 변경했다고 가정하자.

 

그런데 기존 Refresh Token이 그대로 남아 있다면, 공격자는 여전히 새 Access Token을 발급받아 로그인 상태를 유지할 수 있다. 즉, 비밀번호 변경이 아무런 효과가 없어진다. 하지만 비밀번호 변경 시 기존 Refresh Token을 무효화하면, 공격자는 더 이상 새로운 Access Token을 받을 수 없게 되고, 자동으로 로그아웃되며 공격을 이어갈 수 없게 된다.

세션과 JWT의 차이점

항목 세션 기반 인증 JWT 기반 인증
방식 서버가 세션 ID를 생성하여 관리 서버가 JWT를 생성하여 클라이언트가 직접 관리
저장 위치 세션 ID는 서버에서 관리 
쿠키는 클라이언트에 저장
JWT를 클라이언트가 보관
상태 Stateful Stateless
확장성 낮음 높음
보안 세션 ID 탈취 시 계정 탈취 JWT 탈취 시 복호화 없이 사용 가능
로그아웃 처리 서버에서 세션 삭제 시 즉시 로그아웃 서버에서 무효화 불가능 -> Blacklist 필요
사용사례 내부 시스템 분산 시스템

 

이를 더 쉽게 이해하기 위해서 다음과 같은 비유로 예시를 들어보자. 

 

🎟️ 놀이공원에 입장하기 🎟️
✅ 세션(Session) = 놀이공원의 관리 시스템
✅ 쿠키(Cookie) = 입장권(티켓)
✅ 클라이언트(Client) = 방문객(사용자)
✅ 서버(Server) = 놀이공원 직원

💡 과정
1️⃣ 🎟️ 입장할 때(로그인) → 놀이공원(서버)은 방문객(사용자)에게 입장권(쿠키) 을 발급함.

이 입장권에는 고유한 번호(세션 ID) 가 적혀 있음.
관리 시스템(세션 DB)에는 이 번호가 누구의 것인지 저장됨.
2️⃣ 🏰 놀이기구를 타려면(요청할 때) → 방문객은 항상 입장권(쿠키) 을 보여줘야 함.

놀이공원 직원(서버)은 입장권에 적힌 번호(세션 ID)를 확인함.
그리고 관리 시스템(세션 DB)에서 방문객 정보를 찾아봄.
3️⃣ 🎢 유효한 입장권이면(세션이 유효하면) 놀이기구 이용 가능!

하지만 입장권이 없거나, 위조되었거나, 유효기간이 만료되면(세션이 끊기면) 놀이기구를 탈 수 없음.
그럴 경우, 다시 표를 사야 함(재로그인).
✅ 즉, 세션(관리 시스템)이 방문객의 정보를 관리하고, 쿠키(입장권)를 통해 인증하는 것!

🔥 쿠키와 JWT 비교 (VIP 패스 vs 일반 티켓)
💳 쿠키 기반 세션 = 일반 입장권

입장권이 있으면 언제든지 놀이기구를 탈 수 있음.
하지만 놀이공원 시스템(세션 DB)에서 항상 확인해야 함.
입장권(쿠키)이 없으면 서버는 아무것도 못함.

🔐 JWT = VIP 패스
VIP 패스에는 방문객 정보가 직접 적혀 있음.
직원(서버)이 시스템(세션 DB)을 확인하지 않아도, 패스를 보면 방문객 정보를 바로 알 수 있음.
단, VIP 패스가 유출되면 큰일 남! (그래서 짧은 시간 안에 새로 발급해야 함 → Access Token + Refresh Token 사용)

보안 개념

JWT, 쿠키, 세션과 같은 인증 방식에는 암호화(Encryption), 복호화(Decryption), 서명(Signature), 해싱(Hashing), 인코딩(Encoding) 등의 개념이 사용된다.

 

이러한 개념들은 암호학(Cryptography)과 보안(Security) 분야에서 매우 방대한 내용을 포함하고 있지만, 여기서는 JWT 및 세션 기반 인증에서 핵심적으로 사용되는 개념만 간단히 정리하고 넘어간다.

 

서명 (Signature)

  • 목적 : 데이터가 변조되지 않음을 검증 (무결성 보장)
  • 방법 : 데이터를 해싱하고 Secret Key로 서명
  • 특징 : 서명은 변경할 수 없지만, 데이터 자체는 읽을 수 있다. 
  • 전자 서명 방식 (HMAC-SHA256, RSA, ECDSA)
  • 인증서 기반 서명 (X.509 인증서)
  • 은행 등 고신뢰 시스템에서 전자 서명 사용

서명은 주로 JWT 방식에서 사용된다. 유저 정보를 Base64로 인코딩 후 비밀키 (Secret Key)로 서명한다. 그 이유는 인코딩된 정보는 디코딩을 통해 언제든지 변조가 가능하기 때문이다. 서버는 서명을 확인 (verify)하여 토큰의 변조 여부를 확인한다.

 

여기서 세션 ID도 Secret Key를 이용해 서명한다라는 문장으로 혼돈이 있었다. 결론적으로는 세션 ID는 기본적으로 랜덤 문자열이지만 보안 강화를 위해 Secret Key를 활용할 수 있다. 즉 세션 ID를 단순한 UUID가 아니라 HMAC 같은 서명 방식으로 생성이 가능하다. 그러나 대부분의 웹 프레임워크는 (PHP, Django 등) 통상적으로 세션 ID에 서명 하지 않는다. 

 

암호화와 복호화

  • 암호화 : 데이터를 읽을 수 없도록 변환
  • 복호화 : 암호화된 데이터를 원래의 형태로 복원하는 과정
  • 대칭 암호화 (AES, DES) : 하나의 키로 암호화 / 복호화
  • 비대칭 암호화 (RSA, ECC) : 공개키로 암호화, 개인키로 복호화
  • HTTP 통신에서 사용한다 (SSL / TLS)

인코딩

  • 데이터를 특정 형식으로 변환하는 과정
  • 암호화와 다르게 보안 목적을 띄지 않으며, 인코딩된 데이터를 보호하기 위해 암호화가 필요하다
  • 방식 : Base64, URL Encoding, UTF-8 

해싱 (Hashing)

  • 데이터를 고정된 길이의 해시값으로 변환 (단방향) -> 같은 입력값은 항상 같은 해시값을 가진다
  • 단, 해시값으로 원래 데이터를 복구할 수 없다 (복호화 불가)
  • 목적 : 비밀번호 저장 시 사용
  • 방식 : MD5 < SHA-1 < SHA-256 < bcrypt < PBKDF2 < Argon2 (왼쪽부터 취약 -> 안전한 순으로 나열)

해싱만으로는 충분한 보안이 보장되지 않으며, 해시값을 노리는 다양한 공격 방식이 존재한다. 해싱의 보안 취약점은 다음과 같다. 

 

Rainbow Table 공격

해시된 값이 이미 계산된 해시 테이블 (레인보우 테이블)과 일치하는지 비교하여 원래 값을 찾는 방법

해결책으로는 Salt 추가 방식이 있다. 솔트는 해싱 전에 랜덤 문자열을 추가하여 동일한 입력값이라도 해시값이 다르게 나오도록 만드는 것이다. 

import bcrypt
password = b"mypassword"
salt = bcrypt.gensalt()
hashed_pw = bcrypt.hashpw(password, salt)

 

Brute Force 공격 (무차별 대입 공격)으로 가능한 모든 조합의 값을 해싱하여 비교하여 공격이 가능하다. 해결책으로는 연산 비용이 높은 알고리즘을 사용한다.