React 26장 - 로그인 구현하기 (1)
포스트
취소

React 26장 - 로그인 구현하기 (1)

React

자료구조

  • client
    • pages
      • Login.js
      • Mypage.js
    • src
      • App.js
      • index.js
  • server
    • controllers
      • users
        • login.js
        • logout.js
        • userInfo.js
      • index.js
    • db
      • data.js
    • index.js

서버 만들기

server/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const express = require("express");
const cors = require("cors");
const logger = require("morgan");
const cookieParser = require("cookie-parser");
const fs = require("fs");
const https = require("https");
const controllers = require("./controllers");
const app = require();

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

const HTTPS_PORT = process.env.HTTPS_PORT || 4000;

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoder({ extended: false }));
app.use(cookieParser());

const corsOptions = {
  origin: "http://localhost:3000",
  credentials: true,
  methods: ["GET", "POST", "OPTION"],
};

app.use(cors(corsOptions));

app.post("/login", controllers.login);
app.post("/logout", controllers.logout);
app.get("/userInfo", controllers.userInfo);

let server;
if (fs.existsSync("./key.pem") && fs.existsSync("./cert.pem")) {
  const privateKey = fs.readFileSync(__dirname + "/key.pem", "utf8");
  const certificate = fs.readFileSync(__dirname + "/cert.pem", "utf8");
  const credentials = {
    key: privateKey,
    cert: certificate,
  };

  server = https.createServer(credentials, app);
  server.listen(HTTPS_PORT, () =>
    console.log(`HTTPS Server is starting on ${HTTPS_PORT}`)
  );
} else {
  server = app.listen(HTTPS_PORT, () =>
    console.log(`HTTP Server is starting on ${HTTP_PORT}`)
  );
}
module.exports = server;
  • {express, cors, logger, cookieParser, fs, https} 모듈을 사용한다.
    • express
      • Node.js 기반의 서버를 만드는 데 사용한다.
      • HTTP 요청 및 응답 처리를 위한 다양한 기능을 제공한다.
    • CORS (Cross-Origin Resource Sharing)
      • 다른 도메인의 리소스를 요청하는 것을 제한한다.
      • 다른 도메인의 리소스에 대한 접근을 허용하도록 설정할 수 있다.
    • Logger
      • 서버에서 발생하는 로그를 기록하고 분석한다.
      • 서버의 문제를 파악하고 해결하는 데 도움이 된다.
    • CookieParse
      • HTTP 요청에서 쿠키를 파싱하고, 요청 객체의 cookie 속성에 쿠키 정보를 저장한다.
      • 서버에서 쿠키를 쉽게 다룰 수 있다.
    • fs(File System)
      • Node.js의 내장 모듈 중 하나로, 파일 시스템에 접근하여 파일을 읽거나 쓰는 등의 작업을 수행한다.
      • 파일 시스템을 다루는 작업에서 유용하게 사용한다.
    • HTTPS
      • HTTP 프로토콜을 보안한 버전으로 모듈로 사용할 수 있다.
  • process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
    • HTTPS 프로토콜을 사용할 때 SSL 인증서의 유효성 검사를 비활성한다.
    • 서버와 클라이언트 간의 통신을 암호화하고 보안을 강화하는 역할을 한다.
    • SSL 인증서의 유효성 검사를 비활성화하면 HTTPS 프로토콜을 사용할 수 있다.
    • 보안성을 고려하여 개발 환경에서만 사용하고 실제 서비스에서는 인증서 검증을 비활성화 하지 않도록 주의해야 한다.
  • const HTTPS_PORT = process.env.HTTPS_PORT || 4000
    • HTTPS 프로토콜을 사용할 포트 번호를 지정한다.
    • 환경 변수로 HTTPS_PORT가 정의되어 있으면 그 값을 변수에 할당하고, 그렇지 않으면 4000을 할당한다.
  • app.use()
    • Express 애플리케이션 객체에 미들웨어 함수를 등록하는 메서드이다.
    • 따라서, 객체에 등록된 미들웨어 함수는 애플리케이션에서 발생하는 모든 요청에 대해 실행된다.
      • logger("dev") : 개발 시 로깅을 위한 미들웨어 함수
      • express.json() : 클라이언트로부터 전송된 JSON 형태의 데이터를 파싱하기 위한 미들웨어 함수
      • express.urlencoded({extended:false}) : 클라이언트로부터 전송된 URL-encoded 데이터를 파싱하기 위한 미들웨어 함수
      • cookieParser() : 클라이언트로부터 전송된 쿠키를 파싱하기 위한 미들웨어 함수
      • cors(corsOptions) : CORS를 허용하기 위한 미들웨어 함수, corsOptions는 옵션 객체로 클라이언트 도메인, 메서드, 쿠키 등을 설정한다.
  • credentials
    • 서버에 대한 인증서와 키를 저장하는 객체이다.
    • 인증서는 클라이언트가 서버와 통신할 때, 서버가 신뢰할 수 있는지 확인할 수 있다.
  • app.post
    • Express 애플리케이션 객체에 HTTP POST 요청을 처리한다.
    • cotrollsers로 정의된 모듈을 불러와서 POST 요청을 처리한다.
  • fs.existsSync("./key.pem")&&fs.existsSync("./cert.pem")
    • 디텍토리 ./에 key.pem과 cert.pem이 있을 경우에 HTTPS 프로토콜을 사용하여 서버를 실행한다.
  • fs.readFileSync(__dirname + "./key.pem", "utf8")
    • 함수를 사용하여 key.pem파일을 읽는다.
    • __dirname은 현재 모듈의 디렉토리 경로를 나타내는 Node.js 전역 변ㅅ누이다.
  • const credentials = {key:privateKey, cert: certificate}
    • 변수에 저장된 값을 이용하여 객체를 생성한다.
    • HTTPS 서버를 생성할 때 인증서와 비밀키를 제공하기 위해 사용된다.
  • server.listen(HTTPS_PORT,() => {...})
    • if문에서 key.pem과 cert.pem 파일이 존재하는 경우에는 https 서버를 만든다.
    • HTTPS 포트에서 서버를 시작한다.
  • app.listen(HTTPS_PORT,() => {...})
    • 존재하지 않을 경우 HTTP 포트에서 서버를 시작한다.

