express-session
은 따로 세션 저장소를 지정하지 않는 이상
인메모리 방식으로 동작합니다.
즉, 서버의 메모리에 세션 정보를 를 저장합니다.
서버를 구동하는 컴퓨터의 메모리에만 저장되기 때문에 프로세스가 종료되면
세션 기록이 휘발됩니다.
메모리의 휘발성 문제를 해결하고 기록을 남기기 위해서는 세션 저장소를 따로 만들어줘야 합니다.
connect-mongo 는 mongodb 기반 세션 저장소 입니다.
connect-mongo
를 사용해봅시다.
npm i connect-mongo
콘솔에서 위 명령어를 입력하여 설치하고,
server.js
파일에서 MongoStore
를 사용합니다.
//server.js
import session from "express-session";
import MongoStore from "connect-mongo"; // MongoStore 을 import
app.use(
session({
secret: "SECRET!!",
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: "db_url" }),
})
);
session({})
에서 store:MongoStore.create({mongoUrl: db_url})
로 설정해주면 지정해준 db 의 주소에 세션을 저장할 sessions 컬렉션
이 생성되고, 세션 정보가 이에 저장됩니다!
그럼, 지난번에 대충 넘어간 session 의 상세 설정을 다시 알아봅시다.
문서를 보면 resave
와 saveUninitialized
속성은 특정 경우가 아닌 이상 false
로 지정하길 권장합니다.
🔔 resave
는 세션이 modify
되지 않더라도 다시 저장할건지의 여부이고,
saveUninitialized
는 초기화되지 않은 세션을 세션 저장소에 저장할지의 여부입니다. 이 때, 초기화되지 않은 세션이란 새로 생성되었지만 modify
되지 않은 세션을 의미합니다.
저번에 두 속성을 true
로 했었는데, 이 뜻은
우리 사이트를 방문하는 모든 사용자들에게 쿠키를 주고 사용자에 대한 세션을 만들어주겠어요^^~ 그리고 딱히 세션에 변경사항이 없더라도 세션을 다시 저장하겠습니다❗
가 됩니다.
이는 매우 비효율적입니다❗❗
1000만명이 방문하면 1000만개의 세션을 만들어야 하나요??
회원가입도 로그인도 하지 않은 단순 방문자인데도요??
어떤 사용자가 1000만번 페이지를 새로고침하면 그때마다 변경 사항도 없는 세션을 또 다시 저장해야 할까요??
그럴 필요가 전혀 없습니다.
따라서, 이 두 속성은 false 로 설정하는 게 권장됩니다.
//server.js
import session from "express-session";
app.use(
session({
// ...
resave: false,
saveUninitialized: false,
})
);
cookie.maxAge
속성을 설정하여 쿠키가 유효한 시간을
밀리초 단위로 설정할 수 있습니다.
app.use(
session({
// ...
cookie: {
maxAge: 60000 // 1분 후 만료
}
})
);
그러면 설정된 시간 후 쿠키가 만료됩니다.
유저가 얼마동안 로그인할 수 있느냐... 를 설정하는 것으로,
현재 서버시간에 maxAge
를 더해서 만료가 되는 일자를 정합니다.
Expires Set-Cookie
속성을 계산할 때 사용합니다.
secret
이란 속성은 쿠키에 sign 을 하기 위해 사용되는 문자열입니다.
쿠키에 사인을 하다니?
더 말하자면 이 쿠키가 우리 서버에서 발급해 준 쿠키임을 인증하기 위함이라고 볼 수 있습니다.
이는 세션 하이재킹이라는 공격을 막기 위해서입니다.
세션 아이디를 사용하여 그 사용자임을 확인하므로, 다른 사람의 쿠키를 훔쳐서 세션 아이디를 가져가 그 사람인 척 할 수가 있습니다.
그를 막기 위해 secret
을 사용하여 암호화를 진행하고, 쿠키를 인증합니다.
따라서, 이 secret
문자열은 매우 추측하기 어려워야 하고, 공개되면 안됩니다!
쿠키 secret
문자열은 아무에게나 공개되면 안됩니다.
또, db 주소
를 하드코딩해서 매번 쓰는 것도 번거롭고 이 주소도 그대로 코드에 드러나면 안됩니다.
따라서 .env
를 만들어 secret
이나 db 주소
와 같은 값들을 변수화합니다.
.env
파일을 깃헙 등 공개된 장소에 올리지 않음으로서 정보를 안전하게 지키고, 유지보수하기 편리하게 만들 수 있습니다.
package.json
과 동일한 계층에 .env
파일을 생성합니다.
// .env 파일
COOKIE_SECRET=sldkDmflemlsmdflf35935sdfLKsNBlml
DB_URL=mongodb://localhost:27017/mydb
위와 같이 변수를 지정합니다.
.env
파일에 작성한 환경 변수는 process.env.KEY
형식으로 불러와
사용할 수 있습니다.
// secret 과 db 주소를 그대로 보이지 않게 함
app.use(
session({
secret: process.env.COOKIE_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: process.env.DB_URL }),
})
);
그런데, 그냥 process.env.DB_URL
이렇게 불러오면
undefined
로 나옵니다. 인식을 못 합니다.
그래서 dotenv
를 사용합니다.
dotenv
는 .env
에서 변수를 불러와 process.env
에 로드해줍니다.
패키지를 설치해줍시다.
dotenv
는 가능한한 빨리!! 프로그램에서 import & configure
되어야 합니다.
따라서 프로젝트가 시작하는 맨 처음 뿌리에서
require('dotenv').config()
를 하여 환경 변수를 process.env
에 로드해줍니다.
그런데 require('dotenv').config()
로 환경 변수를 불러오려면 환경 변수를 사용하는 모든 파일에서 require('dotenv').config()
를 실행해야 합니다.
매우 번거로우므로 ES6 를 사용하는 입장에서 import
를 사용합시다.
프로그램 트리의 맨 첫 파일에서 import "dotenv/config"
로 모듈을 포함하면 됩니다.
// init.js 파일
import "dotenv/config"
서버 실행을 위해 npm 이 실행하는 첫 파일인 init.js
에서만 import 해주면 됩니다.
사용 방법이 좀 새로운데?
import dotenv from 'dotenv' dotenv.config()
라고 사용하는 거 아닌가?
싶을 수 있지만, 공식 문서를 보면 위 처럼 실행했을 때 환경 변수가 undefined
로 나오게 됩니다.
위 문서를 보면 모듈과 import 에 대한 자세한 설명이 있습니다.
dotenv
가 인용하는 부분을 살펴보면,
import 선언을 포함한 모듈을 실행할 때, import 한 모듈들이 먼저 로드되고, 각 모듈의 내용이 실행된다. 이미 실행되었을 경우는 건너뛰면서...
– ES6 In Depth: Modules
라고 합니다. (자세한 내용은 원문 참고)
dotenv 문서에서 예시로 든 다음 코드를 봅시다.
// errorReporter.mjs
import { Client } from 'best-error-reporting-service'
export default new Client(process.env.API_KEY)
// index.mjs
import dotenv from 'dotenv'
dotenv.config()
import errorReporter from './errorReporter.mjs'
errorReporter.report(new Error('documented example'))
이 코드는 예상처럼 실행되지 않습니다.
process.env.API_KEY
의 결과가 undefined
로 나오기 때문입니다.
index.mjs
에서 dotenv
와 errorReporter.mjs
모듈을 import 하여 사용하고 있습니다.
코드를 보면,
errorReporter.mjs
파일에서 환경변수 사용index.mjs
파일에서 dotenv
사용, config
로 .env
에서 환경변수를 process.env
에 할당index.mjs
에서 errorReporter.mjs
모듈 사용index.mjs
에서 errorReporter.report(new Error('documented example'))
로 errorReporter.mjs
모듈의 환경변수를 사용한 Client 객체의 메서드 호출 ➡️ 환경변수 사용이렇게 됩니다.
쉽게 말하면 errorReporter.mjs
파일에서 환경변수를 사용하고, index.mjs
파일에서 dotenv
로 환경 변수를 로드하여 사용하려 합니다.
❗
위 코드가 제대로 동작하지 않았던 이유는 index.mjs
파일 내의 코드보다 import
한 모듈이 먼저 실행되기 때문입니다.
즉, dotenv.config()
가 index.mjs
에서 이뤄지기 때문에 index.mjs
가 import
한 모듈의 내용보다 나중에 처리됩니다. 따라서 .env
의 환경변수가 process.env
에 로드되기도 전에 errorReporter.mjs
에서 환경변수를 먼저 호출합니다. 그러므로 환경변수의 값이 undefined
가 되는 문제가 발생합니다.
문제를 해결하려면 아래와 같이 작성하면 된다고 합니다.
import 'dotenv/config'
import errorReporter from './errorReporter.mjs'
errorReporter.report(new Error('documented example'))
import
하는 동시에 config
로 로드해줍시다.