안녕하세요, 오늘은 현대 웹 개발에서 중요한 역할을 하는 JWT(JSON Web Token)에 대해 자세히 알아보겠습니다. 이 글에서는 JWT의 기본 개념부터 구조, 그리고 실제 사용 방법까지 다룰 예정입니다.
1. JWT란 무엇인가?
JWT의 정의 및 개요
JWT는 JSON Web Token의 약자로, 당사자 간에 정보를 안전하게 전송하기 위한 개방형 표준(RFC 7519)입니다. 이 토큰은 JSON 객체로 인코딩되어 있으며, 디지털 서명이 되어 있어 신뢰성을 보장합니다.
JWT는 주로 인증(Authentication)과 정보 교환에 사용됩니다. 웹 애플리케이션에서 사용자가 로그인하면, 서버는 JWT를 생성하여 클라이언트에게 전달합니다. 이후 클라이언트는 이 토큰을 사용하여 서버에 요청을 보낼 때마다 자신의 신원을 증명합니다.
JWT의 탄생 배경 (Auth0와 RFC 7519)
JWT의 개념은 Auth0라는 회사에서 처음 제안되었습니다. Auth0는 개발자들이 인증과 권한 부여를 쉽게 구현할 수 있도록 돕는 플랫폼을 제공하는 회사입니다. 그들은 기존의 인증 방식들이 가진 한계를 극복하고자 JWT를 고안했습니다.
2015년 5월, JWT는 IETF(Internet Engineering Task Force)에 의해 RFC 7519로 표준화되었습니다. 이로써 JWT는 공식적인 웹 표준이 되었고, 많은 개발자들이 이를 채택하기 시작했습니다.
다른 인증 방식과의 비교 (세션 기반 인증과의 차이)
JWT를 이해하기 위해서는 기존의 세션 기반 인증과 비교해 보는 것이 도움이 됩니다.
- 세션 기반 인증:
- 사용자가 로그인하면 서버에 세션을 생성하고 세션 ID를 클라이언트에게 전달합니다.
- 클라이언트는 이 세션 ID를 쿠키에 저장하고, 매 요청마다 이를 서버에 전송합니다.
- 서버는 세션 저장소에서 해당 세션 ID를 확인하여 사용자를 인증합니다.
- JWT 기반 인증:
- 사용자가 로그인하면 서버는 JWT를 생성하여 클라이언트에게 전달합니다.
- 클라이언트는 이 토큰을 저장하고 (주로 로컬 스토리지나 쿠키), 매 요청마다 이를 헤더에 포함하여 서버에 전송합니다.
- 서버는 토큰의 서명을 확인하여 사용자를 인증합니다.
주요 차이점:
- 상태 저장 vs 무상태: 세션 기반 인증은 서버에 상태를 저장해야 하지만, JWT는 무상태(stateless)입니다.
- 확장성: JWT는 서버 측 세션 저장소가 필요 없어 더 쉽게 확장할 수 있습니다.
- 보안: 세션 ID는 의미 없는 문자열이지만, JWT는 자체적으로 정보를 포함하고 있어 주의가 필요합니다.
2. JWT의 구조
JWT는 세 부분으로 구성되어 있으며, 각 부분은 점(.)으로 구분됩니다. 구조는 다음과 같습니다:
xxxxx.yyyyy.zzzzz
여기서 각 부분은 다음을 나타냅니다:
- xxxxx: Header (헤더)
- yyyyy: Payload (페이로드)
- zzzzz: Signature (서명)
JWT의 기본 구성 요소
- Header (헤더):
헤더는 주로 두 가지 정보를 담고 있습니다:- 토큰의 유형 (typ): JWT
- 사용된 해싱 알고리즘 (alg): 예를 들어, HMAC SHA256 또는 RSA
{ "alg": "HS256", "typ": "JWT" }
- Payload (페이로드):
페이로드에는 클레임(claim)이라 불리는 엔티티에 대한 명세와 추가적인 데이터를 포함합니다. 클레임은 세 가지 유형이 있습니다:- 등록된 클레임: 미리 정의된 클레임. 예: iss (발행자), exp (만료 시간), sub (주제) 등
- 공개 클레임: 사용자 정의 클레임. JWT를 사용하는 당사자들 간에 합의된 정보
- 비공개 클레임: 당사자들 간에 정보를 공유하기 위해 생성된 맞춤 클레임
{ "sub": "1234567890", "name": "John Doe", "admin": true }
- Signature (서명):
서명은 헤더의 인코딩 값과 페이로드의 인코딩 값을 합친 후, 비밀 키로 해싱하여 생성합니다. 이는 토큰이 변조되지 않았음을 확인하는 데 사용됩니다.HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
Base64 인코딩의 이해
JWT의 각 부분은 Base64Url 인코딩을 사용하여 인코딩됩니다. Base64Url은 일반적인 Base64 인코딩과 유사하지만, URL에서 안전하게 사용할 수 있도록 '+' 대신 '-', '/' 대신 '_'를 사용하고, 패딩 문자 '='를 제거합니다.
Base64 인코딩의 주요 목적은 바이너리 데이터를 텍스트 형식으로 변환하는 것입니다. JWT에서는 이를 통해 JSON 객체를 문자열로 변환하고, URL에서 안전하게 전송할 수 있게 합니다.
주의할 점은 Base64 인코딩은 암호화가 아니라는 것입니다. 따라서 JWT의 헤더와 페이로드는 누구나 디코딩하여 읽을 수 있습니다. 이 때문에 JWT에 민감한 정보를 포함시키지 않는 것이 중요합니다.
JWT의 동작 방식 (서명과 검증)
JWT의 동작 방식은 다음과 같습니다:
- 토큰 생성:
- 서버는 헤더와 페이로드를 생성합니다.
- 각 부분을 Base64Url로 인코딩합니다.
- 인코딩된 헤더와 페이로드를 비밀 키를 사용하여 서명합니다.
- 최종적으로 헤더, 페이로드, 서명을 점(.)으로 구분하여 하나의 문자열로 만듭니다.
- 토큰 전송:
- 생성된 토큰을 클라이언트에게 전송합니다.
- 클라이언트는 이 토큰을 저장하고, 이후 요청 시 Authorization 헤더에 포함시켜 서버에 전송합니다.
- 토큰 검증:
- 서버는 받은 토큰을 점(.)을 기준으로 분리합니다.
- 헤더와 페이로드를 사용하여 서명을 재생성합니다.
- 재생성된 서명과 토큰의 서명 부분을 비교합니다.
- 서명이 일치하면 토큰이 변조되지 않았음을 확인할 수 있습니다.
- 추가로 토큰의 만료 시간 등을 확인하여 유효성을 검사합니다.
이러한 방식으로 JWT는 토큰의 무결성을 보장하고, 서버는 클라이언트의 신원을 확인할 수 있습니다.
3. JWT 생성 및 검증
JWT 생성 과정 (서명 방식: HMAC, RSA 등)
JWT를 생성할 때는 주로 두 가지 서명 방식을 사용합니다:
- HMAC (Hash-based Message Authentication Code):
- 대칭 키 암호화 방식입니다.
- 하나의 비밀 키를 사용하여 서명을 생성하고 검증합니다.
- 구현이 간단하고 빠르지만, 키 관리에 주의가 필요합니다.
- RSA (Rivest–Shamir–Adleman):
- 비대칭 키 암호화 방식입니다.
- 공개 키와 개인 키 쌍을 사용합니다.
- 개인 키로 서명을 생성하고, 공개 키로 검증합니다.
- 키 관리가 더 복잡하지만, 보안성이 높습니다.
JWT 생성 과정:
- 헤더와 페이로드 객체를 생성합니다.
- 각 객체를 JSON으로 직렬화합니다.
- JSON 문자열을 Base64Url로 인코딩합니다.
- 인코딩된 헤더와 페이로드를 점(.)으로 연결합니다.
- 선택한 알고리즘(HMAC 또는 RSA)과 비밀 키를 사용하여 서명을 생성합니다.
- 서명을 Base64Url로 인코딩합니다.
- 인코딩된 헤더, 페이로드, 서명을 점(.)으로 연결하여 최종 JWT를 생성합니다.
JWT 검증 과정 (무결성 확인 및 유효성 검증)
JWT를 받은 서버는 다음과 같은 과정을 통해 토큰을 검증합니다:
- JWT를 점(.)을 기준으로 헤더, 페이로드, 서명으로 분리합니다.
- 헤더를 디코딩하여 사용된 알고리즘을 확인합니다.
- 헤더와 페이로드를 사용하여 서명을 재생성합니다.
- 재생성된 서명과 받은 서명을 비교합니다.
- 서명이 일치하면 토큰의 무결성이 확인됩니다.
- 페이로드를 디코딩하여 클레임을 확인합니다.
- 만료 시간(exp), 발행 시간(iat) 등의 클레임을 검사하여 토큰의 유효성을 확인합니다.
- 필요에 따라 추가적인 클레임(예: 발행자, 대상자 등)을 검증합니다.
JWT의 유효 기간 설정과 관리
JWT의 유효 기간 설정은 보안과 사용자 경험 사이의 균형을 맞추는 중요한 요소입니다.
- 유효 기간 설정:
- 'exp' (Expiration Time) 클레임을 사용하여 토큰의 만료 시간을 지정합니다.
- 일반적으로 짧은 유효 기간(예: 15분~1시간)을 설정하여 보안을 강화합니다.
- 리프레시 토큰:
- 짧은 유효 기간의 단점을 보완하기 위해 리프레시 토큰을 사용할 수 있습니다.
- 리프레시 토큰은 더 긴 유효 기간을 가지며, 새로운 액세스 토큰을 발급받는 데 사용됩니다.
- 토큰 갱신:
- 클라이언트는 액세스 토큰이 만료되기 전에 리프레시 토큰을 사용하여 새 액세스 토큰을 요청합니다.
- 서버는 리프레시 토큰의 유효성을 확인하고 새로운 액세스 토큰을 발급합니다.
- 토큰 폐기:
- JWT는 기본적으로 무상태이므로 서버 측에서 직접 폐기할 수 없습니다.
- 필요한 경우, 블랙리스트를 유지하여 특정 토큰을 무효화할 수 있습니다.
- 또는 모든 토큰에 고유 식별자를 포함시키고, 서버에서 유효한 식별자 목록을 관리할 수 있습니다.
예시 코드 (Node.js/Express를 사용한 JWT 생성 및 검증)
Node.js와 Express 프레임워크를 사용하여 JWT를 생성하고 검증하는 간단한 예제를 살펴보겠습니다. 이 예제에서는 jsonwebtoken
라이브러리를 사용합니다.
먼저, 필요한 패키지를 설치합니다:
npm install express jsonwebtoken
그리고 다음과 같이 코드를 작성합니다:
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
const secretKey = "your-secret-key";
// JWT 생성
app.post("/login", (req, res) => {
// 여기서는 사용자 인증 로직을 생략하고 바로 토큰을 생성합니다
const user = { id: 1, username: "example" };
const token = jwt.sign(user, secretKey, { expiresIn: "1h" });
res.json({ token });
});
// JWT 검증 미들웨어
function authenticateToken(req, res, next) {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (token == null) return res.sendStatus(401);
jwt.verify(token, secretKey, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
// 보호된 라우트
app.get("/protected", authenticateToken, (req, res) => {
res.json({ message: "보호된 데이터에 접근했습니다", user: req.user });
});
app.listen(3000, () => {
console.log("서버가 3000번 포트에서 실행 중입니다");
});
이 예제에서:
/login
라우트에서 JWT를 생성합니다. 실제 애플리케이션에서는 사용자 인증 후에 토큰을 생성해야 합니다.authenticateToken
미들웨어는 요청 헤더에서 토큰을 추출하고 검증합니다./protected
라우트는 인증된 사용자만 접근할 수 있습니다.
이 코드를 실행하면, 클라이언트는 다음과 같이 API를 사용할 수 있습니다:
/login
에 POST 요청을 보내 토큰을 받습니다.- 받은 토큰을 Authorization 헤더에 "Bearer [token]" 형식으로 포함시켜
/protected
에 GET 요청을 보냅니다.
이러한 방식으로 JWT를 사용하면 서버는 세션을 유지하지 않고도 클라이언트의 인증 상태를 확인할 수 있습니다.
결론
이번 글에서는 JWT의 기본 개념, 구조, 그리고 생성 및 검증 과정에 대해 살펴보았습니다. JWT는 현대 웹 개발에서 널리 사용되는 인증 방식으로, 특히 분산 시스템이나 마이크로서비스 아키텍처에서 유용합니다.
JWT의 주요 장점은 다음과 같습니다:
- 서버의 상태를 유지할 필요가 없어 확장성이 좋습니다.
- 다양한 프로그래밍 언어에서 지원되어 호환성이 높습니다.
- 토큰 자체에 정보를 포함할 수 있어 유연합니다.
그러나 JWT를 사용할 때는 다음 사항에 주의해야 합니다:
- 토큰의 페이로드는 암호화되지 않으므로 민감한 정보를 포함하지 않아야 합니다.
- 적절한 유효 기간 설정과 토큰 관리가 중요합니다.
- 클라이언트 측 저장소(예: 로컬 스토리지)에 토큰을 저장할 때는 보안에 주의해야 합니다.
JWT를 올바르게 이해하고 사용한다면, 안전하고 효율적인 인증 시스템을 구축할 수 있습니다. 다음 편에서는 JWT의 고급 주제와 실제 애플리케이션에서의 활용 방안에 대해 더 자세히 다루도록 하겠습니다.
'Computer Science' 카테고리의 다른 글
IoC의 란(feat. typescript) (0) | 2024.10.15 |
---|---|
JWT(JSON Web Token)의 이해와 활용: 3편 (2) | 2024.10.14 |
JWT(JSON Web Token)의 이해와 활용: 2편 (0) | 2024.10.14 |
HTTP OPTIONS 메서드란 무엇인가? (0) | 2024.09.30 |
SQL과 NoSQL 데이터베이스의 차이점: 백엔드 개발자의 선택 기준 (0) | 2024.09.09 |