server/users/login.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const { USER_DATA } = require("../../db/data");

module.exports = (req, res) => {
  const { userId, password } = req.body.loginInfo;
  const { checkedKeepLogin } = req.body;
  const userInfo = {
    ...USER_DATA.filter(
      (user) => user.userId === userId && user.password === password
    )[0],
  };
  const cookiesOption = {
    domain: "localhost",
    path: "/",
    httpOnly: true,
    sameSite: "none",
    secure: true,
  };

  if (userInfo.id === undefined) {
    res.status(401).send("Not Authorized");
  } else if (checkedKeepLogin === true) {
    cookiesOption.maxAge = 1000 * 60 * 30;
    cookiesOption.expires = new Date(Date.now() + 1000 * 60 * 30);
    res.cookie("cookieId", userInfo.id, cookiesOption);
    res.redirect("/userinfo");
  } else {
    res.cookie("cookieId", userInfo.id, cookiesOption);
    res.redirect("/userinfo");
  }
};
  • user/index.js에서 모듈을 사용하는데, login.js모듈을 만든다.
  • 이렇게 작성된 모듈은 server/index.js에서 사용하게 된다.
  • const {userId, password} = req.body.loginInfo
    • POST 요청으로 전송된 데이터에서 loginInfo 프로퍼티의 값을 의미한다.
    • POST 요청으로 loginInfo 객체가 전달되며, 이 객체는 우리가 client 서버에서 입력한 값이다. (client/src/pages/Login.js)
  • const {checkedKeepLogin} = req.body
    • 마찬가지로 Login.js에서 실행시킨 checkedKeepLogin 값을 의미한다.
  • const userInfo = {...USER_DATA.filter(...)}
    • userInfo라는 변수에 db/data.js에서 받아온 데이터 값에서, 우리가 작성한 userId(req.body.loginInfo)를 비교하여, 아이디와 비밀번호가 일치하는 배열을 새로 담는다.
  • const cookiesOption = {domain: ..., path: ...}
    • 새로 생성할 쿠키에 대한 옵션을 담고 있다.
    • domain : 쿠키가 전송될 도메인을 설정한다.
      • 로컬로 설정하였기 때문에 로컬 개발환경에서 쿠키를 사용한다.
    • path : 쿠키가 전송될 URL 경로를 설정한다.
      • /로 설정되어 있으므로, 서버의 루트 경로에서 쿠키를 사용할 수 있다.
    • httpOnly : 옵션이 true로 설정되어 있으면, 자바스크립트로 쿠키에 접근할 수 없도록 설정하며, 보안 상 권장된다.
    • sameSite : CSRF 공격을 방어하며, none으로 설정될 경우 secure 옵션과 함께 설정되어야 한다.
    • secure : HTTPS 프로토콜을 사용하는 경우에만 쿠키를 전송될 수 있도록 한다.
  • (userInfo.id === undefined){res.status(401)...}
    • 우리가 입력한 id와 기존의 값이 일치하지 않는다면 401에러를 출력한다.
  • (checkedKeepLogin === true){cookiesOption.maxAge = ...}
    • 만약 자동로그인을 선택했을 경우 쿠키를 전송하는데, maxAge는 쿠키의 유효을 설정하는데 위 경우에선 30분동안 쿠키가 유지된다.
    • expires는 쿠키가 만료될 시간을 설정하는데, 현재의 시간에 30분을 더한 시간으로 설정된다.
    • cookie() 메서드를 이용하여 쿠키를 생성하고, 첫 번째 인자로 쿠키의 이름을, 두 번째 인자로 쿠키에 담을 값(userInfo.id)을, 세 번째 인자로 쿠키의 설정을 전달한다.
    • redirect() 메서드를 이용하여 쿠키를 생성 후 페이지를 리 다이렉트한다.
    • 생성한 쿠키는 HTTP 헤더에 포함된다.

