JWT
JWT(JSON Web Token)은 JSON 형식의 데이터를 저장하는 토큰이다.
JWT의 구성요소
- 헤더(HEADER): 토큰 종류와 해시 알고리즘 정보가 저장
- 페이로드(PAYLOAD): 토큰의 내용물이 인코딩된 부분
- 시그니처(SIGNATURE): 일련의 문자열로, 시그니처를 통해 토큰이 변조되었는 지 여부를 확인
JWT 토큰은 JWT 비밀 키를 알지 않는 이상 변조가 불가능 JWT 토큰의 단점은 용량이 큼
JWT 인증 실습
- JWT 모듈 설치
npm i jsonwebtoken
- 시크릿 키 env에 저장
// .env
JWT_SECRET=비밀키임의생성
- JWT 검증 미들웨어 생성
// midlewares/index.js
const jwt = require('jsonwebtoken')
...
exports.verifyToken = (req, res, next) => {
try {
res.locals.decoded = jwt.verify(
req.headers.authorization,
process.env.JWT_SECRET
);
return next();
} catch (error) {
if (error.name === "TokenExpiredError") {
return res.status(419).json({
code: 419,
message: "토큰이 만료되었습니다.",
});
}
return res.status(401).json({
code: 401,
message: "유효하지 않은 토큰입니다.",
});
}
};
`jwt.verify` 메서드로 토큰을 검증함
- 첫번째 인수: 토큰 -> 요청 헤더에 저장된 토큰 `req.headers.athorization`을 사용
- 두번쨰 인수: 토큰의 비밀키 -> `process.env.JWT_SECRET`
토큰의 비밀키가 일치 하지 않거나 유효기간이 만료됐다면 에러가 발생해 `catch`문으로 이동함
인증에 성공한다면 토큰의 내용이 반환됨
- JWT 토큰 발급 미들웨어 생성
// controllers/v2.js
exports.createToken = async (req, res) => {
const { frontSecret } = req.body;
try {
const domain = await Domain.findOne({
where: { frontSecret },
include: {
model: User,
attribute: ["nick", "id"],
},
});
if (!domain) {
return res.status(401).json({
code: 401,
message: "등록되지 않은 도메인입니다. 먼저 도메인을 등록하세요",
});
}
const token = jwt.sign(
{
id: domain.User.id,
nick: domain.User.nick,
},
process.env.JWT_SECRET,
{
expiresIn: "3m",
issuer: "nodebird",
}
);
return res.json({
code: 200,
message: "토큰 발급되었습니다.",
token,
});
} catch (error) {
console.error(error);
return res.status(500).json({
code: 500,
message: "서버 에러",
});
}
};
`jwt.sign` 메서드로 토큰을 발급함
- 첫 번째 인수: 토큰의 내용 -> { 아이디와 이름 등 토큰 내용 삽입 }
- 두번째 인수: 토큰의 비밀키 -> `process.env.JWT_SECRET`
- 세번째 인수: 토큰의 설정 -> {유효기간, 발급자 등}
API 사용량 제한 구현
- express-rate-limit 모듈 설치
npm i express-rate-limit
- apiLimiter 미들웨어 추가
exports.apiLimiter = async (req, res, next) => {
let user;
if (res.locals.decoded?.id) {
user = await User.findOne({ where: { id: res.locals.decoded.id } });
}
rateLimit({
windowMs: 60 * 1000,
max: user?.type === "preminum" ? 100 : 10,
handler(req, res) {
res.status(this.statusCode).json({
code: this.statusCode,
message: "1분에 한 번만 요청할 수 있습니다.",
});
},
})(req, res, next);
};
`rateLimit`함수로 API 사용량 제한 구현
첫번째 인수: 옵션 -> {window(기준 시간), max(허용 횟수), handler(제한 초과 시 콜백 함수)}
- 라우터에 사용량 제한 미들웨어 삽입
const express = require("express");
const { verifyToken, apiLimiter } = require("../middlewares");
// routes/v2.js
const {
createToken,
tokenTest,
getMyPosts,
getPostsByHashtag,
corsWhenDomainMatches,
getFollowersByUser,
getFollowingsByUser,
} = require("../controllers/v2");
const router = express.Router();
router.use(corsWhenDomainMatches);
// 토큰 발급 라우터
// /v2/token
router.post("/token", apiLimiter, createToken);
// 토큰 테스트 라우터
router.get("/test", apiLimiter, verifyToken, tokenTest);
router.get("/posts/my", verifyToken, apiLimiter, getMyPosts);
router.get("/posts/hashtag/:title", verifyToken, apiLimiter, getPostsByHashtag);
router.get(
"/user/:id/followings",
verifyToken,
apiLimiter,
getFollowingsByUser
);
router.get("/user/:id/followers", verifyToken, apiLimiter, getFollowersByUser);
module.exports = router;
CORS
서버의 도메인과 요청을 보내는 클라이언트의 도메인이 다르면 요청을 차단하는것을 CORS(Cross-Origin Resource Sharing)이라 한다.
서버에서 서로 전송할때는 문제가 안되지만 클라이언트에서 서버로 요청시 CORS에 의해 요청이 차단된다.
브라우저에서 서버로 요청할때 기본적으로 `OPTION` 메서드로 실제 요청을 보내기 전에 서버가 요청의 도메인, 헤더와 메서드 등을 허용하는지 체크한다.
CORS 문제를 해결하려면 응답헤더에 `Access-Control-Aollow-Origin`과 `Access-Control-Aollow-Headers` 헤더를 넣어야한다.
const express = require("express");
const { verifyToken, apiLimiter } = require("../middlewares");
const {
createToken,
tokenTest,
getMyPosts,
getPostsByHashtag,
corsWhenDomainMatches,
getFollowersByUser,
getFollowingsByUser,
} = require("../controllers/v2");
const router = express.Router();
router.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "http://localhost:4000");
res.setHeader("Access-Control-Allow-Headers", "content-type");
next();
});
...
- cors 모듈 설치
직접 헤더에 넣어줘도 되지만 이러한 기능을 편리하게 제공하는 cors모듈을 사용할 수 있다.
npm i cors
- cors 패키지 적용
exports.corsWhenDomainMatches = async (req, res, next) => {
const domain = await Domain.findOne({
where: { host: new URL(req.get("origin")).host },
});
if (domain) {
cors({
origin: req.get("origin"),
credentials: true,
})(req, res, next);
} else {
next();
}
};
`credentials`: `true`이면 다른 도메인간에 쿠키가 공유됨
`origin`: 해당 도메인만 승인
참고
Node.js 교과서 : 네이버 도서
네이버 도서 상세정보를 제공합니다.
search.shopping.naver.com
[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지 강의 | 제로초(조현영) - 인프런
제로초(조현영) | 노드가 무엇인지부터, 자바스크립트 최신 문법, 노드의 API, npm, 모듈 시스템, 데이터베이스, 테스팅 등을 배우고 5가지 실전 예제로 프로젝트를 만들어 나갑니다. 클라우드에 서
www.inflearn.com
'Node.js' 카테고리의 다른 글
[Node.js 교과서] 섹션 12 - 웹 소켓으로 실시간 데이터 전송하기 (0) | 2024.12.08 |
---|---|
[Node.js 교과서] 섹션 11 - 노드 서비스 테스트 하기 (3) | 2024.12.05 |
[error] Sequelize A is not associated to B (1) | 2024.11.19 |
[노드 교과서] 섹션 9 - 익스프레스로 SNS 서비스 만들기 (0) | 2024.11.17 |
JS로 차트 훼손 없이 엑셀 수정하기!!! (1) | 2024.11.16 |