[Node.js 교과서] 섹션 10 - 웹 API 서버 만들기

2024. 11. 22. 18:46·Node.js

 

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  (3) 2024.11.19
[노드 교과서] 섹션 9 - 익스프레스로 SNS 서비스 만들기  (2) 2024.11.17
JS로 차트 훼손 없이 엑셀 수정하기!!!  (1) 2024.11.16
'Node.js' 카테고리의 다른 글
  • [Node.js 교과서] 섹션 12 - 웹 소켓으로 실시간 데이터 전송하기
  • [Node.js 교과서] 섹션 11 - 노드 서비스 테스트 하기
  • [error] Sequelize A is not associated to B
  • [노드 교과서] 섹션 9 - 익스프레스로 SNS 서비스 만들기
bearn_soo
bearn_soo
추상적으로 받아들이되 구체적으로 이해하라
  • bearn_soo
    초밥구이
    bearn_soo
  • 전체
    오늘
    어제
    • 분류 전체보기 (78)
      • Javascript (16)
      • Node.js (18)
      • 알고리즘 (8)
      • 네트워크 (2)
      • 데이터베이스 (10)
      • 운영체제 (11)
      • 자료구조 (6)
      • 공부기록 (7)
  • 인기 글

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
bearn_soo
[Node.js 교과서] 섹션 10 - 웹 API 서버 만들기
상단으로

티스토리툴바