Json Web Token 의 줄임말로, 서버와 클라이언트의 사이에서 정보를 안전하게 전송하기 위해 도와주는 표준 '웹 토큰' 이다.
Json 파일의 형태의 데이터를 안전하게 전송하고, 검증하는 기능을 제공하며, 이를 통해 다양한 암호화 알고리즘을 가능하게 해 신뢰성 또한 보장된다.
파일의 형태는 'header.payload.signature' 로 3가지의 데이터를 포함하고 있다.
그래서 실제로는 어떻게 생겼을까?
이는 우리가 가지고 있다고 가정하는 데아터이다. 파일의 형식별로 구분되어 header, payload, verify signature 로 총 3가지의 데이터가 있고 모두 초기 형태를 유지하고 있다.
JWT는 이 각각의 정보를 암호화 하여 ' . ' 점으로 연결한다.
인코딩 된 왼쪽의 데이터가 JWT화 된 데이터인데, 사람이 알아볼 수 없는 형식을 띄고 있다.
이러한 절차를 통해서 데이터의 안전성을 보장하는 것이다.
세가지 형식을 하나씩 살펴보면
1. Header : 토큰의 타입, 어떤 암호화를 사용한 데이터인지 정의한다.
2. Payload : 실제 전달하려는 데이터를 포함한다.
3. Signature : header 와 payload 에 'Secret Key' 를 이용하여 생성해, 이 JWT 토큰이 정상적인지 확인하는 기능을 수행한다.
이렇게 만들어진 JWT는 쿠키나 경로 매개변수 params 를 통해 전달된다.
JWT의 특성을 조금만 더 알아보자면,
이 JWT를 가진 사람이라면 고유 암호화 키인 secret key 에 대한 정보가 없더라도, 해당 토큰에 어떤 데이터가 담겨있는지 확인할 수 있다는 것이다. '변조'만 불가능 할 뿐, 데이터를 확인하는 것은 누구나 가능하다는 것이다.
해서 개인정보 등의 민감한 데이터는 이 JWT에 담지 않도록 해야 한다.
현재 학습을 JavaScript 로 하고 있다고 해서, 이 언어에서만 가능한 방식이 아니라 모든 언어에서 가능한 단순 '데이터 형식' 이다. 개념으로서 존재하는 형태이기 때문에 이를 코드로 구현해 사용하는 것이 일반적인 과정이다.
먼저 쿠키/세션에 대해서 간단히 설명하자면, '권한이 필요한 API 에서 사용하는 이전 방문 정보를 기억하는 데이터 파일' 이라고 할 수 있다.
특히 쿠키의 경우, 웹사이트를 돌아다니다 보면 '쿠키 사용 승인' 과 같은 메세지가 나타나며 쿠키를 지급하는 것을 허용하겠냐 묻는 경우가 있다. 이는 해당 웹사이트의 서버에서 우리에게 쿠키를 발급해 이후의 데이터 통신을 더 편하게 만들어도 되겠냐- 라고 묻는 것리이라고 보면 되겠다.
해서 이 쿠키/세션과 JWT는 차이점이 있는데,
먼저 JWT는 단순 데이터를 '표현하는 형식' 이라는 것과, 쿠키/세션은 데이터를 '교환/관리하는 방식' 이라는 점에서 그 목적이 다르다고 볼 수 있다.
또한 JWT 데이터는 변조가 어렵다는 점과 서버에 별도의 상태정보를 저장하지 않는다는 점으로 서버를 Stateless(무상태) 로 관리 가능하다는 점이 있다.
반대로 쿠키/세션은 로그인이나 세션 데이터를 서버에 저장하는 Stateful(상태 보존) 하게 데이터를 관리한다.
이 두 동작은 State를 '서버' 라고 바꿔보면 이해가 쉬울 수 있다. 예를 들어 Stateless -> Serverless : 즉 '서버가 없는' 이란 말이다.
서버가 다운된 후 다시 가동된 후에도 다운되기 전과 이후에 같은 동작을 수행한다면 stateless, 서로 다른 동작을 수행한다면 statefull 이라고 할 수 있다.
즉 서버에 저장된 데이터에 따라 다른 로직을 수행하느냐의 차이인 것이다.
예를 들어 로그인 과정의 경우, 서버에 저장된 회원가입한 사용자의 정보가 없다면 로그인 시도를 해도 이를 받아들이지 못할 것이다. 이런 경우 서버에 저장된 데이터에 따라 다른 동작을 수행하므로 statefull 상태라고 볼 수 있다.
다시 JWT에 관한 이야기로 돌아와서, 이 JWT를 사용하기 위해선 오픈소스 라이브러리 'jsonwebtoken' 을 사용해야 한다.
# yarn을 이용해 프로젝트를 초기화합니다.
yarn init -y
# jsonwebtoken, express 라이브러리를 설치합니다.
yarn add jsonwebtoken express
라이브러리를 설치하고 나면
다음과 같이 jsonwebtoken 이 추가된 것이 보인다.
이제부터 jsonwebtoken 라이브러리에 내장된 메서드들을 사용할 수 있다.
JWT의 가장 원초적인 기능, 데이터 암호화이다.
라이브러리의 sign 메서드를 사용해 JWT을 생성할 것이다.
import jwt from 'jsonwebtoken';
const token = jwt.sign({ myPayloadData: 1234 }, 'mysecretkey');
console.log(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2OTA4NzM4ODV9.YUmYY9aef9HOO8f2d6Umh2gtWRXJjDkzjm5FPhsQEA0
jwt.sign 을 실행하고, 그 안에 저장할 데이터 myPayloadData : 1234 와 고유 식별키 mysecretkey 를 같이 입력해주었다.
그 결과
다음처럼 token 이라는 변수에 JWT에 할당된 것을 확인할 수 있다.
그럼 암호화를 했다면, 이걸 다시 풀어보는 복호화를 실행해보자.
메서드로는 'decode' 라는 메서드를 사용한다.
import jwt from 'jsonwebtoken';
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2OTA4NzM4ODV9.YUmYY9aef9HOO8f2d6Umh2gtWRXJjDkzjm5FPhsQEA0";
const decodedValue = jwt.decode(token);
console.log(decodedValue); // { myPayloadData: 1234, iat: 1690873885 }
token 변수에는 위에서 만들었던 JWT값을 넣어주었고, jwt.decode 를 통해 해당 token을 복호화하는 것이다.
코드를 실행하면
암호화 과정에서 입력했던 muPayloadData가 나타나는 것을 확인할 수 있다.
그럼 이제 JWT 데이터가 조작되지 않았는지 검증의 절차를 진행해야 한다.
import jwt from 'jsonwebtoken';
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2OTA4NzM4ODV9.YUmYY9aef9HOO8f2d6Umh2gtWRXJjDkzjm5FPhsQEA0";
const decodedValueByVerify = jwt.verify(token, "mysecretkey");
console.log(decodedValueByVerify); // { myPayloadData: 1234, iat: 1690873885 }
사용 메서드는 'verify' 를 사용하였다.
만약 mysecretkey 가 token 에 할당된 JWT 데이터의 암호화 키와 동일하다면, 정상적으로 검증과 복호화가 이루어질 것이다.
실행하면 다음과 같이 정상적으로 원본 데이터가 출력되는 것을 알 수 있는데, 그렇다면 만약 secretkey 가 다르다면 어떻게 될까?
에러가 발생하면서 JsonWebTokenError 라는 내용이 보인다. invalid signature 라고 하며 잘못된 서명, 즉 암호화 키가 일치하지 않다고 알려주고 있다.
JWT는 위에서 설명해온 것처럼 데이터의 암호화, 인증 등의 과정이 가능하다. 특징으로는
1. 인증 서버에서 발급된 데이터가 맞는지 위변조 여부 확인 가능
2. 누구든지 JWT 내부 데이터 확인 가능
이라는 두가지 특징이 있는데, 사용 이유에 대해서 예시를 보면서 알아보자.
JWT를 적용하지 않은 로그인 API를 구현한다고 해보자.
import express from 'express';
const app = express();
app.post('/login', function (req, res, next) {
const user = { // 사용자 정보
userId: 203, // 사용자의 고유 아이디 (Primary key)
email: "archepro84@gmail.com", // 사용자의 이메일
name: "이용우", // 사용자의 이름
}
res.cookie('sparta', user); // sparta 라는 이름을 가진 쿠키에 user 객체를 할당합니다.
return res.status(200).end();
});
app.listen(5002, () => {
console.log(5002, "번호로 서버가 켜졌어요!");
});
API 내부에서 return res.status(200).end() 로 반환하여 어떠한 데이터를 전달하는 것이 아니라 res.cookie()로 쿠키만 반환한 결과이다.
이러한 경우 사용자의 정보가 쿠키에 할당받게 되고, 이는 쿠키의 속성/정보가 클라이언트를 통해 수정될 수 있다는 점에서 이 데이터가 위변조 되었는지 확인이 불가능하다는 큰 단점이 있다.
만약 이 API를 JWT를 사용하는 방식으로 바꾼다면
import express from 'express';
import JWT from 'jsonwebtoken';
const app = express();
app.post('/login', (req, res) => {
// 사용자 정보
const user = {
userId: 203,
email: 'archepro84@gmail.com',
name: '이용우',
};
// 사용자 정보를 JWT로 생성
const userJWT = JWT.sign(
user, // user 변수의 데이터를 payload에 할당
'secretOrPrivateKey', // JWT의 비밀키를 secretOrPrivateKey라는 문자열로 할당
{ expiresIn: '1h' }, // JWT의 인증 만료시간을 1시간으로 설정
);
// userJWT 변수를 sparta 라는 이름을 가진 쿠키에 Bearer 토큰 형식으로 할당
res.cookie('sparta', `Bearer ${userJWT}`);
return res.status(200).end();
});
app.listen(5002, () => {
console.log(5002, '번호로 서버가 켜졌어요!');
});
다음과 같이 작성해볼 수 있을 것이다.
과정으로는 먼저 사용자의 정보는 const user = { ~ } 를 통해 받았다고 가정하고 진행한다. 이 데이터를 JWT.sign 을 통해 JWT파일로 생성하고, Bearer 토큰 형식으로 전달한다. 이 전달하는 데이터는 sparta 라는 쿠키에 담는다.
secretkey 는 'secretOrPrivateKey' 라고 작성하였는데, 이를 통해 위변조 여부를 확인할 수 있다.
또한 코드에서는 작성하지 않았지만 쿠키의 지속시간과는 별개로 JWT만의 만료 시간을 설정할 수 있어 무분별한 요청을 방지할 수 있다.
JWT를 사용하지 않고 구현한 API보다 더 안전하고 효율적인 데이터 관리가 가능한 것이다.
글을 마무리하면서 JWT의 활용 방법 예시를 같이 첨부한다.
#7. Prisma Method, 게시글 API 실습 (0) | 2024.12.18 |
---|---|
#6. ORM - Prisma (0) | 2024.12.17 |
#5. Raw Query (1) | 2024.12.13 |
#4. SQL 제약 조건 (0) | 2024.12.12 |
#3. SQL 기초 (0) | 2024.12.11 |