server/controllers/users/logout.js

1
2
3
4
5
6
7
8
9
10
11
module.exports = (req, res) => {
  const cookiesOption = {
    domain: "localhost",
    path: "/",
    httpOnly: true,
    sameSite: "none",
    secure: true,
  };

  res.status(205).clearCookie("cookieId", cookiesOption).send("logout");
};
  • 로그아웃 쿠키의 옵션을 지정한다.
    • 코드 분석은 위의 login.js 참조
  • clearCookie("cookieId",cookiesOption)
    • 쿠키를 클라이언트에서 삭제하는 역할을 하며, 지정된 cookiesOption 옵션을 적용한다.
  • 마지막으로 로그아웃 메세지를 보낸다.

server/controllers/users/userInfo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { USER_DATA } = require("../../db/data");

module.exports = (req, res) => {
  const cookieId = req.cookies.cookieId;
  const userInfo = {
    ...USER_DATA.filter((user) => user.id === cookieId)[0],
  };
  if (!cookieId || !userInfo.id) {
    res.status(401).send("Not Authorized");
  } else {
    delete userInfo.password;
    res.send(userInfo);
  }
};
  • const cookieId = req.cookies.cookieId
    • 요청을 받으면 쿠키에 입력된 cookieId를 가져온다.
  • const userInfo = {...USER_DATA.filter((user) => user.id == cookieId)[0]}
    • 기존의 데이터에 담겨있는 유저 아이디와 cookieId를 비교하여 새로운 배열을 만든다.
  • !cookieId || !userInfo.id
    • 만약 쿠키가 없거나 기존 데이터에의 id가 없는 경우 401 에러를 보낸다.
  • else
    • 만약 위 쿠키와 id를 가지고 있는 경우에는 사용자의 정보를 보낸다.
    • password는 민감한 정보이기 때문에 삭제한다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.