JWT(JSON Web Token)는 인증과 관련된 정보를 JSON 형식으로 저장한 암호화된 웹 토큰입니다.
JWT 구조
토큰은 순서대로 header, payload, signature로 구성되어 있습니다. 세 가지 요소는 온점 (”.”)으로 구분되며, 전체적인 형태는 header.payload.signature과 같은 구조를 띄게 됩니다.
- header : 보통 2가지 정보, 토큰 유형(JWT)과 사용된 서명 알고리즘으로 구성되어 있습니다.
{
"alg": "HS256",
"typ": "JWT"
}
- payload : 토큰에 저장할 정보들은 이 부분에 들어갑니다.
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
- signature : header와 payload를 서명한 값을 가지고 있습니다. 서명할 때는 header에 적혀있는 서명 알고리즘과 비밀 키를 사용하기 때문에 무결성을 확인할 수 있습니다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
JWT 장단점
장점
- 세션을 사용한다면 세션을 위한 별도의 저장소가 필요하지만 JWT는 필요한 정보를 모두 담고 있기 때문에 서버에 저장 공간을 차지하지 않습니다.
- 확장성 : 토큰 기반으로 하는 다른 인증 시스템들과 연동이 가능합니다. OAuth(ex: 구글, 페이스북)을 사용하는 시스템들과도 호환이 됩니다.
단점
- 정보를 토큰에 저장하기 때문에 담아야 할 정보가 많아질 수록 통신에 부담이 갈 수 있습니다.
- Payload는 누구나 디코딩할 수 있기 때문에 중요/민감한 정보는 저장하면 안됩니다.
- 토큰은 유효기간이 끝날 때까지 누구나 사용할 수 있기 때문에 관리가 잘 안된다면 취약점이 될 수 있습니다.
JWT 생성
다음 코드는 '/signin'에서 로그인을 진행합니다. 26번째 줄에 적혀 있는 부분이 앞서 언급한 signature에 사용되는 비밀 키입니다.
// src/controller/user/controller.ts
const express = require('express');
const passport = require('passport');
const jwt = require('jsonwebtoken');
const router = express.Router();
router.post('/signin',async (req, res, next) => {
try {
// 아까 local로 등록한 인증과정 실행
passport.authenticate('local', (passportError, user, info) => {
// 인증이 실패했거나 유저 데이터가 없다면 에러 발생
if (passportError || !user) {
res.status(400).json({ message: info.reason });
return;
}
// user 데이터를 통해 로그인 진행
req.login(user, { session: false }, (loginError) => {
if (loginError) {
res.send(loginError);
return;
}
// 클라이언트에게 JWT생성 후 반환
const token = jwt.sign(
{ id: user.id, name: user.name, auth: user.auth },
'jwt-secret-key'
);
res.json({ token });
});
})(req, res);
}catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;
JWT 검증
다음 코드는 클라이언트가 가지고 있는 JWT를 보고 authentication 과정을 진행해서 로그인을 유지할 수 있게 합니다.
// src/passport/index.js
const passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const { ExtractJwt, Strategy: JWTStrategy } = require('passport-jwt');
const bcrypt = require('bcrypt');
const User = require('../models/user');
// 중략... (id와 password 확인하는 부분)
const JWTConfig = {
jwtFromRequest: ExtractJwt.fromHeader('authorization'),
secretOrKey: 'jwt-secret-key',
};
const JWTVerify =async (jwtPayload, done) => {
try {
// payload의 id값으로 유저의 데이터 조회
const user =await User.findOne({ where: { id: jwtPayload.id } });
// 유저 데이터가 있다면 유저 데이터 객체 전송
if (user) {
done(null, user);
return;
}
// 유저 데이터가 없을 경우 에러 표시
done(null, false, { reason: '올바르지 않은 인증정보 입니다.' });
} catch (error) {
console.error(error);
done(error);
}
};
module.exports = () => {
passport.use('local',new LocalStrategy(passportConfig, passportVerify));
passport.use('jwt',new JWTStrategy(JWTConfig, JWTVerify));
};
참고 문서:
728x90
반응형
'Backend' 카테고리의 다른 글
[Passport.js] Local Strategy에서 username 대신 userId 사용하기 (0) | 2024.07.15 |
---|---|
[Swagger] Default 값, 예시 값 설정하기 (0) | 2024.07.10 |
HTTP 응답 상태 코드(Status Code) (1) | 2024.01.04 |
API(Application Programming Interface) (0) | 2022.11.12 |