토큰 기반의 인증 시스템은 인증받은 사용자들에게 토큰을 발급하고, 서버에 요청을 할 때 헤더에 토큰을 함께 보내도록 하여 유효성 검사를 한다. 이러한 시스템에서는 더이상 사용자의 인증 정보를 서버나 세션에 유지하지 않고 클라이언트 측에서 들어오는 요청만으로 작업을 처리한다. 즉, 서버 기반의 인증 시스템과 달리 상태를 유지하지 않으므로 Stateless한 구조를 갖는다. 이러한 토큰 기반의 인증 방식을 통해 수많은 문제점들을 해결할 수 있는데, 대표적으로 사용자가 로그인이 되어있는지 안되어있는지 신경쓰지 않고 손쉽게 시스템을 확장할 수 있다.
"아 우리가 발급해 준 토큰이 맞네!"
라는 판단이 될 경우, 클라이언트의 요청을 처리한 후 응답을 보내준다.Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다. JWT는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달한다. 주로 회원 인증이나 정보 전달에 사용된다.
Access Token
Refresh Token
JWT는 Header, Payload, Signature의 3 부분으로 이루어지며, Json 형태인 각 부분은 Base64로 인코딩 되어 표현된다. 또한 각각의 부분을 이어 주기 위해 . 구분자를 사용하여 구분한다.
Header
토큰의 헤더는 typ과 alg 두 가지 정보로 구성된다. alg는 헤더(Header)를 암호화 하는 것이 아니고, Signature를 해싱하기 위한 알고리즘을 지정하는 것이다.
{
"alg": "HS256",
"typ": "JWT"
}
Payload
Payload에는 정보가 담겨 있다. 어떤 정보에 접근 가능한지에 대한 권한을 담을 수도 있고, 사용자의 유저 이름 등 필요한 데이터는 이곳에 담아 Sign 시킨다. Payload 에는 민감한 정보는 되도록 담지 않는 것이 좋다.
{
"sub": "someInformation",
"name": "phillip",
"iat": 151623391
}
Signature
서명(Signature)은 토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다. 서명(Signature)은 위에서 만든 헤더(Header)와 페이로드(Payload)의 값을 각각 BASE64로 인코딩하고, 인코딩한 값을 비밀 키를 이용해 헤더(Header)에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 BASE64로 인코딩하여 생성한다.
예를 들어, 만약 HMAC SHA256 알고리즘(암호화 방법 중 하나)을 사용한다면 signature는 아래와 같은 방식으로 생성된다.
HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);
생성된 토큰은 HTTP 통신을 할 때 Authorization이라는 key의 value로 사용된다. 일반적으로 value에는 Bearer이 앞에 붙여진다.
{ "Authorization": "Bearer {생성된 토큰 값}", }
const jwt = require('jsonwebtoken');
const token = jwt.sign(토큰에_담을_값, ACCESS_SECRET, { 옵션1: 값, 옵션2: 값, ... });
generateAccessToken: (data) => {
return jwt.sign(data, process.env.ACCESS_SECRET, { expiresIn: "15s" });
},
generateRefreshToken: (data) => {
return jwt.sign(data, process.env.REFRESH_SECRET, { expiresIn: "30d" });
},
const jwt = require('jsonwebtoken');
const authorization = req.headers['authorization'];
// console.log(authorization)
// Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcklkIjoia2ltY29kaW5nIiwiZW1haWwiOiJraW1jb2RpbmdAY29kZXN0YXRlcy5jb20iLCJjcmVhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJ1cGRhdGVkQXQiOiIyMDIwLTExLTE4VDEwOjAwOjAwLjAwMFoiLCJpYXQiOjE2NDA3MDc3NTJ9.eYAkxDeIdHUDavKB7cF-xpMLXWzCQRGErN2nZpwE6Pc
const token = authorization.split(' ')[1];
// split사용시 ['Bearer', '암호화된token'] 순서로 배열에 담기게 되니까 Bearer빼고 암호화된token만 갖져다 쓰기 위함
const data = jwt.verify(token, ACCESS_SECRET); // 복호화