오늘은 JWT 를 한번 재미있게 해볼생각입니다.
오늘 배워보고 회사에 적용하면 얼마나 편하고 좋은지 알아볼거에요
그전에 알아야 적용하고 자랑도 할거죠??
"한 친구가 나에게 물었습니다, 'JWT가 뭐야?' 그래서 나는 말했습니다, '그건 정보를 담은 디지털 상자야. 그런데 이 상자는 아무나 열 수 없게 잠금장치가 달려있지.'"
일단 잠금 디지털 장치라고 생각해봐요
디저털 장치에 뭐가 들어가 있을까요??
디지털 장치(JWT)에는 단 3가지만 있습니다.
JWT의 구성 요소
헤더는 토큰의 유형(JWT)과 사용된 서명 알고리즘(예: HMAC SHA256 또는 RSA)을 포함합니다.
{
"alg": "HS256",
"typ": "JWT"
}
이 JSON 객체는 Base64Url 인코딩되어 JWT의 첫 번째 부분을 형성합니다.
Base64Url 이 뭔지 모를수도 있으니
Base64는 데이터를 텍스트 형식으로 인코딩하는 방법이에요. 보통 3바이트의 바이너리 데이터를 4개의 ASCII 문자로 변환하죠. 이 과정에서 알파벳 대문자, 소문자, 숫자 그리고 몇 가지 특수 문자를 사용해요.
그런데, URL에서는 몇 가지 문자를 사용할 수 없거나, 사용하면 안 되는 경우가 있어요. 예를 들어, '+'나 '/' 같은 문자는 URL에서 특별한 의미를 가지니까요. 그래서 등장한 것이 바로 Base64Url입니다.
Base64Url은 Base64와 거의 같지만, '+'를 '-'로, '/'를 '_'로 대체해요. 이렇게 하면 URL에서 안전하게 사용할 수 있게 되죠. 그리고 마지막에 '=' 기호로 패딩하는 것도 제거해서 URL을 더 깔끔하게 만들어요.
예를 들어 볼게요. 만약 우리가 "Hello"라는 문자열을 Base64로 인코딩하면 "SGVsbG8="가 되죠. 이걸 Base64Url로 인코딩하면 "SGVsbG8"가 됩니다.
페이로드에는 클레임(claim)이 포함됩니다. 클레임은 엔터티(일반적으로 사용자)에 대한 정보입니다.
클레임에 대해서 또 알아보죠
클레임(claims)이라고 불리는 일련의 정보가 들어 있습니다. 클레임은 주로 사용자 정보나 토큰의 메타데이터를 포함해요. 클레임은 크게 세 종류로 나뉩니다: 등록된 클레임(Registered Claims), 공개 클레임(Public Claims), 그리고 비공개 클레임(Private Claims).
등록된 클레임 (Registered Claims):
이 클레임들은 JWT 표준에 정의되어 있으며, 특정한 의미를 가지고 있어요. 예를 들어:
iss: 토큰을 발급한 주체 (issuer)
sub: 토큰의 주체 (subject)
aud: 토큰의 대상 (audience)
exp: 토큰의 만료 시간 (expiration time)
iat: 토큰이 발급된 시간 (issued at)
공개 클레임 (Public Claims):
이 클레임들은 누구나 정의할 수 있는 클레임이에요. 그러나 이름 충돌을 방지하기 위해, 충돌 방지 네임스페이스를 사용하는 것이 좋습니다.
비공개 클레임 (Private Claims):
이 클레임들은 발급자와 수신자 간의 협의에 따라 정의되는 클레임입니다. 예를 들어, 특정 사용자의 권한이나 ID와 같은 정보를 포함할 수 있어요.
이 JSON 객체도 Base64Url 인코딩되어 JWT의 두 번째 부분을 형성합니다.
서명은 인코딩된 헤더, 인코딩된 페이로드, 비밀 키, 그리고 헤더에 명시된 알고리즘을 사용하여 생성됩니다.
이렇게 해서 위에 사진처럼 디지털 상자가 만들어 지는거에요 디지털상자는 JWT 에요
이게 끝이에요 개념은 하지만 예제를 한번 봐야겠지요??
1.클라이언트가 보호된 자원에 접근하려고 할 때, JWT를 HTTP 요청의 Authorization 헤더에 포함시킵니다.
2.서버는 JWT를 디코딩하여 헤더와 페이로드를 추출합니다.
3.서명 검증을 통해 토큰이 변경되지 않았음을 확인합니다.
4.토큰의 만료 시간을 확인하고, 필요한 추가 검증을 수행합니다.
저의 주언어인 Node.js 로 알려드릴게요
먼저, JWT를 생성하는 방법입니다. jsonwebtoken 라이브러리를 사용합니다.
설치는 설명안할게요 ~~ㅎㅎ
const jwt = require('jsonwebtoken');
// 비밀 키 설정
const secretKey = 'your-256-bit-secret';
// 페이로드 설정
const payload = {
sub: '1234567890',
name: 'John Doe',
admin: true
};
// JWT 생성
const token = jwt.sign(payload, secretKey, { algorithm: 'HS256', expiresIn: '1h' });
console.log('JWT:', token);
생성된 JWT를 검증하는 방법입니다.
const jwt = require('jsonwebtoken');
// 비밀 키 설정
const secretKey = 'your-256-bit-secret';
// 클라이언트로부터 받은 JWT
const token = '받은 토큰을 여기에 넣으세요';
try {
// JWT 검증
const decoded = jwt.verify(token, secretKey);
console.log('Decoded Payload:', decoded);
} catch (err) {
console.error('Invalid Token:', err.message);
}
간단하게 SSO 를 구현한다고 생각하면
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
const secretKey = 'your-256-bit-secret';
// 미들웨어를 사용하여 JWT 검증
function authenticateToken(req, res, next) {
const token = req.headers['authorization'];
if (!token) 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: 'This is a protected route', user: req.user });
});
app.listen(3001, () => {
console.log('Service running on port 3001');
});
이렇게 구현해서 로그인 유지도 옛날 방식인 세션이 아닌 JWT 를 사용해보세요
그러면 데이터베이스 에서 유저 조회도 빠지게 되고 성능도 올라가는거 아니겠어요??
아그리고 your-256-bit-secret 이부분이 secretKey는 JWT를 생성하고 검증할 때 사용되는 중요한 요소입니다. secretKey는 보안성이 매우 중요하기 때문에 신중하게 생성하고 관리해야 합니다.
만드는 방법은
const crypto = require('crypto');
const secretKey = crypto.randomBytes(32).toString('hex');
console.log('Generated Secret Key:', secretKey);
간단하죠?? 이렇게 만들어서 넣어줘도 되고 이러면 라우터에 하트코딩보다는 .env 에 환경변수로 처리 하면 깔끔하겠지요??
지금 예시는 SSO 지만 다른것도 구현할수 있어요
2024 트렌드는 제가 생각했을때는
1.SSO
2.디바이스 간에 인증 방식
3.서버리스로 인증방식
4.마이크로 서비스간에 인증방식
이렇게 있는거 같아요. 재미있었으면 좋아요한방 주세요