세션 저장소와 환경변수 관리

345·2023년 2월 6일
0

express-session 은 따로 세션 저장소를 지정하지 않는 이상
인메모리 방식으로 동작합니다.

즉, 서버의 메모리에 세션 정보를 를 저장합니다.
서버를 구동하는 컴퓨터의 메모리에만 저장되기 때문에 프로세스가 종료되면
세션 기록이 휘발됩니다.

메모리의 휘발성 문제를 해결하고 기록을 남기기 위해서는 세션 저장소를 따로 만들어줘야 합니다.

✅ 세션 저장소 connect-mongo

connect-mongo - npm

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

express-session - npm

문서를 보면 resavesaveUninitialized 속성은 특정 경우가 아닌 이상 false 로 지정하길 권장합니다.

🔔 resave 는 세션이 modify 되지 않더라도 다시 저장할건지의 여부이고,
saveUninitialized 는 초기화되지 않은 세션을 세션 저장소에 저장할지의 여부입니다. 이 때, 초기화되지 않은 세션이란 새로 생성되었지만 modify 되지 않은 세션을 의미합니다.

❗ 왜 false 로 해야하나?

저번에 두 속성을 true 로 했었는데, 이 뜻은

우리 사이트를 방문하는 모든 사용자들에게 쿠키를 주고 사용자에 대한 세션을 만들어주겠어요^^~ 그리고 딱히 세션에 변경사항이 없더라도 세션을 다시 저장하겠습니다❗

가 됩니다.

이는 매우 비효율적입니다❗❗

1000만명이 방문하면 1000만개의 세션을 만들어야 하나요??
회원가입도 로그인도 하지 않은 단순 방문자인데도요??

어떤 사용자가 1000만번 페이지를 새로고침하면 그때마다 변경 사항도 없는 세션을 또 다시 저장해야 할까요??

그럴 필요가 전혀 없습니다.
따라서, 이 두 속성은 false 로 설정하는 게 권장됩니다.

//server.js 
import session from "express-session";

app.use(
  session({
	// ...
    resave: false,
    saveUninitialized: false,
  })
);

cookie.maxAge

cookie.maxAge 속성을 설정하여 쿠키가 유효한 시간을
밀리초 단위로 설정할 수 있습니다.

app.use(
  session({
	// ...
    cookie: {
      maxAge: 60000 // 1분 후 만료
    }
  })
);

그러면 설정된 시간 후 쿠키가 만료됩니다.
유저가 얼마동안 로그인할 수 있느냐... 를 설정하는 것으로,
현재 서버시간에 maxAge 를 더해서 만료가 되는 일자를 정합니다.
Expires Set-Cookie 속성을 계산할 때 사용합니다.


secret

secret 이란 속성은 쿠키에 sign 을 하기 위해 사용되는 문자열입니다.

쿠키에 사인을 하다니?

더 말하자면 이 쿠키가 우리 서버에서 발급해 준 쿠키임을 인증하기 위함이라고 볼 수 있습니다.

이는 세션 하이재킹이라는 공격을 막기 위해서입니다.
세션 아이디를 사용하여 그 사용자임을 확인하므로, 다른 사람의 쿠키를 훔쳐서 세션 아이디를 가져가 그 사람인 척 할 수가 있습니다.

그를 막기 위해 secret 을 사용하여 암호화를 진행하고, 쿠키를 인증합니다.
따라서, 이 secret 문자열은 매우 추측하기 어려워야 하고, 공개되면 안됩니다!

🔥 env 변수화

쿠키 secret 문자열은 아무에게나 공개되면 안됩니다.
또, db 주소 를 하드코딩해서 매번 쓰는 것도 번거롭고 이 주소도 그대로 코드에 드러나면 안됩니다.

따라서 .env 를 만들어 secret 이나 db 주소 와 같은 값들을 변수화합니다.
.env 파일을 깃헙 등 공개된 장소에 올리지 않음으로서 정보를 안전하게 지키고, 유지보수하기 편리하게 만들 수 있습니다.


package.json 과 동일한 계층에 .env 파일을 생성합니다.

// .env 파일
COOKIE_SECRET=sldkDmflemlsmdflf35935sdfLKsNBlml
DB_URL=mongodb://localhost:27017/mydb

위와 같이 변수를 지정합니다.


✨ dotenv 적용하기

.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 - npm

dotenv.env 에서 변수를 불러와 process.env 에 로드해줍니다.
패키지를 설치해줍시다.


❗ ES6 모듈 import

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 로 나오게 됩니다.

ES6 In Depth: Modules

위 문서를 보면 모듈과 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 에서 dotenverrorReporter.mjs 모듈을 import 하여 사용하고 있습니다.
코드를 보면,

  1. errorReporter.mjs 파일에서 환경변수 사용
  2. index.mjs 파일에서 dotenv 사용, config.env 에서 환경변수를 process.env 에 할당
  3. index.mjs 에서 errorReporter.mjs 모듈 사용
  4. index.mjs 에서 errorReporter.report(new Error('documented example'))errorReporter.mjs 모듈의 환경변수를 사용한 Client 객체의 메서드 호출 ➡️ 환경변수 사용

이렇게 됩니다.

쉽게 말하면 errorReporter.mjs 파일에서 환경변수를 사용하고, index.mjs 파일에서 dotenv 로 환경 변수를 로드하여 사용하려 합니다.


위 코드가 제대로 동작하지 않았던 이유는 index.mjs 파일 내의 코드보다 import 한 모듈이 먼저 실행되기 때문입니다.

즉, dotenv.config()index.mjs 에서 이뤄지기 때문에 index.mjsimport 한 모듈의 내용보다 나중에 처리됩니다. 따라서 .env 의 환경변수가 process.env 에 로드되기도 전에 errorReporter.mjs 에서 환경변수를 먼저 호출합니다. 그러므로 환경변수의 값이 undefined 가 되는 문제가 발생합니다.


문제를 해결하려면 아래와 같이 작성하면 된다고 합니다.

import 'dotenv/config'
import errorReporter from './errorReporter.mjs'

errorReporter.report(new Error('documented example'))

import 하는 동시에 config 로 로드해줍시다.

profile
기록용 블로그 + 오류가 있을 수 있습니다🔥

0개의 댓글