흰 스타렉스에서 내가 내리지

배포 본문

Node.js

배포

주씨. 2022. 6. 12. 19:03
728x90

 

 

morgan을 배포용으로

// app.js

if (process.env.NODE_ENV === "production") {
  app.use(morgan("combined"));
} else {
  app.use(morgan("dev"));
}

combined 모드는 dev 모드에 비해 더 많은 사용자 정보를 로그로 남기므로 추후 버그를 해결할 때 더 유용하게 사용할 수 있다. 

process.env.NODE_ENV는 .env에 넣을 수 없다. 개발, 배포환경에 따라 값이 변해야 하는데, .env 파일은 정적 파일이기 떄문.

NODE_ENV를 동적으로 바꾸는 방법은 cross-env를 이용한다.

 

 

express-session을 배포용으로

// app.js

const sessionOption = {
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
};
if (process.env.NODE_ENV === "production") {
  sessionOption.proxy = true;
  // sessionOption.cookie.secure = true;
}

app.use(session(sessionOption));

배포 환경일 때는 proxy와 cookie.secure를 true로 바꾼다.

하지만 무조건 이렇게 해야하는 것은 아니다.

proxy를 true로 적용해야 하는 경우는 https 적용을 위해 노드 서버 앞에 다른 서버를 두었을 때다.

cookie.secure도 https를 적용할 때만 true로 바꾼다. 

 

 

 

데이터베이스를 배포환경으로 

현재 config.json에 비밀번호가 하드코딩 되어있다. 

JSON 파일이라 변수를 사용할 수 없으므로, JS파일로 변경하자.

// config/config.js

require("dotenv").config();

module.exports = {
  development: {
    username: "root",
    password: process.env.SEQUELIZE_PASSWORD,
    database: "nodebird",
    host: "127.0.0.1",
    dialect: "mysql",
  },
  test: {
    username: "root",
    password: process.env.SEQUELIZE_PASSWORD,
    database: "nodebird_test",
    host: "127.0.0.1",
    dialect: "mysql",
  },
  production: {
    username: "root",
    password: process.env.SEQUELIZE_PASSWORD,
    database: "nodebird",
    host: "127.0.0.1",
    dialect: "mysql",
    logging: false,
  },
};

이어서 .env에 SEQUELIZE_PASSWORD 를 추가해준다.

 

 

 

cross-env 패키지를 사용하면 동적으로 process.env를 변경할 수 있다.

npm i cross-env
  // package.json
  
  
  "scripts": {
    "test": "jest",
    "start": "cross-env NODE_ENV=production PORT=80 node server",
    "dev": "nodemon server",
    "coverage": "jest --coverage"
  },

npm start는 배포 환경에서 사용되는 스크립트이고,

npm run dev는 개발 환경에서 사용하는 스크립트이다.

argv 형태로 동적으로 process.env.NODE_ENV와 process.env.PORT를 전달해준다고 생각하면 된다.

 

 

 

 

npm i sanitize-html

XSS(Cross Site Scripting) 공격을 막기 위한 패키지이다.

XSS는 사이트에 스크립트를 삽입하는 공격이다.

서버에서는 사용자가 게시글을 업로드할 때 스크립트가 포함되어 있는지 검사해서, 존재한다면 제거해야 한다.

import sanitizeHtml from 'sanitize-html';

const html = "<strong>hello world</strong>";
console.log(sanitizeHtml(html));

특정 태그를 허용하는 것처럼 다양한 옵션들이 존재하는데, 자세한 내용은 npm 공식 문서를 보도록 하자

 

 

 

npm i csurf
const csurf = require("csurf");
const csrfProtection = csrf({ cookie: true });

app.get("/form", csrfProtection, (req, res, next) => {
  res.render("csrf", { csrfToken: req.csrfToken() });
});

app.post("/form", csrfProtection, (req, res, next) => {
  res.send("ok");
});

CSRF(Cross-Site Request Forgery)는 사용자가 의도치 않게 공격자가 의도한 행동을 하게 만드는 공격이다. 

예를 들어 특정 페이지에 방문할 때 저절로 로그아웃되거나, 게시글이 써지는 현상을 유도할 수 있다. 

심지어 은행과 같은 사이트에서는 다른 사람에게 송금하는 행동을 넣는 등 상황에 따라 크게 악용할 수 있다. 

이 공격을 막으려면 내가 한 행동이 내가 한 것이 맞다는 점을 인증해야 한다. 

이때 CSRF 토큰이 사용되고, csurf 패키지는 이 토큰을 쉽게 발급하거나 검증할 수 있도록 돕는다.

 

익스프레스의 미들웨어 형식으로 동작하며 form 같은 것을 렌더링할 때 CSRF 토큰을 같이 제공한다. 

현재 cookie를 사용하는 것으로 옵션을 설정했으므로 cookie-parser 패키지도 연결되어 있어야 한다.

토큰은 req.csrfToken()으로 가져올 수 있다. 

프런트엔드에 렌더링된 CSRF 토큰을 나중에 form을 제출할 때 데이터와 함께 제출하면 된다. 상황에 따라 CSRF 토큰을 적용하는 방법이 다른데, 이는 공식 문서를 함고하자.

 

 

 

pm2

npm i pm2
  // package.json
  
  
  "scripts": {
    "test": "jest",
    "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js",
    "dev": "nodemon server",
    "coverage": "jest --coverage"
  },

배포용 nodemon 이라고 생각하면 된다. 자세한 내용은 다른 pm2 게시글에 업로드하겠다.

 

 

 

winston

npm i winston

스프링의 log4j 랑 비슷한거라고 생각하면 된다. 로그를 찍어준다.

// logger.js

const { createLogger, format, transports } = require("winston");

const logger = createLogger({
  level: "info",
  format: format.json(),
  transports: [
    new transports.File({ filename: "combined.log" }),
    new transports.File({ filename: "error.log", level: "error" }),
  ],
});

if (process.env.NODE_ENV !== "production") {
  logger.add(new transports.Console({ format: format.simple() }));
}

module.exports = logger;
// app.js

...
...
const logger = require("./logger");

...
...
app.use((req, res, next) => {
  const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  logger.info("hello");	// 추가 
  logger.error(error.message);	// 추가
  next(error);
});

...
...

자세한건 winston 게시글에 업로드하겠다.

 

 

 

 

helmet hpp

npm i  helmet hpp

서버의 각종 취약점을 보완해준다. 

// app.js

...

const helmet = require("helmet");
const hpp = require("hpp");

...
...

if (process.env.NODE_ENV === "production") {
  app.use(morgan("combined"));
  app.use(helmet({ contentSecurityPolicy: false }));
  app.use(hpp());
} else {
  app.use(morgan("dev"));
}

...
...

 

 

 

connect-redis