Token Authentication vs. Session-based Auth

세션 기반 인증(Session-based Authentication)

세션 기반 인증은 전통적인 인증 방식으로, 서버가 사용자의 로그인 상태를 세션으로 유지하는 방식이다.

작동 원리

  1. 인증 과정:
    • 사용자가 자격 증명(사용자 이름/비밀번호)을 제출한다.
    • 서버는 자격 증명을 검증하고, 유효한 경우 고유한 세션 ID를 생성한다.
    • 서버는 세션 ID와 관련 사용자 정보를 서버 측 저장소(메모리, 데이터베이스, 캐시 등)에 저장한다.
    • 서버는 세션 ID를 클라이언트에게 쿠키로 전송한다.
    • 클라이언트는 이후 요청 시 이 쿠키를 자동으로 포함시킨다.
    • 서버는 쿠키의 세션 ID를 확인하여 사용자를 식별한다.
  2. 세션 수명 주기:
    • 세션은 사용자가 로그인할 때 생성된다.
    • 세션은 일정 시간이 지나면 만료된다(서버 설정에 따라 다름).
    • 사용자가 로그아웃하면 세션이 명시적으로 파기된다.
    • 서버는 세션의 유효성과 만료를 관리한다.

주요 특징

  • 상태 유지(Stateful): 서버가 세션 정보를 저장하고 관리한다.
  • 쿠키 기반: 주로 HTTP 쿠키를 통해 세션 ID를 전달한다.
  • 서버 측 저장소: 세션 데이터가 서버에 저장된다.
  • 간단한 구현: 대부분의 웹 프레임워크에서 기본적으로 지원한다.
  • 명시적인 세션 관리: 서버가 세션 생성, 검증, 만료, 파기를 제어한다.

토큰 인증(Token Authentication)

토큰 인증은 클라이언트에게 서명된 토큰을 발급하여 인증하는 방식이다. 가장 널리 사용되는 토큰 형식은 JWT(JSON Web Token)이다.

작동 원리

  1. 인증 과정:
    • 사용자가 자격 증명을 제출한다.
    • 서버는 자격 증명을 검증하고, 유효한 경우 토큰을 생성한다.
    • 토큰에는 사용자 식별자, 권한, 만료 시간 등의 정보가 포함되고 서명된다.
    • 서버는 토큰을 클라이언트에게 반환한다.
    • 클라이언트는 토큰을 저장하고(로컬 스토리지, 쿠키 등) 이후 요청의 헤더에 포함시킨다.
    • 서버는 토큰의 서명을 검증하여 사용자를 식별한다.
  2. 토큰 수명 주기:
    • 토큰은 발급 시 만료 시간이 설정된다.
    • 토큰은 클라이언트 측에서 관리된다.
    • 만료된 토큰은 자동으로 무효화된다.
    • 리프레시 토큰을 통해 접근 토큰을 갱신할 수 있다.

주요 특징

  • 무상태(Stateless): 서버는 클라이언트 상태를 저장하지 않는다.
  • 자체 포함적(Self-contained): 필요한 모든 정보가 토큰 자체에 포함된다.
  • 확장성: 서버 간 세션 공유 없이 수평적 확장이 용이하다.
  • 플랫폼 독립적: 다양한 클라이언트 플랫폼에서 사용 가능하다.
  • 유연한 저장: 토큰은 다양한 방식으로 클라이언트에 저장될 수 있다.

세션 기반 인증 vs. 토큰 인증 비교

특성세션 기반 인증토큰 인증
상태 관리Stateful(상태 유지)Stateless(무상태)
저장 위치주로 서버 측(세션 저장소)주로 클라이언트 측
확장성제한적(세션 공유 필요)높음(서버 간 공유 필요 없음)
트래픽적음(세션 ID만 전송)상대적으로 많음(전체 토큰 전송)
보안 요소세션 ID서명된 토큰(JWT 등)
구현 복잡성낮음(프레임워크 지원)중간(직접 구현 필요)
서버 자원높음(세션 저장)낮음(토큰 검증만)
인증 정보 저장서버 메모리/DB/캐시클라이언트(로컬 스토리지, 쿠키 등)
만료 처리서버에서 제어토큰 자체에 만료 시간 내장
로그아웃 메커니즘간단(세션 삭제)복잡(클라이언트 토큰 삭제 + 블랙리스트)
API 호환성제한적(쿠키 의존)우수(Authorization 헤더)
CSRF 취약성높음(대응책 필요)낮음(저장 방식에 따라 다름)
XSS 취약성중간(HttpOnly 쿠키)높음(로컬 스토리지 사용 시)
세션 탈취 위험세션 ID 탈취토큰 탈취
멀티 도메인어려움(쿠키 제한)용이함(헤더 기반)
모바일 지원제한적우수함
오프라인 처리불가능가능(토큰 저장 시)
마이크로서비스 적합성낮음높음
개발자 경험단순함복잡하나 유연함
데이터 크기작음(세션 ID만)큼(전체 페이로드)

