๐ํค์๋: API ์๋ฒ, ํฌ๋กค๋ง, JWT ํ ํฐ ์ธ์ฆ, ์ฌ์ฉ๋ ์ ํ, CORS
1. API์ ์น API ์๋ฒ์ ๊ฐ๋
- API (Application Programming Interface): ๋ค๋ฅธ ์ดํ๋ฆฌ์ผ์ด์ ์์ ํ์ฌ ํ๋ก๊ทธ๋จ์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๊ฒ ํ์ฉํ๋ ์ ์ ์ด๋ค.
- ์น API: ๋ค๋ฅธ ์น ์๋น์ค์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ฑฐ๋ ์์์ ๊ฐ์ ธ์ฌ ์ ์๋ ์ฐฝ๊ตฌ์ด๋ค.
- ์น API ์๋ฒ: ์๋ฒ์ API๋ฅผ ์ฌ๋ ค์ URL์ ํตํด ์ ๊ทผํ ์ ์๊ฒ ๋ง๋ ๊ฒ์ด๋ค.
2. ํฌ๋กค๋ง(crawling)
- ํฌ๋กค๋ง: ์น ์ฌ์ดํธ๊ฐ ์์ฒด์ ์ผ๋ก ์ ๊ณตํ๋ API๊ฐ ์๊ฑฐ๋ API ์ด์ฉ์ ์ ํ์ด ์์ ๋ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด๋ค.
- ์น ์ฌ์ดํธ์ ์ ๋ณด๋ฅผ ์ผ์ ์ฃผ๊ธฐ๋ก ์์งํด ์์ฒด์ ์ผ๋ก ๊ฐ๊ณตํ๋ ๊ธฐ์ ์ด๋ค.
- ์น ์ฌ์ดํธ์์ ์ง์ ์ ๊ณตํ๋ API๊ฐ ์๋๋ค. → ์ํ๋ ์ ๋ณด๋ฅผ ์ป์ง ๋ชปํ ์๋ ์๋ค.
- ๋๋ฉ์ธ/robots.txt์ ์ ์ํ์ฌ ์น ์ฌ์ดํธ๊ฐ ์ด๋ค ํ์ด์ง์ ํฌ๋กค๋ง์ ํ์ฉํ๋์ง ํ์ธํ ์ ์๋ค.
- ์ฃผ๊ธฐ์ ์ธ ํฌ๋กค๋ง=์น ์๋ฒ์ ํธ๋ํฝ์ด ์ฆ๊ฐํด ์๋ฒ์ ๋ฌด๋ฆฌ๊ฐ ๊ฐ๋ค. → ๊ณต๊ฐํด๋ ๋๋ ์ ๋ณด๋ค์ API๋ก ๋ง๋ค์ด API๋ฅผ ํตํด ๊ฐ์ ธ๊ฐ๊ฒ ํ๋ ๊ฒ์ด ์ข๋ค.
- NodeBird ์๋น์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ณต์ ํ๋ค.
- ๋ค๋ฅธ ์๋น์ค์์ NodeBird ์๋น์ค์ ๊ฒ์๊ธ, ํด์ํ๊ทธ, ์ฌ์ฉ์ ์ ๋ณด๋ฅผ JSON ํ์์ผ๋ก ์ ๊ณตํ๋ค.
- ๋จ, ์ธ์ฆ๋ฐ์ ์ฌ์ฉ์๋ง ์ผ์ ํ ๋น๋ ์์์ API ํธ์ถ์ ํ์ฉํ๋ค.
1. nodebird-api ํด๋๋ฅผ ๋ง๋ ํ package.json ํ์ผ์ ์์ฑํ๋ค.
2. (1) npm init์ผ๋ก ์์ฑํ ํ dependencies๋ค์ ์ค์นํ๊ฑฐ๋ (2)) ์๋ pakage.json์ ๋ณต์ฌํ๋ค.
package.json
{
"name": "nodebird-api",
"version": "0.0.1",
"description": "NodeBird API ์๋ฒ",
"main": "app.js",
"scripts": {
"start": "nodemon app",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Zero Cho",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.0",
"cookie-parser": "^1.4.5",
"dotenv": "^16.0.0",
"express": "^4.17.1",
"express-session": "A1.17.1",
"morgan": "^1.10.0",
"mysql2":, "^2.1.0",
"nunjucks": "^3.2.1",
"passport": "^0.5.2",
"passport-kakao": "^1.0.1",
"passport-local": "^1.0.0",
"sequelize": "^6.0.0",
"uuid":"^8.3.2"
},
"devDependencies": {
"nodemon": "^2.0.3"
}
}
์ฝ์
$ npm i
- NodeBird์์ config, models, passport, middle wares ํด๋-์ ๋ด์ฉ๋ฌผ์ ๋ณต์ฌํด nodebird-api ํด๋์ ๋ถ์ฌ ๋ฃ๋๋ค.
- controllers์ routes ํด๋๋ auth.js๋ง ๊ทธ๋๋ก ์ฌ์ฉํ๋ค.
- .env ํ์ผ์ ๋ณต์ฌํ๊ณ , ๋ค๋ฅธ ํด๋์ ํ์ผ์ ์๋ก ๋์ฌ ๋๋ง๋ค ์ง์ ์์ฑํ๋ค.
3. ์๋ฌ ํ์ ํ์ผ์ ๋ง๋ ๋ค.
- views ํด๋๋ฅผ ๋ง๋ค๊ณ error.html ํ์ผ์ ์์ฑํ๋ค.
4. ๋๋ฉ์ธ ๋ชจ๋ธ์ ์ถ๊ฐํ๋ค.
- ๋๋ฉ์ธ ๋ชจ๋ธ์๋ ์ธํฐ๋ท ์ฃผ์, ๋๋ฉ์ธ ์ข ๋ฅ, ํด๋ผ์ด์ธํธ ๋น๋ฐํค๊ฐ ๋ค์ด๊ฐ๋ค.
- type ์ปฌ๋ผ์ ENUM ์์ฑ์ผ๋ก ๋ฌด๋ฃ(free)๋ ํ๋ฆฌ๋ฏธ์(premium) ๋ ์ค ํ๋๋ง ๊ฐ์ผ๋ก ์ ๋ ฅํ ์ ์๊ฒ ํ๋ค.
- ํด๋ผ์ด์ธํธ ๋น๋ฐ ํค๋ ์์ฒญํ ๋๋ฉ์ธ๊น์ง ์ผ์นํด์ผ ์์ฒญ์ ๋ณด๋ผ ์ ์๊ฒ ์ ํ์ ๋๋ค.
- clientSecret ์ปฌ๋ผ์ ์ถฉ๋ ๊ฐ๋ฅ์ฑ์ด ๋งค์ฐ ์ ์ ๋๋คํ ๋ฌธ์์ด์ธ UUID๋ผ๋ ํ์ ์ ๊ฐ์ง๋ค.
- ๋๋ฉ์ธ ๋ชจ๋ธ์ ์ฌ์ฉ์ ๋ชจ๋ธ๊ณผ ์ผ๋๋ค ๊ด๊ณ๋ฅผ ๊ฐ๋๋ฐ, ์ฌ์ฉ์ ํ ๋ช ์ด ์ฌ๋ฌ ๋๋ฉ์ธ์ ์์ ํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
5. ๋ก๊ทธ์ธํ๋ ํ๋ฉด์ ๋ง๋ ๋ค.
- ๋๋ฉ์ธ์ ๋ฑ๋กํ๋ ํ๋ฉด๋ ํฌํจ๋๋ค.
- ๋ก๊ทธ์ธ์ ํ์ง ์์๋ค๋ฉด ๋ก๊ทธ์ธ ์ฐฝ์ด ๋จผ์ ๋จ๊ณ , ๋ก๊ทธ์ธํ ์ฌ์ฉ์์๊ฒ ๋๋ฉ์ธ ๋ฑ๋ก ํ๋ฉด์ ๋ณด์ฌ์ค๋ค.
6. ์๋ฒ ์คํํ๊ธฐ
- http://localhost:8002๋ก ์ ์ํ๋ฉด API๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ํ๊ฐ๊ฐ ํ์ํ๋ค.
- ์ฌ์ฉ์ ์ ๋ณด๋ NodeBird ์ฑ๊ณผ ๊ณต์ ํ๋ฏ๋ก NodeBird ์ฑ์ ์์ด๋๋ก ๋ก๊ทธ์ธํ๋ฉด ๋๋ค.
- ๋ก๊ทธ์ธ ํ์๋ ๋๋ฉ์ธ ๋ฑ๋ก ํ๋ฉด์ด ๋ํ๋๋ค.
- ์น ๋ธ๋ผ์ฐ์ ์์ ์์ฒญ์ ๋ณด๋ผ ๋, ์๋ต์ ํ๋ ๊ณณ๊ณผ ๋๋ฉ์ธ์ด ๋ค๋ฅด๋ฉด CORS ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋ค.
-localhost:4000 ๋๋ฉ์ธ์ ๋ฑ๋กํ์ฌ ๋ฐ๊ธ๋ฐ์ ๋น๋ฐ ํค๋ NodeBird API๋ฅผ ํธ์ถํ ๋ ์ธ์ฆ ์ฉ๋๋ก ์ฌ์ฉํ๋ค.
(์์ธํ ์ฝ๋๋ ์ฑ ์ฐธ์กฐ...)
1. JWT(JSON Web Token)
- JWT: JSON ํ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ํ ํฐ์ด๋ค.
- ๋ด์ฉ์ ๋ณผ ์ ์๊ธฐ์ JWT์๋ ๋ฏผ๊ฐํ ๋ด์ฉ์ ๋ฃ์ผ๋ฉด ์๋๋ค.
- JWT๋ ๋ค์๊ณผ ๊ฐ์ ์ธ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋์ด์๋ค.
โ ํค๋
- ํ ํฐ ์ข ๋ฅ์ ํด์ ์๊ณ ๋ฆฌ์ฆ ์ ๋ณด๊ฐ ๋ค์ด์๋ค.
โก ํ์ด๋ก๋
- ํ ํฐ์ ๋ด์ฉ๋ฌผ์ด ์ธ์ฝ๋ฉ๋ ๋ถ๋ถ์ด๋ค.
โข ์๊ทธ๋์ฒ
- ์ผ๋ จ์ ๋ฌธ์์ด, ์๊ทธ๋์ฒ๋ฅผ ํตํด ํ ํฐ์ ๋ณ์กฐ ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๋ค.
- JWT ๋น๋ฐํค๋ก ๋ง๋ค์ด์ง๋ฉฐ, ๋ ธ์ถ ์์ ํ ํฐ์ด ์์กฐ๋ ์ ์๋ค.
- ์๊ทธ๋์ฒ ์์ฒด๋ ์จ๊ธฐ์ง ์์๋ ๋๋ค.
2. ํ ํฐ์ ์ฌ์ฉํ๋ ์ด์
โ ํ ํฐ ๋ด์ฉ ํ์ธํ๊ธฐ (https://jwt.io)
- ์ฐ์ธก ํ๋จ์ serect ๋ถ๋ถ์ด JWT ๋น๋ฐ ํค ์ด๋ค.
- ํ์ด๋ก๋ ๋ถ๋ถ์ด ๋ ธ์ถ๋์ด ๋ด์ฉ์ ์ ์ ์๋ค.
โก ๋ด์ฉ์ด ๋ ธ์ถ๋๋ JWT ํ ํฐ์ ์ฌ์ฉํ๋ ์ด์
- ๋ด์ฉ์ด ์๋ ๋๋คํ ํ ํฐ์ ์ฌ์ฉํ ๊ฒฝ์ฐ: ํ ํฐ์ ์ฃผ์ธ, ๊ถํ์ ์์ฒญ๋ง๋ค ์ฒดํฌํด์ผํ๋ค.
- JWT ํ ํฐ์ JWT ๋น๋ฐ ํค๋ฅผ ์์ง ์๋ ์ด์ ๋ณ์กฐ๊ฐ ๋ถ๊ฐ๋ฅํ๋ค.
- ๋ณ์กฐํ ํ ํฐ์ ์๊ทธ๋์ฒ๋ฅผ ๋น๋ฐ ํค๋ฅผ ํตํด ๊ฒ์ฌํ ๋ ๊ตฌ๋ณํ ์ ์๋ค.
→ ์ธ๋ถ์ ๋ ธ์ถ๋์ด๋ ์ข์ ์ ๋ณด์ ํ ํด ์ฌ์ฉ์ ์ด๋ฆ, ๊ถํ์ ๋ฃ๊ณ ์์ฌํ๋ฉฐ ๋ด์ฉ๋ฌผ์ ๋ฏฟ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ ์์ด๋ ๊ถํ์ ์ค ์ ์๋ค.
3. JWT ํ ํฐ์ ๋จ์
- ๋ด์ฉ๋ฌผ์ด ๋ค์ด์์ด ๋๋คํ ํ ํฐ์ ์ฌ์ฉํ ๋๋ณด๋ค ์ฉ๋์ด ํฌ๋ค.
- ์์ฒญ ๋๋ง๋ค ํ ํฐ์ด ์ค๊ฐ์ ๋ฐ์ดํฐ ์์ด ์ฆ๊ฐํ๋ค.
→ ๋๋คํ ๋ฌธ์์ด์ ์ฌ์ฉํด์ ๋งค๋ฒ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์กฐํํ๋ ์์ ์ ๋น์ฉ๊ณผ JWT ํ ํฐ์ ์ฌ์ฉํด์ ๋ฐ์ํ๋ ๋ฐ์ดํฐ ๋น์ฉ์ ๋น๊ตํ์ฌ ์ ์ ํ ๋์ ์ฌ์ฉํ๋ฉด ์ข๋ค.
4. ์น ์๋ฒ์์ JWT ํ ํฐ ์ธ์ฆ ๊ณผ์ ๊ตฌํํ๊ธฐ
โ JWT ๋ชจ๋ ์ค์นํ๊ธฐ
์ฝ์
$ npm i jsonwebtoken
โก JWT๋ฅผ ์ฌ์ฉํด์ API ๋ง๋ค๊ธฐ
- ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ API๋ฅผ ์ฐ๋ ค๋ฉด JWT ํ ํฐ์ ๋ฐ๊ธํ๊ณ ์ธ์ฆ์ ๋ฐ์์ผํ๋ค.
→ ๋ผ์ฐํฐ์ ๊ณตํต๋๋ฏ๋ก ๋ฏธ๋ค์จ์ด๋ก ๋ง๋ค์ด ๋๋ ๊ฒ์ด ์ข๋ค.
nodebird-api/.env
COOKIE_SECRET=cookiesecret
KAKAO_ID=5d4daf57becfd72fd9c919882552c4a6
JWT_SECRET=jwtSecret
nodebird-api/middlewares/index.js
const jwt = require('jsonwebtoken');
...
exports.verifyToken = (req, res, next) => {
try {
//์ธ์ฆ ์ฑ๊ณต ์์๋ ํ ํฐ์ ๋ด์ฉ์ด ๋ฐํ๋์ด res.locals.decoded์ ์ ์ฅ
res.locals.decoded = jwt.verify(req.headers.authorization, process.env.JWT_SECRET);
// jwt.verify ๋ฉ์๋๋ก ํ ํฐ์ ๊ฒ์ฆํ ์ ์๋ค.
// ์ฒซ ๋ฒ์งธ ์ธ์๋ก ํ ํฐ, ๋ ๋ฒ์งธ ์ธ์๋ก ํ ํฐ์ ๋น๋ฐ ํค๋ฅผ ๋ฃ๋๋ค.
return next();
} catch (error) { //
if (error.name === 'TokenExpiredError') { // ์ ํจ ๊ธฐ๊ฐ ์ด๊ณผ๋ ๊ฒฝ์ฐ
return res.status(419).json({
code: 419, //์ฝ๋๋ 400๋ฒ ๋ ์ซ์ ์ค์์ ๋ง์๋๋ก ์ ํ ๊ฐ๋ฅ
message: 'ํ ํฐ์ด ๋ง๋ฃ๋์์ต๋๋ค',
});
}
return res.status(401).json({ // ํ ํฐ์ ๋น๋ฐํค๊ฐ ์ผ์นํ์ง ์๋ ๊ฒฝ์ฐ
code: 401,
message: '์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค',
});
}
};
- res.locals์ ์ฌ์ฉ์ ์์ด๋, ๋๋ค์, ๋ฐ๊ธ์ ์ ํจ๊ธฐ๊ฐ ๋ฑ์ ์ ์ฅํ์ผ๋ฏ๋ก ๋ค์ ๋ฏธ๋ค์จ์ด์์ ํ ํฐ์ ๋ด์ฉ๋ฌผ์ ์ฌ์ฉํ ์ ์๋ค.
nodebird-api/routes/v1.js
const express = require('express');
const { verifyToken } = require('../middlewares');
const { createToken, tokenTest } = require('../controllers/v1');
const router = express.Router();
// POST /v1/token: ํ ํฐ์ ๋ฐ๊ธํ๋ ๋ผ์ฐํฐ
router.post('/token', createToken);
// GET /v1/test: ์ฌ์ฉ์๊ฐ ํ ํฐ์ ํ
์คํธํด๋ณผ ์ ์๋ ๋ผ์ฐํฐ
router.get('/test', verifyToken, tokenTest);
module.exports = router;
- ๋ผ์ฐํฐ ์ด๋ฆ/๋ฒ์ (v1): ํ ๋ฒ ๋ฒ์ ์ด ์ ํด์ง ํ์๋ ๋ผ์ฐํฐ๋ฅผ ํจ๋ถ๋ก ์์ ํ๋ฉด ์๋๋ค.
- ๊ธฐ์กด์ ์๋ ๋ผ์ฐํฐ๊ฐ ์์ ๋๋ฉด API๋ฅผ ์ฌ์ฉํ๋ ํ๋ก๊ทธ๋จ๋ค์ด ์ค์๋ํ ์ ์๋ค.
→ ๊ธฐ์กด ์ฌ์ฉ์์๊ฒ ์ํฅ์ด ๋ฏธ์น ์ ๋๋ก ์์ ํด์ผ ํ๋ค๋ฉด, ๋ฒ์ ์ ์ฌ๋ฆฐ ๋ผ์ฐํฐ๋ฅผ ์๋ก ์ถ๊ฐํ ๋ค ์๋ก์ด API๊ฐ ๋์์์ ์๋ฆฐ๋ค.
- ๋ฒ์ ์ ๋ฐ๋์ ๋ผ์ฐํฐ์ ํ์ํ ํ์๋ ์๋ค. (ํค๋์ ๋ฒ์ ํ์, ์ฟผ๋ฆฌ์คํธ๋ง/๋ณธ๋ฌธ์ ๋ฒ์ ํ์ ๊ฐ๋ฅ)
nodebird-api/controllers/v1.js
const jwt = require('jsonwebtoken');
const { Domain, User } = require('../models');
exports.createToken = async (req, res) => {
const { clientSecret } = req.body;
try {
const domain = await Domain.findOne({
where: { clientSecret },
include: {
model: User,
attribute: ['nick', 'id'],
},
});
if (!domain) {
return res.status(401).json({
code: 401,
message: '๋ฑ๋ก๋์ง ์์ ๋๋ฉ์ธ์
๋๋ค. ๋จผ์ ๋๋ฉ์ธ์ ๋ฑ๋กํ์ธ์',
});
}
const token = jwt.sign({ //sign์ ์ฒซ๋ฒ์งธ ์ธ์: ํ ํฐ์ ๋ด์ฉ (์ฌ์ฉ์ ์์ด๋, ๋๋ค์)
id: domain.User.id,
nick: domain.User.nick,
}, process.env.JWT_SECRET, { //sign ๋๋ฒ์งธ ์ธ์: ํ ํฐ์ ๋น๋ฐํค
expiresIn: '1m', // 1๋ถ //sign ์ธ๋ฒ์งธ ์ธ์: ํ ํฐ์ ์ค์ (์ ํจ๊ธฐ๊ฐ, ๋ฐ๊ธ์)
issuer: 'nodebird',
});
return res.json({ //์ผ์ ํ ํ์์ผ๋ก ๊ฐ์ถฐ์ผ ์๋ต๋ฐ๋ ์ชฝ์์ ์ฒ๋ฆฌํ๊ธฐ ์ข์
code: 200, //์ฝ๋๊ฐ 200๋ฒ๋๊ฐ ์๋๋ฉด ์๋ฌ
message: 'ํ ํฐ์ด ๋ฐ๊ธ๋์์ต๋๋ค', //code๋ฅผ ์ดํดํ์ง ๋ชปํ ๊ฒฝ์ฐ๋ฅผ ๋๋น
token,
});
} catch (error) {
console.error(error);
return res.status(500).json({
code: 500,
message: '์๋ฒ ์๋ฌ',
});
}
};
exports.tokenTest = (req, res) => {
res.json(res.locals.decoded);
};
- ๋ผ์ฐํฐ๋ฅผ ์๋ฒ์ ์ฐ๊ฒฐํ๋ค.
nodebird-api/app.js
...
dotenv.config();
const v1=require('./routes/v1');
const authRouter=require('./routes/auth');
...
app.use(passport.sessing());
app.use('/v1', v1);
app.use('/auth', authRouter);
...
- API ์ฌ์ฉํ๋ ์๋น์ค: ๋ค๋ฅธ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ฏ๋ก ํด๋ผ์ด์ธํธ ์ญํ ์ ํ๋ค.
- NodeBird ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ถ์ดํ๋ ์ฌ์ฉ์= ๋ณดํต ๊ทธ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํด 2์ฐจ์ ์ธ ์๋น์ค๋ฅผ ํ๋ ค๋ ํ์ฌ๊ฐ API๋ฅผ ์ด์ฉํ๋ค.
(์์) ์ผํ๋ชฐ๋ค์ ์ต์ ๊ฐ๋ฅผ ์๋ ค์ฃผ๋ ์๋น์ค๊ฐ 2์ฐจ ์๋น์ค๊ฐ ๋๋ค.
- 2์ฐจ ์๋น์ค ์ด๋ฆ์ NodeCat์ผ๋ก ํ๋ค.
โ nodebird-api์ API๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ์ฃผ๋ชฉ์ ์ธ ์๋ฒ
nodecat/package.json
{
"name": "nodecat",
"version": "0.0.1",
"description": "NodeBird 2์ฐจ ์๋น์ค",
"main": "app.js",
"scripts": {
"start": "nodemon app"
},
"author": "Zero Cho",
"license": "ISC",
"dependencies": {
"axios": "^0.27.2",
"cookie-parser": "^1.4.6",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"express-session": "^1.17.3",
"morgan": "^1.10.0",
"nunjucks": "^3.2.3"
},
"devDependencies": {
"nodemon": "^2.0.16"
}
}
์ฝ์
$ npm i
โก ์๋ฒํ์ผ๊ณผ ์๋ฌ๋ฅผ ํ์ํ ํ์ผ ์์ฑ
nodecat/app.js
const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
dotenv.config();
const indexRouter = require('./routes');
const app = express();
app.set('port', process.env.PORT || 4000);
app.set('view engine', 'html');
nunjucks.configure('views', {
express: app,
watch: true,
});
app.use(morgan('dev'));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
}));
app.use('/', indexRouter);
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} ๋ผ์ฐํฐ๊ฐ ์์ต๋๋ค.`);
error.status = 404;
next(error);
});
app.use((err, req, res, next) => {
res.locals.message = err.message;
res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
res.status(err.status || 500);
res.render('error');
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '๋ฒ ํฌํธ์์ ๋๊ธฐ ์ค');
});
nodecat/error.html
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>
โข ์ฌ์ฉ์ ์ธ์ฆ ์งํ์ ํ ์คํธํ๋ ๋ผ์ฐํฐ ๋ง๋ค๊ธฐ
- clientSecret์ .env
nodecat/.env
COOKIE_SECRET=nodecat
CLIENT_SECRET=7d67444e-fd01-4f9b-8680-f72464d02a57
nodecat/routes/index.js
const express = require('express');
const { test } = require('../controllers');
const router = express.Router();
// POST /test
router.get('/test', test);
module.exports = router;
nodecat/controllers/index.js
const axios = require('axios');
exports.test = async (req, res, next) => { // ํ ํฐ ํ
์คํธ ๋ผ์ฐํฐ
try {
if (!req.session.jwt) { // ์ธ์
์ ํ ํฐ์ด ์์ผ๋ฉด ํ ํฐ ๋ฐ๊ธ ์๋
const tokenResult = await axios.post('http://localhost:8002/v1/token', {
clientSecret: process.env.CLIENT_SECRET,
});
if (tokenResult.data?.code === 200) { // ํ ํฐ ๋ฐ๊ธ ์ฑ๊ณต
req.session.jwt = tokenResult.data.token; // ์ธ์
์ ํ ํฐ ์ ์ฅ
} else { // ํ ํฐ ๋ฐ๊ธ ์คํจ
return res.json(tokenResult.data); // ๋ฐ๊ธ ์คํจ ์ฌ์ ์๋ต
}
}
// ๋ฐ๊ธ๋ฐ์ ํ ํฐ ํ
์คํธ
const result = await axios.get('http://localhost:8002/v1/test', {
headers: { authorization: req.session.jwt },// ๋ฐ๊ธ๋ฐ์ ํ ํฐ์ผ๋ก ํ ํฐ์ด ์ ํจํ์ง ํ
์คํธ
});
return res.json(result.data);
} catch (error) {
console.error(error);
if (error.response?.status === 419) { // ํ ํฐ ๋ง๋ฃ ์
return res.json(error.response.data);
}
return next(error);
}
};
- GET/test ๋ผ์ฐํฐ ์ฌ์ฉํ๊ธฐ
- ์ฝ์์ ํ๋ ๋ ๋์ ์๋ฒ(localhost:4000)์ nodebird-api(localhost:8002)๋ ์คํ ์ค์ด์ด์ผํ๋ค.
NodeCat๊ณผ NodeBird API ์๋ฒ์์ ๋ชจ๋ ์คํ
$ npm start
//๊ฐ๊ฐ ๋ค๋ฅธ ํฌํธ์์ ๋๊ธฐ ์ค
4000 ๋ฒ ํฌํธ์์ ๋๊ธฐ ์ค
8002๋ฒ ํฌํธ์์ ๋๊ธฐ ์ค
- localhost:8002๋ API ์๋น์ค๋ฅผ ์ ๊ณตํ๋ nodebird-api ์๋ฒ
- localhost:4000์ API ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ NodeCat ์๋ฒ
- ์ ์ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋ฐ๊ธ๋ฐ์ ํ ํฐ์ ๋ด์ฉ์ด ํ์๋๋ค.
- 1๋ถ์ ๊ธฐ๋ค๋ฆฐ ํ ๋ค์ https://localhost:4000/test ๋ผ์ฐํฐ์ ์ ์ํ๋ฉด ํ ํฐ์ด ๋ง๋ฃ๋์๋ค๋ ๋ฉ์์ง๊ฐ ๋ํ๋๋ค.
- ๋ค์ API ์ ๊ณต์(nodebird-api)์ ์ ์ฅ์ผ๋ก ๋์์์ ๋๋จธ์ง API ๋ผ์ฐํฐ๋ฅผ ์์ฑํ๋ค.
nodebird-api/routes/v1.js
const express = require('express');
const { verifyToken } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v1');
const router = express.Router();
...
// GET /v1/posts/my
router.get('/posts/my', verifyToken, getMyPosts);
// GET /v1/posts/hashtag/:title
router.get('/posts/hashtag/:title', verifyToken, getPostsByHashtag);
module.exports = router;
- GET/v1/posts/my์ GET /v1/posts/hashtag/:title์ ๋ด๊ฐ ์ฌ๋ฆฐ ํฌ์คํธ์ ํด์ํํฌ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ผ์ฐํฐ
nodebird-api/controllers/v1.js
const jwt = require('jsonwebtoken');
const { Domain, User, Post, Hashtag } = require('../models');
...
exports.tokenTest = (req, res) => {
res.json(res.locals.decoded);
};
exports.getMyPosts = (req, res) => {
Post.findAll({ where: { userId: res.locals.decoded.id } })
.then((posts) => {
console.log(posts);
res.json({
code: 200,
payload: posts,
});
})
.catch((error) => {
console.error(error);
return res.status(500).json({
code: 500,
message: '์๋ฒ ์๋ฌ',
});
});
};
exports.getPostsByHashtag = async (req, res) => {
try {
const hashtag = await Hashtag.findOne({ where: { title: req.params.title } });
if (!hashtag) {
return res.status(404).json({
code: 404,
message: '๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค',
});
}
const posts = await hashtag.getPosts();
return res.json({
code:200,
payload:posts,
});
} catch(error){
console.error(error);
return res.status(500).json({
code:500,
message:'์๋ฒ ์๋ฌ',
});
}
};
- ์ฌ์ฉํ๋ ์ธก(NodeCat)์์๋ ์์ API๋ฅผ ์ด์ฉํ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
- ํ ํฐ์ ๋ฐ๊ธ๋ฐ๋ ๋ถ๋ถ์ ๋ฐ๋ณต๋๋ฏ๋ก ํจ์๋ก ๋ง๋ค์ด ์ฌ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
nodecat/routes/index.js
const express = require('express');
const { searchByHashtag, getMyPosts } = require('../controllers');
const router = express.Router();
router.get('/myposts', getMyPosts);
router.get('/search/:hashtag', searchByHashtag);
module.exports = router;
nodecat/.env
API_URL=http://localhost:8002/v1
ORIGIN=http://localhost:4000
nodecat/controllers/index.js
const axios = require('axios');
const URL = process.env.API_URL;
axios.defaults.headers.origin = process.env.ORIGIN; // origin ํค๋ ์ถ๊ฐ
//(1)
const request = async (req, api) => {
try {
if (!req.session.jwt) { // ์ธ์
์ ํ ํฐ์ด ์์ผ๋ฉด
const tokenResult = await axios.post(`${URL}/token`, {
clientSecret: process.env.CLIENT_SECRET,
});
req.session.jwt = tokenResult.data.token; // ์ธ์
์ ํ ํฐ ์ ์ฅ
}
return await axios.get(`${URL}${api}`, {
headers: { authorization: req.session.jwt },
}); // API ์์ฒญ
} catch (error) {
if (error.response?.status === 419) { // ํ ํฐ ๋ง๋ฃ ์ ํ ํฐ ์ฌ๋ฐ๊ธ ๋ฐ๊ธฐ
delete req.session.jwt;
return request(req, api);
} // 419 ์ธ์ ๋ค๋ฅธ ์๋ฌ์ด๋ฉด
throw error;
}
};
//(2)
exports.getMyPosts = async (req, res, next) => {
try {
const result = await request(req, '/posts/my');
res.json(result.data);
} catch (error) {
console.error(error);
next(error);
}
};
//(3)
exports.searchByHashtag = async (req, res, next) => {
try {
const result = await request(
req,
`/posts/hashtag/${encodeURIComponent(req.params.hashtag)}`,
);
res.json(result.data);
} catch (error) {
if (error.code) {
console.error(error);
next(error);
}
}
};
(1)
- request ํจ์: NodeBird API์ ์์ฒญ์ ๋ณด๋ด๋ ํจ์
- ํค๋์ origin ๊ฐ์ localhost:4000์ผ๋ก ์ค์ : ์ด๋์ ์์ฒญ์ ๋ณด๋ด๋์ง ํ์ ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
- ์ธ์ ์ ํ ํฐ์ด ์์ผ๋ฉด clientSecret์ ์ฌ์ฉํด ํ ํฐ์ ๋ฐ๊ธ๋ฐ๋ ์์ฒญ์ ๋ณด๋ธ๋ค.
- ๋ฐ๊ธ๋ฐ์ ํ์๋ ํ ํฐ์ ์ด์ฉํด API ์์ฒญ์ ๋ณด๋ธ๋ค.
- ํ ํฐ์ ์ฌ์ฌ์ฉ์ ์ํด ์ธ์ ์ ์ ์ฅํ๋ค.
- ํ ํฐ์ด ๋ง๋ฃ๋์ด 419 ์๋ฌ๊ฐ ๋ฐ์ํ ๋, ํ ํฐ์ ์ง์ฐ๊ณ request ํจ์๋ฅผ ์ฌ๊ท์ ์ผ๋ก ํธ์ถ์ ์ฌ์์ฒญํ๋ค.
- ๊ฒฐ๊ด๊ฐ์ ์ฝ๋์ ๋ฐ๋ผ ์ฑ๊ณต ์ฌ๋ถ๋ฅผ ์ ์ ์๊ณ , ์คํจ ์์๋ ์คํจ ์ข ๋ฅ๋ฅผ ์ ์ ์๋ค.
(2)
- GET /myposts ๋ผ์ฐํฐ: API๋ฅผ ์ฌ์ฉํด ์์ ์ด ์์ฑํ ํฌ์คํธ๋ฅผ JSON ํ์์ผ๋ก ๊ฐ์ ธ์ค๋ ๋ผ์ฐํฐ
- ํ์ฌ๋ JSON์ผ๋ก ์๋ตํ์ง๋ง ํ ํ๋ฆฟ ์์ง์ ์ฌ์ฉํด ํ๋ฉด์ ๋ ๋๋งํ ์ ์๋ค.
(3)
- GET/search/:hashtag ๋ผ์ฐํฐ: API๋ฅผ ์ฌ์ฉํด ํด์ํ๊ทธ๋ฅผ ๊ฒ์ํ๋ ๋ผ์ฐํฐ
- localhost:4000์ ์ ์ํ๋ฉด GET / ๋ผ์ฐํฐ๋ ๋ง๋ค์ง ์์๊ธฐ์ GET / ๋ผ์ฐํฐ๊ฐ ์๋ค๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
- ๋ ธ๋ ํด์ํ๊ทธ๊ฐ ๋ฌ๋ฆฐ ๊ฒ์๊ธ๋ค์ ๊ฒ์ํ ๊ฒฐ๊ณผ์ด๋ค.
- ๋ฐ์ดํฐ๊ฐ JSON ํ์์ผ๋ก ์ค๋ฏ๋ก ์ํ๋๋๋ก ์ฌ์ฉํ ์ ์๋ค.
- 1๋ถ ๋ค ์์ฒญ์ด ๋ง๋ฃ๋๊ณ ๋์ ๋ค์ ์์ฒญ์ ๋ณด๋ด๋ฉด ์์์ ํ ํฐ์ ์ฌ๋ฐ๊ธํ ํ ๋ค์ ์์ฒญ์ ๋ณด๋ธ๋ค.
- nodebird-api์ ์ฝ์์๋ ๋ค์๊ณผ ๊ฐ์ ์ธ ๊ฐ์ ์์ฒญ์ด ๊ธฐ๋ก๋๋ค.
POST /v1/posts/hashtag/๋
ธ๋ 419 0.962 ms - 56
POST /v1/token 200 23.383 ms - 252
POST /v1/posts/hashtag/๋
ธ๋ 200 8.288 ms - 395
- ์ธ์ฆ๋ ์ฌ์ฉ์๋๋ผ๋ ๊ณผ๋ํ API ์ฌ์ฉ์ API ์๋ฒ์ ๋ฌด๋ฆฌ๊ฐ ๊ฐ๋ค.
→ ์ผ์ ๊ธฐ๊ฐ ๋ด์ API๋ฅผ ์ฌ์ฉํ ์ ์๋ ํ์๋ฅผ ์ ํํด ์๋ฒ์ ํธ๋ํฝ์ ์ค์ด๋ ๊ฒ์ด ์ข๋ค.
- ์ด๋ฌํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ express-rate-limit ํจํค์ง๋ฅผ nodebird-api ์๋ฒ์ ๋ค์ ํจํค์ง๋ฅผ ์ค์นํ๋ค.
์ฝ์
npm i express-rate-limit
- verifyToken ๋ฏธ๋ค์จ์ด ์๋์ apiLimiter ๋ฏธ๋ค์จ์ด์ deprecated ๋ฏธ๋ค์จ์ด๋ฅผ ์ถ๊ฐํ๋ค.
nodebird-api/middlewares/index.js
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
...
exports.apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1๋ถ
max: 1,
handler: function (req, res) {
res.status(this.statusCode).json({
code: this.statusCode, // ๊ธฐ๋ณธ๊ฐ 429
message: '1๋ถ์ ํ ๋ฒ๋ง ์์ฒญํ ์ ์์ต๋๋ค.'
});
}
});
exports.deprecated = (req, res) => {
res.status(410).json({
code: 410,
message: '์๋ก์ด ๋ฒ์ ์ด ๋์์ต๋๋ค. ์๋ก์ด ๋ฒ์ ์ ์ฌ์ฉํ์ธ์.'
});
};
- apiLimiter ๋ฏธ๋ค์จ์ด๋ฅผ ๋ผ์ฐํฐ์ ๋ฃ์ผ๋ฉด ๋ผ์ฐํฐ์ ์ฌ์ฉ๋ ์ ํ์ด ๊ฑธ๋ฆฐ๋ค.
- 1๋ถ์ ํ ๋ฒ ํธ์ถ ๊ฐ๋ฅํ๊ณ , ์ฌ์ฉ๋ ์ ํ์ ์ด๊ณผํ๋ฉด 429 ์ํ์ฝ๋์ ํจ๊ป ํ์ฉ๋์ ์ด๊ณผํ๋ค๋ ์๋ต์ ์ ์กํ๋ค.
- deprecated ๋ฏธ๋ค์จ์ด๋ ์ฌ์ฉํ๋ฉด ์๋๋ ๋ผ์ฐํฐ์ ๋ถ์ธ๋ค.
- 410 ์ฝ๋์ ํจ๊ป ์๋ก์ด ๋ฒ์ ์ ์ฌ์ฉํ๋ผ๋ ๋ฉ์์ง๋ฅผ ์๋ตํ๋ค.
- ์ฌ์ฉ๋ ์ ํ์ด ์ถ๊ฐ๋์์ผ๋ฏ๋ก ๊ธฐ์กด API ๋ฒ์ ๊ณผ ํธํ๋์ง ์๋๋ค.
- ์๋ก์ด ๋ผ์ฐํฐ v2๋ ๊ธฐ์กด v1๊ณผ ๊ธฐ๋ณธ์ ์ผ๋ก ๋์ผํ๋ฏ๋ก ์ผ๋ถ๋ง ์์ ๋๋ค.
nodebird-api/routes/v2.js
const express = require('express');
const { verifyToken, apiLimiter} = require('../middlewares');
const { createToken, tokenTest, getMyPosts, get PostsByHashtag}= require(' ../controllers/v2');
const router = express.Router;
// POST /v2/token
router.post('/token', apilimiter, createToken);
// POST /v2/test
router.get('/test', apilimiter, verifyToken, tokenTest);
// GET /v2/posts/my
router.get('/posts/my', apiLimiter, verifyToke n, getMyPosts);
// GET /v2/posts/hashtag/:title
router.get('/posts/hashtag/:title', apiLimiter, verifyToken, getPostsByHashtag);
module.exports=router;
nodebird-api/controllers/v2.js
...
exports.createToken = async (req, res) => {
const { clientSecret } = req.body;
try {
const domain = await Domain.findOne({
where: { clientSecret },
include: {
model: User,
attributes: ['nick', 'id'], // 'attribute'๋ฅผ 'attributes'๋ก ์์
},
});
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: '30m', // 30๋ถ
issuer: 'nodebird',
});
return res.json({
code: 200,
message: 'ํ ํฐ์ด ๋ฐ๊ธ๋์์ต๋๋ค',
token,
});
} catch (error) {
console.error(error);
return res.status(500).json({
code: 500,
message: '์๋ฒ ์๋ฌ',
});
}
};
...
- ํ ํฐ ์ ํจ๊ธฐ๊ฐ์ 30๋ถ์ผ๋ก ๋๋ฆฌ๊ณ ๋ผ์ฐํฐ์ ์ฌ์ฉ๋ ์ ํ ๋ฏธ๋ค์จ์ด๋ฅผ ์ถ๊ฐํ๋ค.
- ๊ธฐ์กด v1 ๋ผ์ฐํฐ๋ฅผ ์ฌ์ฉํ ๋๋ ๊ฒฝ๊ณ ๋ฉ์์ง๋ฅผ ๋์์ค๋ค.
nodebird-api/routes/v1.js
const express = require('express');
const { verifyToken, deprecated } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v1');
const router = express.Router();
router.use(deprecated);
//POST /v1/token
router.post('/token', createToken);
...
- ๋ผ์ฐํฐ ์์ deprecated ๋ฏธ๋ค์จ์ด๋ฅผ ์ถ๊ฐํด v1์ผ๋ก ์ ๊ทผํ ๋ชจ๋ ์์ฒญ์ deprecated ์๋ต์ ๋ณด๋ด๋๋ก ํ๋ค.
- ์๋ก ๋ง๋ ๋ผ์ฐํฐ๋ฅผ ์๋ฒ์ ์ฐ๊ฒฐํ๋ค.
nodebird-api/app.js
...
const v1 = require('./routes/v1');
const v2 = require('./routes/v2');
const authRouter = require('./routes/auth');
...
app.use('/v1', v1);
app.use('/v2', v2);
app.use('/auth', authRouter);
...
- ์ฌ์ฉ์ ์ ์ฅ(NodeCat)์ผ๋ก ๋์์์ ์๋ก ์๊ธด ๋ฒ์ ์ ํธ์ถํ๋ค.
nodecat/.env
...
API_URL=http://localhost:8002/v2
...
- v2๋ก ๋ฐ๊พธ์ง ์๊ณ v1์ ๊ณ์ ์ฌ์ฉํ๋ค๋ฉด 410 ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
- 1๋ถ์ ํ ๋ฒ๋ณด๋ค ๋ ๋ง์ด API๋ฅผ ํธ์ถํ๋ฉด 429 ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
- NodeCat์ด nodebird-api๋ฅผ ํธ์ถํ๋ ๊ฒ์ ์๋ฒ์์ ์๋ฒ๋ก API๋ฅผ ํธ์ถํ ๊ฒ์ด๋ค.
โ NodeCat์ ํ๋ฐํธ์์ nodebird-api์ ์๋ฒ API๋ฅผ ํธ์ถ๋๋ค๋ฉด?
nodecat/routes/index.js
const express = require('express');
const { searchByHashtag, getMyPosts, renderMain } = require('../controllers');
const router = express.Router();
router.get('/myposts', getMyPosts);
router.get('/search/:hashtag', searchByHashtag);
router.get('/', renderMain);
module.exports = router;
nodecat/controllers/index.js
..
exports.renderMain = (req, res) => {
res.render('main', { key: process.env.CLIENT_SECRET });
};
โก ํ๋ฐํธ ํ๋ฉด๋ ์ถ๊ฐํ๋ค.
nodecat/views/main.html
<!DOCTYPE html>
<html>
<head>
<title>ํ๋ฐํธ API ์์ฒญ</title>
</head>
<body>
<div id="result"></div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.post('http://localhost:8002/v2/token', {clientSecret: '{{key}}',})
//{{key}}๋ถ๋ถ์ด ๋์ ์ค์ ์ํด ์ค์ ํค๋ก ์นํ๋์ด ๋ ๋๋ง ๋๋ค.
.then((res) => {
document.querySelector('#result').textContent = JSON.stringify(res.data);
})
.catch((err) => {
console.error(err);
});
</script>
</body>
</html>
- ์ค์ ์๋น์ค์์๋ ์๋ฒ์์ ์ฌ์ฉํ๋ ๋น๋ฐ ํค์ ํ๋ฐํธ์์ ์ฌ์ฉํ๋ ๋น๋ฐ ํค๋ฅผ ๋ฐ๋ก ๋๋ ๊ฒ์ด ์ข๋ค.
→ clientSecret ์ธ์ frontSecret ๊ฐ์ ์ปฌ๋ผ์ ์ถ๊ฐํด ๋ฐ๋ก ๊ด๋ฆฌํ๋ ๊ฒ์ ๊ถ์ฅํ๋ค.
โข CORS ์๋ฌ
- https://localhost:4000์ ์ ์ํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉฐ ์ ๋๋ก ๋์ํ์ง ์๋๋ค.
- Access-Control-Allow-Origin ์๋ฌ: ํค๋๊ฐ ์๋ค๋ ๋ด์ฉ์ ์๋ฌ
- ๋ธ๋ผ์ฐ์ ์ ์๋ฒ์ ๋๋ฉ์ธ์ด ์ผ์นํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ์์ฒญ์ด ์ฐจ๋จ๋๋ค.
- CORS ๋ฌธ์ : ํ์ฌ ์์ฒญ์ ๋ณด๋ด๋ ํด๋ผ์ด์ธํธ(localhost:4000)์ ์์ฒญ์ ๋ฐ๋ ์๋ฒ(localhost:8002)์ ๋๋ฉ์ธ์ด ๋ฌ๋ผ ๋ฐ์ํ๋ ๋ฌธ์
- Network ํญ์๋ ๋ณด๋ธ ์ ์๋ OPTIONS ๋ฉ์๋๊ฐ ํ์๋๊ณ , POST ์์ฒญ์ CORS error ์คํจํ๋ค๊ณ ๋ฌ๋ค.
- OPTIONS ๋ฉ์๋๋ ์ค์ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ์ ์๋ฒ๊ฐ ์์ฒญ์ ๋๋ฉ์ธ, ํค๋์ ๋ฉ์๋ ๋ฑ์ ํ์ฉํ๋์ง ์ฒดํฌํ๋ ์ญํ ์ ํ
๋ค.
NodeBird API ์ฝ์
OPTIONS /v2/token 200 0.302 ms - 4
- NodeBird API ์๋ฒ ์ฝ์์๋ OPTIONS ์์ฒญ์ด ๊ธฐ๋ก๋๋ค.
โฃ CORS ๋ฌธ์ ํด๊ฒฐํ๊ธฐ
- ์๋ต ํค๋์ ํด๋ผ์ด์ธํธ ๋๋ฉ์ธ์ ์์ฒญ์ ํ๋ฝํ๋ค๋ ๋ป์ธ Access-Control-Allow-Origin ํค๋๋ฅผ ๋ฃ์ด์ผํ๋ค.
- ์๋ต ํค๋๋ฅผ ์กฐ์ํ๋ ค๋ฉด ์๋ต์ API ์๋ฒ๊ฐ ๋ณด๋ด๋ ๊ฒ์ด๊ธฐ์ NodeBird API ์๋ฒ์์ ๋ฐ๊ฟ์ผํ๋ค.
→ NodeBird API์ cors ๋ชจ๋์ ์ค์นํ๋ฉด ๋๋ค.
$ npm i cors
- cors ํจํค์ง ์ค์น ํ v2.js์ ์ ์ฉํ๋ค.
nodebird-api/routes/v2.js
const express = require('express');
const cors = require('cors');
const { verifyToken, apiLimiter } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v2');
const router = express.Router();
router.use(cors({
credentials: true, //๋ค๋ฅธ ๋๋ฉ์ธ ๊ฐ์ ์ฟ ํค๊ฐ ๊ณต์ ๋๋ค.
}));
...
- ์ด์ ์๋ต์ Access-Control-Allow-Origin ํค๋๊ฐ ์ถ๊ฐ๋์ด ๋๊ฐ๋ค.
- credentials: true๋ฅผ ํ์ฑํํด์ผ ๋ค๋ฅธ ๋๋ฉ์ธ ๊ฐ์ ์ฟ ํค๊ฐ ๊ณต์ ๋๋ค.
- credentials: true๋ฅผ ํ์ฑํํ์ง ์์ผ๋ฉด ํ๋ฐํธ์ ์๋ฒ์ ๋๋ฉ์ธ์ด ๋ค๋ฅธ ๊ฒฝ์ฐ ๋ก๊ทธ์ธ์ด ๋์ง ์์ ์ ์๋ค.
- https://localhost:4000์ ์ ์ํ๋ฉด ํ ํฐ์ด ๋ฐ๊ธ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
- ์ด ํ ํฐ์ ์ฌ์ฉํด ๋ค๋ฅธ API ์์ฒญ์ ๋ณด๋ด๋ฉด ๋๋ค.
- Access-Control-Allow-Origin:*์ ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ ํ์ฉํ๋ค๋ ๋ป์ด๋ค.
- credentials:true ์ต์ ์ Access-Control-Allow-Credentials ํค๋๋ฅผ true๋ก ๋ง๋ ๋ค.
→ ์์ฒญ์ ๋ณด๋ด๋ ์ฃผ์ฒด๊ฐ ํด๋ผ์ด์ธํธ๋ผ์ ๋น๋ฐํค๊ฐ ๋ชจ๋์๊ฒ ๋ ธ์ถ๋๋ ์๋ก์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
CORS ์์ฒญ๋ ํ์ฉํ์ผ๋ฏ๋ก ์ด ๋น๋ฐ ํค๋ฅผ ๊ฐ์ง๊ณ ๋ค๋ฅธ ๋๋ฉ์ธ๋ค์ด API ์๋ฒ์ ์์ฒญ์ ๋ณด๋ผ ์ ์๋ค.
→ ๋น๋ฐ ํค ๋ฐ๊ธ ์ ํ์ฉํ ๋๋ฉ์ธ์ ์ ๊ฒ ํ์ผ๋ ํธ์คํธ์ ๋น๋ฐ ํค๊ฐ ๋ชจ๋ ์ผ์นํ ๋๋ง CORS๋ฅผ ํ์ฉํ๊ฒ ์์ ํ๋ค.
nodebird-api/routes/v2.js
const express = require('express');
const { verifyToken, apiLimiter, corsWhenDomainMatches } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v2');
const router = express.Router();
router.use(corsWhenDomainMatches);
// POST /v2/token
router.post('/token', apiLimiter, createToken);
...
nodebird-api/middlewares/index.js
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const { Domain } = require('../models');
...
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();
}
};
- ํด๋ผ์ด์ธํธ์ ๋๋ฉ์ธ(req,get('origin'))๊ณผ ํธ์คํธ๊ฐ ์ผ์นํ๋ ๊ฒ์ด ์๋์ง ๊ฒ์ฌํ๋ค.
- ์ผ์นํ๋ค๋ฉด CORS ํ์ฉํด ๋ค์ ๋ฏธ๋ค์จ์ด๋ก ๋ณด๋ด๊ณ , ์ผ์นํ์ง ์๋๋ค๋ฉด CORS ์์ด next๋ฅผ ํธ์ถํ๋ค.
- cors ๋ฏธ๋ค์จ์ด์ ์ธ์์ origin ์์ฑ์ ์ถ๊ฐํด ํ์ฉํ ๋๋ฉ์ธ๋ง ๋ฐ๋ก ์ ๋๋ค.
- ๋ค์ http://localhost:4000์ ์ ์ํ๋ฉด ์ฑ๊ณต์ ์ผ๋ก ํ ํฐ์ ๊ฐ์ ธ์จ๋ค.
- Access-Control-Allow-Origin์ด * ๋์ http://localhost:4000์ผ๋ก ์ ์ฉ๋์ด ์๋ค.
- ํน์ ๋๋ฉ์ธ๋ง ํ์ฉํ๋ฏ๋ก ํ์ฉ๋์ง ์์ ๋ค๋ฅธ ๋๋ฉ์ธ์์ ์์ฒญ์ ๋ณด๋ด๋ ๊ฒ์ ์ฐจ๋จํ ์ ์๋ค.
1. ๋ค๋ฅธ ์ดํ๋ฆฌ์ผ์ด์ ์์ ํ์ฌ ํ๋ก๊ทธ๋จ์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๊ฒ ํ์ฉํ๋ ์ ์ ์ ( )๋ผ๊ณ ํ๋ค.
2. JWT๋ ( ) ํ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ํ ํฐ์ด๋ค.
3. JWT ํ ํฐ์( )๋ฅผ ์์ง ์๋ ์ด์ ๋ณ์กฐ๊ฐ ๋ถ๊ฐ๋ฅํ๊ณ , ๋ณ์กฐํ ํ ํฐ์ ( )๋ฅผ ํตํด ๊ฒ์ฌํ ๋ ๊ตฌ๋ณํ ์ ์๋ค.
4. API ์ฌ์ฉํ๋ ์๋น์ค๋ ๋ค๋ฅธ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ฏ๋ก ( ) ์ญํ ์ ํ๋ค.
5. ( )๋ฏธ๋ค์จ์ด๋ฅผ ๋ผ์ฐํฐ์ ๋ฃ์ผ๋ฉด ๋ผ์ฐํฐ์ ์ฌ์ฉ๋ ์ ํ์ด ๊ฑธ๋ฆฐ๋ค.
6. ํ์ฌ ์์ฒญ์ ๋ณด๋ด๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ ๋ฐ๋ ์๋ฒ์ ๋๋ฉ์ธ์ด ๋ฌ๋ผ ๋ฐ์ํ๋ ๋ฌธ์ ๋ฅผ ( )๋ฌธ์ ๋ผ๊ณ ํ๋ค.
7. ( ) ์ต์ ์ Access-Control-Allow-Credentials ํค๋๋ฅผ true๋ก ๋ง๋ ๋ค.
8. ๋ค์ ์ฃผ์์ ์ฐธ๊ณ ํ์ฌ ์ฝ๋์ ๋น์นธ์ ์ฑ์ฐ์์ค.
const express = require('express');
const { verifyToken } = require('../middlewares');
const { createToken, tokenTest } = require('../controllers/v1');
const router = express.Router();
// POST /v1/token: ํ ํฐ์ ๋ฐ๊ธํ๋ ๋ผ์ฐํฐ
router.______(________, _________);
// GET /v1/test: ์ฌ์ฉ์๊ฐ ํ ํฐ์ ํ
์คํธํด๋ณผ ์ ์๋ ๋ผ์ฐํฐ
router.______(________, _________);
module.exports = router;
9. ๋ค์ ์ฃผ์์ ์ฐธ๊ณ ํ์ฌ ์ฝ๋์ ๋น์นธ์ ์ฑ์ฐ์์ค.
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
...
exports.apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1๋ถ
max: 1,
handler: function (req, res) {
res.status(_________).json({
code: __________, // ๊ธฐ๋ณธ๊ฐ 429
message: '1๋ถ์ ํ ๋ฒ๋ง ์์ฒญํ ์ ์์ต๋๋ค.'
});
}
});
exports.deprecated = (req, res) => {
res.status(______).json({ //์๋ฌ์ฝ๋ 401
code: ______,
message: '์๋ก์ด ๋ฒ์ ์ด ๋์์ต๋๋ค. ์๋ก์ด ๋ฒ์ ์ ์ฌ์ฉํ์ธ์.'
});
};
1. API
2. JSON
3. JWT ๋น๋ฐ ํค, ์๊ทธ๋์ฒ
4. ํด๋ผ์ด์ธํธ
5. apiLimiter
6. CORS
7. credential:true
8.
const express = require('express');
const { verifyToken } = require('../middlewares');
const { createToken, tokenTest } = require('../controllers/v1');
const router = express.Router();
// POST /v1/token: ํ ํฐ์ ๋ฐ๊ธํ๋ ๋ผ์ฐํฐ
router.post('/token', createToken);
// GET /v1/test: ์ฌ์ฉ์๊ฐ ํ ํฐ์ ํ
์คํธํด๋ณผ ์ ์๋ ๋ผ์ฐํฐ
router.get('/test', verifyToken, tokenTest);
module.exports = router;
9.
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
...
exports.apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1๋ถ
max: 1,
handler: function (req, res) {
res.status(this.statusCode).json({
code: this.statusCode, // ๊ธฐ๋ณธ๊ฐ 429
message: '1๋ถ์ ํ ๋ฒ๋ง ์์ฒญํ ์ ์์ต๋๋ค.'
});
}
});
exports.deprecated = (req, res) => {
res.status(410).json({
code: 410,
message: '์๋ก์ด ๋ฒ์ ์ด ๋์์ต๋๋ค. ์๋ก์ด ๋ฒ์ ์ ์ฌ์ฉํ์ธ์.'
});
};
์ถ์ฒ) ์กฐํ์ , ใNode.js ๊ต๊ณผ์ ๊ฐ์ 3ํใ, ๊ธธ๋ฒ(2022), 10์ฅ
Corner node.js 2ํ
Editor : Sullivan
[๋ ธ๋ 2ํ] #15. AWS์ GCP๋ก ๋ฐฐํฌํ๊ธฐ (0) | 2025.01.24 |
---|---|
[๋ ธ๋ 2ํ] #12. ์น ์์ผ์ผ๋ก ์ค์๊ฐ ๋ฐ์ดํฐ ์ ์กํ๊ธฐ (0) | 2025.01.17 |
[๋ ธ๋ 2ํ] #10. ์ต์คํ๋ ์ค๋ก SNS ์๋น์ค ๋ง๋ค๊ธฐ (0) | 2025.01.03 |
[๋ ธ๋ 2ํ] #9. ๋ชฝ๊ณ ๋๋น (3) | 2024.12.27 |
[๋ ธ๋ 2ํ] #8. MySQL (3) | 2024.11.29 |