JWT는 JSON 형식의 데이터를 저장하는 토큰이다. 헤더,페이로드, 시그니처
세 부분으로 구성되어 있다.
필요한 npm 패키지는 express-generator, dotenv, jsontokenweb 이다.
npm i express-generator dotenv jsontokenweb
명령어로 실행해 필요한 npm 패키지를 설치한다.
JWT 토큰 발급 시 POST 요청을, 발급된 토큰을 테스트할때 GET 요청을 할것이다. 이런 요청을 보내고 테스트 하기 위해 필요한 프로그램이 POSTMAN이다.
JWT 인증에 사용할 비밀키를 환경변수에 등록해준다. 비밀키는 소스코드에 입력해도 되나, 외부에 쉽게 노출되지 않도록 환경변수에 등록해 관리하는 것이 좋다.
Express 서버 프로젝트의 루트 디렉토리에 .env
파일을 생성한 후 JWT 인증에 사용할 비밀키를 입력해준다.
JWT_SECRET=JwTsEcReTkEyOrHaShInG
JWT 토큰이 유효한지 검사하는 메서드를 만든다.
const jwt = require('jsonwebtoken');
exports.verifyToken = (req, res, next) => {
// 인증 완료
try {
req.decoded = jwt.verify(req.headers.authorization, process.env.JWT_SECRET)
return next();
}
// 인증 실패
catch(error) {
if (error.name === 'TokenExpireError') {
return res.status(419).json({
code: 419,
message: '토큰이 만료되었습니다.'
});
}
return res.status(401).json({
code: 401,
message: '유효하지 않은 토큰입니다.'
});
}
}
jsonwebtoken의 sign()메서드로 JWT 토큰을 발급한다. 이때 토큰에 들어갈 내용(페이로드)과 비밀키 그리고 옵션을 넣어준다.
localhost:3000/token
주소로 POST 요청 시 토큰을 발급하는 라우터를 만들었다. localhost:3000/token/test
주소로 GET요청시 발급된 토큰을 테스트하는 라우터를 만들었다. /*** routes/token.js ***/
const express = require('express');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const { verifyToken } = require('./middlewares');
const router = express.Router();
router.post('/', async (req, res) => {
try {
const id = 'vappet'
const nick = 'hodoopapa'
// jwt.sign() 메소드: 토큰 발급
const token = jwt.sign({
id,
nick,
}, process.env.JWT_SECRET, {
expireIn: '1m' //1분
issuer: '토큰 발급자'
});
return res.json({
code: 200,
message: '토큰이 발급되었습니다.',
token,
});
catch (error) {
console.error(error);
return res.status(500).json({
code: 500,
message: '서버 에러',
});
}
});
router.get('/test', verifyToken, (req, res) => {
res.json(req.decoded);
});
module.exports = router;
이렇게 만든 라우터를 app.js
에 연결한다.
/*** app.js ***/
const tokenRouter = require('./routes/token');
//...
app.use('/token', tokenRouter);
//...
Postman에서 다음과 같이 설정한 후 Send 버튼을 클릭해 JWT 토큰을 발급 받는다.
다음과 같은 응답이 돌아온다. 여기서 token 값이 JWT 토큰이다.
{
"code": 200,
"message": "토큰이 발급되었습니다."
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
Postman에서 다음과 같이 설정한 후 Send 버튼을 클릭해 발급된 JWT 토큰을 테스트한다.
토큰이 유효한 경우 내용(페이로드)이 포함된 응답이 돌아옵니다.
{
"id": "vappet",
"nick": "hodoopapa",
"iat": 1581831357,
"exp": 1581831477,
"iss": "토큰발급자"
}
토큰이 유효하지 않은 경우, 즉 발급한지 1분이 지난 토큰을 보낸 경우 토큰이 만료되었다는 응답이 돌아온다.
{
"code": 419,
"message": "토큰이 만료되었습니다."
}
/*** routes/api.js ***/
const express = require('express');
const router = express.Router();
const { verifyToken } = require('./middlewares');
router.get('/', verifyToken, (req, res) => {
const users = [
{ id: 1, name: 'Node.js' },
{ id: 2, name: 'npm' },
{ id: 3, name: 'Pengsu' },
]
// 모든 정보 제공
res.json(users);
});
router.get('/:id', verifyToken, async(req, res) => {
const users = [
{ id: 1, name: 'Node.js' },
{ id: 2, name: 'npm' },
{ id: 3, name: 'Pengsu' },
]
// 특정 정보를 찾아 제공
user = users.find(u => u.id === parseInt(req.params.id));
res.send(user);
});
module.exports = router;
이렇게 만든 라우터를 app.js
에 연결한다.
/*** app.js ***/
//...
const tokenRouter = require('./routes/token');
const apiRouter = require('./routers/api);
//...
app.use('/token', tokenRouter);
app.use('/api', apiRouter);
//...
express-rate-limit 패키지를 사용하면 Express 서버에 접속하는 클라이언트의 사용량을 제한할 수 있다.
npm i express-rate-limit
명령어를 사용해 해당 패키지를 설치한다.
토큰 및 API 사용을 제한하는 리미터 메서드를 생성한다. 여기서는 토큰을 1분당 한 번, API를 1분에 최대 5번 호출하도록 제한하는 리미터를 만들어보자.
const RateLimit = require('express-rate-limit');
exports.tokenLimiter = new RateLimit({
windowMs: 1000 * 60, // 기준 시간 (1000ms * 60 = 1분)
max: 1, // 허용 횟수
delayMs: 0, // 호출 간격
handler(req, res) { // 제한 초과 시 콜백함수
res.status(this.statusCode).json({
code: this.statusCode, // 기본값: 429
message: '1분에 한 번만 요청할 수 있습니다.',
});
},
});
exports.apiLimiter = new RateLimit({
windowMs: 1000 * 60, // 기준 시간 (1000ms * 60 = 1분)
max: 5, // 허용 횟수
delayMs: 0, // 호출 간격
handler(req, res) { // 제한 초과 시 콜백함수
res.status(this.statusCode).json({
code: this.statusCode, // 기본값: 429
message: '1분에 최대 다섯 번 요청할 수 있습니다.',
});
},
});
라우터에 리미터 메서드를 콜백함수로 연결한다.
const express = require('express');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const { verifyToken, tokenLimiter } = require('./middlewares');
const router = express.Router();
// 토큰 발급 라우터에 tokenLimiter 연결
router.post('/', tokenLimiter, async(req, res) => {
// 생략..
});
module.exports = router;
const express = require('express');
const router = express.Router();
const { verifyToken, apiLimiter } = require('./middlewares');
// API 제공 라우터에 apiLimiter 연결
router.get('/', apiLimiter, verifyToken, (req, res) => {
// 생략..
});
router.get('/:id', apiLimiter, verifyToken, async(req, res) => {
// 생략 ...
});
module.exports = router;
Node.js 교과서(조현영)
ㅇ낳으므로 -> 않으므로 오타 있어요