심층 분석

구현 예시

세션 기반 인증 구현 (Node.js/Express):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const express = require('express');
const session = require('express-session');
const app = express();

// 세션 미들웨어 설정
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: { secure: true, httpOnly: true, maxAge: 3600000 } // 1시간
}));

// 로그인 라우트
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // DB에서 사용자 검증 (예시)
  const user = authenticateUser(username, password);
  
  if (user) {
    // 세션에 사용자 정보 저장
    req.session.user = {
      id: user.id,
      username: user.username,
      role: user.role
    };
    return res.status(200).json({ message: '로그인 성공' });
  }
  
  return res.status(401).json({ message: '인증 실패' });
});

// 인증 확인 미들웨어
function isAuthenticated(req, res, next) {
  if (req.session.user) {
    return next();
  }
  return res.status(401).json({ message: '인증이 필요합니다' });
}

// 보호된 라우트
app.get('/protected', isAuthenticated, (req, res) => {
  res.json({ message: '보호된 리소스', user: req.session.user });
});

// 로그아웃
app.get('/logout', (req, res) => {
  req.session.destroy();
  res.json({ message: '로그아웃 성공' });
});

토큰 인증 구현 (Node.js/Express, JWT):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();

const SECRET_KEY = 'your-secret-key';

// 로그인 라우트
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // DB에서 사용자 검증 (예시)
  const user = authenticateUser(username, password);
  
  if (user) {
    // JWT 토큰 생성
    const token = jwt.sign(
      { id: user.id, username: user.username, role: user.role },
      SECRET_KEY,
      { expiresIn: '1h' } // 1시간
    );
    
    return res.status(200).json({ message: '로그인 성공', token });
  }
  
  return res.status(401).json({ message: '인증 실패' });
});

// 인증 확인 미들웨어
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN" 형식
  
  if (!token) return res.status(401).json({ message: '토큰이 필요합니다' });
  
  jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) return res.status(403).json({ message: '토큰이 유효하지 않습니다' });
    
    req.user = user;
    next();
  });
}

// 보호된 라우트
app.get('/protected', authenticateToken, (req, res) => {
  res.json({ message: '보호된 리소스', user: req.user });
});

// 로그아웃 (클라이언트 측에서 토큰 삭제가 필요)
app.get('/logout', (req, res) => {
  // 서버 측에서는 특별한 작업이 필요 없음
  // 토큰 블랙리스트를 사용할 수 있음 (실제 구현은 더 복잡)
  res.json({ message: '로그아웃 성공' });
});

장단점 분석

세션 기반 인증의 장단점

장점:

  • 서버 측에서 세션을 완전히 제어할 수 있다.
  • 세션을 언제든지 무효화할 수 있어 로그아웃 처리가 간단하다.
  • 클라이언트에는 세션 ID만 저장되므로 데이터 노출 위험이 적다.
  • 대부분의 웹 프레임워크에서 기본적으로 지원하여 구현이 간단하다.
  • HttpOnly 쿠키 사용 시 XSS 공격으로부터 상대적으로 안전하다.

단점:

  • 서버 측 세션 저장소가 필요하여 서버 자원을 소비한다.
  • 다중 서버 환경에서는 세션 동기화 메커니즘이 필요하다.
  • 확장성 제한: 사용자가 많아질수록 세션 관리 부담이 증가한다.
  • CSRF(Cross-Site Request Forgery) 공격에 취약할 수 있다.
  • 모바일 애플리케이션이나 크로스 도메인 요청에서 쿠키 처리가 복잡하다.
토큰 인증의 장단점

장점:

  • 무상태 특성으로 서버 확장성이 우수하다.
  • 서버 측 저장소가 필요하지 않아 리소스 효율성이 좋다.
  • 다양한 도메인 및 서비스에서 사용 가능하다(크로스 도메인).
  • 모바일 애플리케이션 및 API 통합에 적합하다.
  • 마이크로서비스 아키텍처에 이상적이다.

단점:

  • 토큰 크기가 세션 ID보다 커서 네트워크 오버헤드가 발생할 수 있다.
  • 토큰 취소가 어렵다(별도의 블랙리스트 메커니즘 필요).
  • 로컬 스토리지에 저장 시 XSS 공격에 취약할 수 있다.
  • 민감한 정보가 토큰에 포함될 경우 보안 위험이 있다.
  • 토큰 관리 로직을 직접 구현해야 한다.

