본문 바로가기
Frontend

인증

by 잘먹는 개발자 에단 2024. 12. 17.

인증(세션 vs 토큰) 개요

세션(Session) 방식

  • 발급 방식:
    1. 사용자가 로그인을 하면, 서버는 "세션 키"를 발급
    2. 서버 내부 DB나 메모리에 사용자 id - 세션 키를 매핑해 저장
    3. 클라이언트에게 세션 키를 전달하고, 클라이언트는 이 키를 쿠키 등에 저장
  • 검증 방식:
    1. 클라이언트 요청 시 세션 키를 전송
    2. 서버는 자신이 가진 세션 저장소를 참조해 해당 세션 키의 유효성 및 해당 사용자와의 매칭 여부 확인
    3. 유효한 경우 응답 제공
  • 한계:
    • 서버 확장(수평 확장) 시 세션 공유 문제 발생
    • 세션 정보를 모든 서버에 공유하기 위해 추가적인 DB나 캐시(레디스 등) 필요

JWT(Token) 방식

  • JWT란?
    • RFC 7519에 정의된 표준화된 토큰 포맷
    • 헤더(Header), 페이로드(Payload), 시그니처(Signature)로 구성된 JSON 기반 토큰
    • 전자 서명(Digital Signature)을 포함하여 토큰 자체로 인증 유효성 검증 가능 → Stateless한 인증 방식
  • 발급 방식:
    1. 사용자가 로그인을 하면, 서버는 사용자 정보 등을 담은 JWT를 생성
    2. 생성 시 비밀키(대칭키)나 개인키(비대칭키)를 사용해 서명
    3. 서명된 JWT를 클라이언트에게 전달
  • 검증 방식:
    1. 클라이언트는 요청 시 Authorization: Bearer <JWT> 헤더로 토큰 전송
    2. 서버는 토큰을 받아 헤더, 페이로드, 시그니처로 분리 후, 서명 알고리즘(HS256, RS256 등)에 따라 토큰의 무결성 검증
    3. 서명 검증이 유효하다면, 페이로드의 정보(사용자 id, 만료시간 등) 기반으로 권한 확인 후 응답
  • 장점:
    • 서버 확장성 우수 (Stateless)
    • 별도의 세션 저장소 필요 없음
  • 단점:
    • 토큰 자체가 탈취당하면 토큰 만료시간 전까지 방어 불가
    • 토큰 무효화(Invalidation)를 서버 측에서 강제로 하기 어려움

Refresh 토큰

  • 개념:
    • Access 토큰보다 긴 유효기간을 가진 토큰
    • DB에 저장해두며, 만료 전 재발급 요청 시 유효성 검증 가능
    • Access 토큰 만료 시, Refresh 토큰 유효하면 새로운 Access 토큰 재발급 가능
  • 필요성:
    • Access 토큰 유효기간을 짧게 하여 보안 강화
    • 사용자가 자주 로그인하지 않고도 새로운 토큰 발급 가능
  • 한계:
    • Refresh 토큰도 탈취당하면 방법 없음
    • 이중 인증(2FA) 등 별도 보안 레이어 추가 필요

관련 개념 설명

  1. RFC(Request For Comments):
    • 인터넷 기술 표준을 정의하는 문서 시리즈
    • JWT는 RFC 7519 문서에 정의됨
  2. 서명(Signature):
    • 토큰의 무결성과 신뢰성을 증명하기 위한 전자적 서명
    • "이 토큰은 내가 발급한 것"이라는 서버 측 인증서 같은 역할
  3. Stateless:
    • 서버가 클라이언트 상태를 직접 관리하지 않는 아키텍처 스타일
    • JWT를 사용하면 토큰 자체가 상태 정보를 포함하고 서명으로 진위 여부 검증 가능 → 추가 DB 조회 최소화
  4. HS256, RS256 등 알고리즘:
    • HS256: HMAC-SHA256 기반 대칭키 방식(서버에 비밀키 하나)
    • RS256: RSA-SHA256 기반 비대칭키 방식(공개키/개인키 쌍 활용)

 

// 예시를 위한 패키지 설치 (설명용)
// npm install jsonwebtoken

const jwt = require('jsonwebtoken'); // jsonwebtoken 라이브러리 불러오기

// 비밀키 설정 (HS256 방식 예)
// 실제로는 환경변수나 안전한 저장소에 보관해야 함
const SECRET_KEY = 'your_secret_key_here'; // 대칭키

// 1. JWT 발급 함수
function generateJWT(userId) {
  // 페이로드 정의
  const payload = {
    sub: userId,            // 사용자 식별자
    iat: Math.floor(Date.now() / 1000), // 토큰 발급 시간(Unix Timestamp)
    exp: Math.floor(Date.now() / 1000) + (60 * 10) // 10분 후 만료 예제
  };

  // 토큰 발급
  const token = jwt.sign(payload, SECRET_KEY, { algorithm: 'HS256' }); 
  // sign 함수는 페이로드와 비밀키를 통해 서명된 JWT 발급
  return token;
}

// 2. JWT 검증 함수
function verifyJWT(token) {
  try {
    // verify 함수로 토큰 검증
    const decoded = jwt.verify(token, SECRET_KEY); 
    // 비밀키를 사용해 서명이 유효한지 검증, 유효하면 디코딩된 페이로드 반환
    return { valid: true, decoded };
  } catch (err) {
    // 검증 실패 시 에러 처리
    return { valid: false, error: err };
  }
}

// 예시 동작
const userId = 'user1234';                    // 가상의 사용자 아이디
const accessToken = generateJWT(userId);      // JWT 발급
console.log('발급된 Access Token:', accessToken); 

const verificationResult = verifyJWT(accessToken); // 토큰 검증
if (verificationResult.valid) {
  console.log('토큰 유효. 페이로드:', verificationResult.decoded);
} else {
  console.log('토큰 무효!', verificationResult.error);
}

 

 

 

Use Flow

1. 유저정보, 시크릿 키로 POST 리퀘 - 액세스토큰, 리프레시 토큰 발급

액세스토큰은 1시간 리프레시 토큰은 7일로 발급

 

2. 미들웨어 함수를 만듦

- 액세스토큰이 만료되면 리프레시 토큰을 체크할 것

- 리프레시 토큰을 DB에 저장했다면 ( 서버 간 정보 동기화의 필요성 ) 

    ㄴ 맞는지 확인

    ㄴ 맞으면 액세스토큰 재발급, 리프레시 토큰 갱신, 업데이트

 

- 리프레시 토큰을 DB에 저장하지 않았다면 ( Serverless 하나 보안이 위험 ) 

    ㄴ 별도 DB 확인 과정 없이, 맞으면 액세스 토큰 재발급, 리프레시 토큰 갱신