React
자료구조
- client
- …
- pages
- Login.js
- Mypage.js
- src
- App.js
- index.js
- server
- controllers
- users
- login.js
- logout.js
- userInfo.js
- index.js
- users
- db
- data.js
- index.js
- controllers
서버 만들기
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 프로토콜을 보안한 버전으로 모듈로 사용할 수 있다.
- express
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는 민감한 정보이기 때문에 삭제한다.