사용 시나리오 분석

세션 기반 인증이 적합한 경우

  1. 전통적인 웹 애플리케이션:
    • 서버 렌더링 페이지 중심의 애플리케이션
    • 단일 도메인 내에서 운영되는 애플리케이션
    • PHP, Ruby on Rails, Django 등의 전통적인 웹 프레임워크
  2. 높은 보안이 요구되는 애플리케이션:
    • 금융, 의료, 정부 서비스 등
    • 실시간 세션 모니터링과 즉시 무효화가 필요한 경우
    • 사용자 활동 추적이 중요한 경우
  3. 제한된 사용자 기반:
    • 사용자 수가 적거나 중간 규모인 애플리케이션
    • 확장성보다 구현 단순성이 중요한 경우

토큰 인증이 적합한 경우

  1. 단일 페이지 애플리케이션(SPA):
    • React, Angular, Vue.js 등으로 구축된 프론트엔드
    • API 중심 아키텍처
  2. 모바일 애플리케이션:
    • iOS, Android 애플리케이션에서 API 호출
    • 웹과 모바일 간의 일관된 인증 체계 필요
  3. 마이크로서비스 아키텍처:
    • 분산 시스템 환경
    • 서비스 간 인증 필요
  4. 대규모 확장이 필요한 시스템:
    • 많은 사용자와 트래픽을 처리해야 하는 경우
    • 클라우드 기반 수평적 확장 환경
  5. 크로스 도메인 서비스:
    • 여러 도메인이나 하위 도메인에 걸친 서비스
    • 써드파티 API 통합

하이브리드 및 현대적 접근법

실제 프로덕션 환경에서는 두 방식의 장점을 결합한 하이브리드 접근법이 사용되기도 한다:

  1. JWT in HTTP-only 쿠키:
    • JWT를 사용하되 HTTP-only 쿠키에 저장하여 XSS 보호
    • CSRF 대응책과 함께 사용
  2. 이중 토큰 방식:
    • 짧은 수명의 접근 토큰 + 긴 수명의 리프레시 토큰
    • 리프레시 토큰은 보안 쿠키에, 접근 토큰은 메모리에 저장
  3. Redis를 이용한 토큰 관리:
    • 토큰의 장점을 유지하면서 Redis와 같은 메모리 DB로 토큰 상태 관리
    • 즉시 토큰 무효화 가능
  4. 서비스별 인증 분리:
    • 핵심 인증은 세션 기반으로, API 통신은 토큰 기반으로 구현
    • 각 서비스의 특성에 맞는 방식 선택

기술적 고려사항

보안 측면

  1. XSS(Cross-Site Scripting) 보호:
    • 세션: HttpOnly 쿠키 사용으로 자바스크립트 접근 방지
    • 토큰: 메모리 저장 또는 HTTP-only 쿠키에 저장 고려
  2. CSRF(Cross-Site Request Forgery) 보호:
    • 세션: CSRF 토큰, SameSite 쿠키 속성 등으로 대응
    • 토큰: Authorization 헤더 사용 시 기본적으로 보호됨
  3. 토큰/세션 탈취 방지:
    • HTTPS 사용 필수
    • 적절한 만료 시간 설정
    • 사용자 IP, 기기 정보 검증(필요시)

성능 측면

  1. 네트워크 오버헤드:
    • 세션: 작은 쿠키 크기로 네트워크 효율성 높음
    • 토큰: 큰 토큰 크기로 상대적인 오버헤드 발생
  2. 서버 자원 사용:
    • 세션: 세션 저장소 필요, 메모리/DB 자원 소비
    • 토큰: 검증만 필요, 계산 리소스만 소비
  3. 확장성:
    • 세션: Redis, Memcached 등을 통한 세션 클러스터링 필요
    • 토큰: 추가 설정 없이 확장 가능

최신 트렌드

  1. 토큰 기반 인증의 증가:
    • 마이크로서비스 아키텍처와 SPA의 인기로 토큰 인증 채택 증가
    • JWT, OAuth 2.0, OpenID Connect의 광범위한 지원
  2. 보안 강화 메커니즘:
    • 다단계 인증(MFA) 통합
    • 생체 인식과 같은 추가 인증 요소
    • 토큰 바인딩(Token Binding)
  3. 세션 최적화:
    • 분산 캐시 시스템을 통한 효율적인 세션 관리
    • 비동기 세션 처리

용어 정리

용어설명

참고 및 출처