JWT토큰이란?
선택적 서명 및 선택적 암호화를 사용하여 데이터를 만들기 위한 인터넷 표준이다. Json 형태로 웹에서 데이터를 안전하게 전달할때 사용한다.
유저를 인증, 인가하기 위한 토큰 기반이다.
RESTful과 같은 Stateless 환경에서 사용자 데이터를 주고받을 수 있다.
인증 Authentication, 인가 Authorization에서 사용한다.
구조
dot `.`을 기준으로 세 부분으로 나뉘게 된다. BASE64 Url 인코딩 방식을 사용한다.
헤더 Header
서명 시 사용하는 키(kid), 사용할 타입(typ), 서명 암호화 알고리즘(alg)의 정보를 가지고 있다.
{
"alg": "HS256",
"typ": "JWT"
}
alg는 서명 알고리즘 (HMAC SHA256)이며, typ는 토큰의 타입 (JWT)를 나타낸다.
페이로드 Payload
토큰에서 사용할 정보의 조각인 클레임 `claims`를 포함한다.
클레임 : JWT 내에서 사용되는 정보 단위이다. 표준 / 커스텀으로 나뉜다.
페이로드는 사용자의 정보를 담고 있으며, password 는 안된다.
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
sub는 사용자 ID, name은 사용자 이름, iat 는 토큰 발급 시간 issue at 이다.
서명 Signature
JWT의 무결성을 보장한다. 헤더와 페이로드를 인코딩한 후, private_key 비밀키와 함께 지정된 알고리즘을 생성한다.
페이로드 값이 조금이라도 바뀌면, 서명값이 통째로 바뀌기 때문에 안전하다.
작동 방식
- 클라이언트가 서버에 로그인 요청을 보낸다.
- 서버는 사용자를 인증하고 JWT 토큰을 생성하여 클라이언트에게 반환한다.
- 클라이언트는 서버로부터 받은 JWT 토큰을 저장한다(주로 로컬 스토리지 또는 쿠키에 저장).
- 클라이언트가 서버에 요청을 보낼 때마다 JWT 토큰을 포함시킨다.
- 서버는 받은 JWT 토큰을 검증하여 요청을 처리한다.
쿠키, 세션과 비교
쿠키 Cookie : (포춘) 쿠키에 필요한 내용을 직접 넣어서 서버 - 클라이언트가 소통한다. 서버가 저장 X (Stateless = 서버 상태저장하지 않는다.= RESTful) > 보안에 취약하다
세션 Session : 중요한 정보는 서버의 금고 (Session)에 저장해두고, 그 정보가 어디있는지 주소(Session ID)만 적어서 쿠키에 담는다. 보안이 비교적 좋다. > 서버가 저장 O Stateless 하지 않다.
차이점?
JWT는 클라이언트 측에 저장된다. 자체적으로 서명되어 무결성을 보장하고, Stateless 방식이다.
장단점
장점
- 독립성: 서버가 상태를 유지하지 않으므로 확장성이 뛰어나다.
- 확장성: 분산 시스템에서 효율적으로 사용될 수 있다.
- 간편한 사용: 클라이언트와 서버 간에 쉽게 통합될 수 있다.
단점
- 토큰 크기: JWT는 클레임 정보를 포함하므로 토큰 크기가 커질 수 있다.
- 보안 취약점: 잘못된 서명 알고리즘 선택이나 비밀 키 관리 부실로 인해 보안 취약점이 발생할 수 있다
최근에도 많이 사용하지 않는 추세라고 한다.
보안 고려 사항
토큰 유출 방지
HTTPS를 사용하여 전송하고, 로컬 스토리지 대신 HttpOnly 쿠키를 사용하는 것이 좋다
토큰 만료 및 갱신
만료 시간을 설정하여 유효 기간을 관리한다. 토큰이 만료되면 클라이언트는 갱신 토큰을 사용하여 새로운 JWT를 요청한다.
access Token, refresh Toke 관련 내용
실습 예제
`npm i jsonwebtoken`을 사용한다.
토큰 생성
var jwt = require("jsonwebtoken");
jwt 모듈 호출
var token = jwt.sign({ foo: "bar" },process.env.PRIVATE_KEY);
foo : payload + 환경변수로 암호키 만들어서, 토큰 생성한다.
토큰 검증
var decoded = jwt.verify(token, process.env.PRIVATE_KEY);
성공하면 페이로드 값 확인할 수 있다.
토큰 사용
jwt.verify(token, 'your_secret_key', (err, decoded) => {
if (err) {
return res.status(401).send('Unauthorized');
}
if (decoded.role !== 'admin') {
return res.status(403).send('Forbidden');
}
req.user = decoded;
next();
});
verify 메소드로 토큰의 유효성을 검증하고, 페이로드의 역할이나 권한을 확인한다.