
프디아 교육 과정에서 인증 시스템에 대해 배우면서 JWT에 대한 궁금증이 생겼다.
전에도 프로젝트에 적용해본적은 있지만 잘 모르고 썼던 것 같아
이번 기회에 개념과 동작 방식, Node.js에서의 활용 방법까지 한눈에 정리해보고자 글을 작성해본다.
인증 시스템의 구조 이해하기
인증이란 무엇인가?
인증(Authentication)은 사용자가 주장하는 신원이 맞는지를 확인하는 과정이다. 예를 들어 아이디 + 비밀번호를 입력했을 때, 서버가 이를 확인하고 로그인에 성공시키는 것이 인증이다.
한 번 인증이 완료되면, 이후 요청에서도 인증된 사용자임을 확인할 수 있어야 하므로 토큰이나 세션 같은 방식으로 사용자 상태를 유지한다.
인증 시스템의 구조
인증 시스템은 주로 이렇게 동작한다.
- 사용자가 로그인 요청
- 서버가 사용자 정보를 확인
- 인증에 성공하면, 사용자 정보를 기억할 수단(Cookie, Session, JWT 등)을 발급
- 이후 요청에서 인증 정보를 함께 전송 -> 서버가 인증 여부 판단
🔍 쿠키, 세션, JWT가 없다면?
사용자의 로그인 상태를 유지할 수 없다.
HTTP는 stateless 프로토콜이기 때문에 방금 요청 보낸 사람이 누구였는지 기억하지 않는다.
인증 상태를 기억할 수 없다면, 사용자는 매번 요청마다 ID와 비밀번호를 보내야 한다.
쿠키 (Cookie)
쿠키란?
쿠키는 사용자의 브라우저에 저장되는 작은 데이터 조각이다.
서버가 클라이언트에게 특정 정보를 저장하도록 요청하면, 클라이언트는 해당 정보를 쿠키 형태로 저장하고, 이후 요청마다 해당 쿠키를 함께 전송한다.
주로 로그인 상태 유지, 사용자 맞춤 설정, 장바구니 정보 등을 저장할 때 사용된다.
쿠키의 특징
- 저장 위치: 클라이언트(브라우저)에 저장
- 자동 전송: 같은 도메인 요청 시, 해당 쿠키가 자동으로 함께 전송됨
- 유효기간 설정 가능: Expires 또는 Max-Age 속성을 통해 쿠키 만료 시점을 지정할 수 있음
- 접근 제어
- HTTPOnly: 자바스크립트로 접근 불가 (XSS 방지)
- Secure: HTTPS에서만 전송됨
- SameSite: 교차 요청 시 쿠키 전송 여부 제어 (CSRF 방지)
- SameSite 옵션
- Strict: 제 3자 사이트 요청에서는 쿠키 전송 X
- Lax: 안전한 요청(GET 등)에서는 허용, 그 외에는 X
- None: 교차 도메인에서도 전송 가능 (Secure 필수)
쿠키는 브라우저에 저장되기 때문에 보안적으로 민감한 정보는 절대 저장하지 않아야 하며, 반드시 필요한 경우 HttpOnly와 Secure 옵션을 활용해 보호해야 한다.
세션 (Session)
세션의 개념과 동작 방식
세션은 사용자의 인증 상태나 기타 정보를 서버가 직접 기억하는 방식이다.
사용자가 로그인하면 서버는 그 사용자에 대한 정보를 세션에 저장하고, 클라이언트에게는 세션을 식별할 수 있는 세션 ID만 전달한다
세션과 쿠키의 관계
세션은 사용자 정보를 서버에서 저장하고 관리하는 방식이고, 쿠키는 그 세션을 식별하기 위한 키를 클라이언트에 저장하는 수단이다.
[서버] <----- 세션 ID가 담긴 쿠키 -----> [클라이언트]
동작 흐름 요약
- 사용자가 로그인하면, 서버는 세션을 생성하고 고유한 세션 ID를 발급
- 서버는 이 세션 ID를 쿠키에 담아 클라이언트로 전송
- 브라우저는 이후 요청마다 이 쿠키(세션 ID 포함)을 자동으로 함께 전송
- 서버는 세션 ID를 통해 저장된 사용자 정보를 꺼내어 인증 여부를 확인
서버에서 세션을 어떻게 관리하는가?
서버는 클라이언트마다 고유한 세션 공간을 만들어 사용자 정보를 저장한다.
예를 들어 로그인한 유저의 ID, 닉네임, 권한 등을 세션에 담을 수 있다.
메모리(서버 RAM) 저장, 파일 기반 저장, 데이터베이스 저장, 인메모리 DB 저장 등을 통해 관리한다.
JWT (JSON Web Token)
JWT란 무엇인가?
JWT는 사용자의 인증 정보를 안전하게 담아 클라이언트에 전달하는 토큰 기반 인증 방식이다.
로그인 후, 서버가 사용자 정보를 바탕으로 토큰을 생성해 클라이언트에 전달하고, 이후 요청마다 이 토큰을 서버에 전송해 인증을 처리한다.
JWT는 서버가 사용자 상태를 저장하지 않아도 되는 stateless 인증 방식이다.
구조: Header / Payload / Signature
xxxxx.yyyyy.zzzzz
(Header.Payload.Signature)
Header (헤더)
토큰의 정보와 서명 방식을 담고 있다.
{
"alg": "HS256", // 서명에 쓴 알고리즘 (예: HMAC SHA-256)
"typ": "JWT" // 토큰 타입 = JWT
}
보통 변하지 않는 고정 정보이다.
서명을 만들 때 어떤 알고리즘을 썼는지 알려준다.
Payload (페이로드)
{
"sub": "1234567890", // 사용자 ID
"name": "유진",
"role": "admin",
"iat": 1718773100, // 발급 시각 (Issued At)
"exp": 1718776700 // 만료 시각 (Expiration Time)
}
실제 담고 싶은 정보가 들어있는 부분이다.
사용자 ID, 이름, 권한, 만료시간 등 다양한 정보를 담을 수 있다.
Base64로 인코딩만 되어 있고 암호화는 안 된다. -> 누구나 읽을 수 있다는 말
비밀번호나 민감 정보를 넣으면 안된다.
Signature (서명)
위 두 부분(Header + Payload)을 비밀키로 서명해서 위조를 막는 부분
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
클라이언트가 서버에서 받은 토큰을 수정하지 못하도록 검증하는 용도이다. 서버는 요청이 들어오면 Signature를 다시 계산해서 검증한다.
동작 흐름 요약
- 사용자가 로그인하면, 서버는 사용자 정보를 검증한 뒤 JWT 토큰을 생성한다.
- 이 토큰은 사용자 정보와 만료 시간 등이 담겨 있고, 서버의 비밀키로 서명 되어 위조를 방지한다.
- 서버는 이 JWT를 클라이언트에 응답으로 전달하고, 클라이언트는 토큰을 localStorage, sessioinStorage, 혹은 쿠키 등에 저장한다.
- 이후 사용자는 요청을 보낼 때마다 이 JWT를 Authorization 헤더에 포함시켜 함께 전송한다.
- 서버는 이 요청을 받을 때마다, 토큰의 서명을 검증하고, 만료 여부를 확인하여 인증된 사용자 요청인지 판단한다.
- 별도의 세션 저장 없이, 토큰 자체로 인증을 처리하는 방식이기 때문에 서버는 무상태(stateless)를 유지할 수 있다.
JWT 발급과 검증: jsonwebtoken
토큰 발급 예제
사용자가 로그인에 성공했을 때, 서버는 해당 사용자의 정보를 바탕으로 JWT를 발급한다.
jsonwebtoken 라이브러리를 사용하면 Node.js에서 다음과 같이 작성할 수 있다.
const jwt = require('jsonwebtoken');
function generateToken(user) {
const payload = {
id: user.id,
email: user.email,
role: user.role,
};
const secret = process.env.JWT_SECRET || 'mySecretKey';
const options = {
expiresIn: '1h', // 토큰 만료 시간 (예: 1시간)
issuer: 'my-app', // 발급자 정보
};
const token = jwt.sign(payload, secret, options);
return token;
}
// 예시
const user = { id: 1, email: 'user@example.com', role: 'admin' };
const token = generateToken(user);
console.log(token);
토큰 검증 흐름
클라이언트가 서버에 요청을 보낼 때, JWT를 Authorization 헤더에 담아 보낸다.
Authorization: Bearer <토큰>
서버는 이 토큰을 검증해서 사용자의 신원을 확인한다.
const jwt = require('jsonwebtoken');
function verifyToken(token) {
const secret = process.env.JWT_SECRET || 'mySecretKey';
try {
const decoded = jwt.verify(token, secret);
console.log('토큰 검증 성공:', decoded);
return decoded;
} catch (err) {
console.error('토큰 검증 실패:', err.message);
return null;
}
}
// 예시
const decoded = verifyToken(token);
- 검증에 실패하면 에러가 발생하므로 try-catch로 감싸야 한다.
- decode 객체에는 발급 시 설정했던 payload가 들어있다.
인증 미들웨어에서의 활용
Express를 예시로, 인증이 필요한 라우터에 JWT 검증을 미들웨어로 적용할 수 있다.
const jwt = require('jsonwebtoken');
function verifyToken(token) {
const secret = process.env.JWT_SECRET || 'mySecretKey';
try {
const decoded = jwt.verify(token, secret);
console.log('토큰 검증 성공:', decoded);
return decoded;
} catch (err) {
console.error('토큰 검증 실패:', err.message);
return null;
}
}
// 예시
const decoded = verifyToken(token);
- JWT는 stateless한 인증 방식이라 서버에 별도의 세션 저장소가 필요 없다.
- 클라이언트가 요청마다 토큰을 보내면, 서버는 토큰만 검증하고 신원 확인 가능하다.
- 토큰 탈취 위험이 있으니 HTTPS 사용과 만료 시간 관리, Refresh Token 전략 등을 반드시 함께 고려해야한다.
이번 미니 프로젝트에서 직접 JWT를 사용해보면서, 필요한 정보를 토큰에 담아 쿠키에 저장하고 다시 해독하는 과정이 흥미로웠다.
실제로 적용하면서 인증 흐름을 더 깊이 이해할 수 있는 좋은 경험이었다!
'프로디지털아카데미' 카테고리의 다른 글
| HTTP Request & Response (2) | 2025.08.17 |
|---|---|
| 내 주력 언어의 특징 - JAVA (Feat. KPT 회고) (10) | 2025.08.10 |
| [Open API를 활용한 금융서비스 프로젝트] SumEarly 발표 후 회고 (8) | 2025.08.01 |
| [TypeScript] JavaScript에 타입을 더하면? TypeScript! (1) | 2025.06.13 |
| [PDA] 프론트엔드 입문 첫걸음: JavaScript 개념 정리 모음 (3) | 2025.06.08 |