그렇게 나는... n일간... 세션을 통해 안전한 로그인 방식을 구현하기 위해 고군분투 중이다... 능지 이슈로 인하여... 한 번에 머리 속에 안 들어오고, 이해를 못하는 내용은 들어도 '아닌데요? 저는 처음듣는데요?' 라는 식으로 뻗대는 내 가련한 뇌...
이런 뻔뻔한 뇌를 가졌지만, 최소한 여기에 써두면 '처음 봤다'는 말은 안 통하겠죠... 억장 와르르멘션. 이걸 왜 아직까지 붙잡고 있는걸까,,, 정말 tired,,,
express
에서 세션 사용하기Node.JS를 사용해서…? 기반으로…? express 서버를 구축해줄 것이다. (typescript 사용 중…? 사실 이런거 어떻게 쓴다고 말하는지 잘 모르겠어용…)
node.js와 express에서는 세션을 내장 모듈로 지원하지 않는다. 때문에 express-session을 따로 설치를 해주어야 한다!
npm i express-session
자세한 내용은 express-session
공식문서에도 나와있기 때문에 참고하면 좋을 것 같다.
하지만 여기서 끝이 아니죠? express-session 설치만으로는 세션의 기능을 완벽하게 사용할 수 없다. 왜냐하면 저장소를 사용하지 않기 때문이다. 때문에 공식 문서에 나와있는 여러가지 compatible session stores 중 하나를 선택하여 저장소로 사용해야 한다. compatible이 무슨 뜻인지 몰랐습니다... 호환 가능이라는 뜻입니다...
노드로 세션 로그인 기능을 구현할 때 많은 블로그를 돌아보며 과연 다른 사람들은 어떤 저장소를 썼을까 정말 많이도 살펴봤었다. 주로 사용하는 저장소로는 session-file-store, express-mysql-session이었던 것 같다. redis에 저장하는 것도 많이 봤다. (몽고DB에 저장할 수 있는 옵션도 많이 본 것 같다. 난 mysql을 사용한다. 그래서 저 두 개가 눈에 제일 많이 들어왔을지도?)
그리고 불행히도 나는 session-file-store를 골랐다.
npm i session-file-store
선택한 이유는 진짜 별 거 없다... 초반에 이해도가 되게 떨어지는 상태로 일단 부딪혀보자 하는 멘땅에 헤딩 급으로 들이박은 상태라... 뭔가 직관적으로 세션 파일 스토어?! 이게 딱 적당해 보이는 걸?! 그리고 디비에 저장한다고 하니... 뭔가 괜히 디비에 뭘 더 넣기 싫은 마음이었다. 애초에 뭐가 좋고 나쁜지를 따져보고 선택한 게 아니라, 뭐라도 일단 맘 가는대로 해보고 (ㅋㅋ) 나중에 이해도가 좀 올라오고 나면 뭣이 중헌지 따져보고 sql 저장소로 바꾸거나 할 생각이었다.
실제로 당시에 작성했던 pr의 일부... 일단 이거라도 되면 감사합니다, 라고 생각했음
암튼 이게 왜 불행한 선택이었는지는 나중에 이야기 할 거고, 사실 그렇게 불행한 선택이 아닐지도... 잘만 쓴다면... 하지만 오늘도 약간 날 괴롭게 했기 때문에 불행한 선택이었다고 느끼는 중이다. 흑흑... 왜 이걸 골랐니...
암튼 그렇기 때문에 session-file-store도 설치해준다.
import session from 'express-session';
import FileStoreModule from 'session-file-store';
const FileStore = FileStoreModule(session);
app.use(
session({
secret: '당신만의...씨크릿 키' // 환경변수에 넣어둬야 하는 비밀 정보... 그런데 이걸 코드에 그대로 쓴 미친놈이 있다?! 바로 나...
resave: false,
saveUninitialized: false,
cookie: {
sameSite: 'lax',
secure: false,
httpOnly: true,
maxAge: 1000 * 60 * 60,
},
store: new FileStore()
})
);
세션은 이정도로 설정하면 될 것 같다.
원래는 cookie-parser 미들웨어도 필요했지만 버전 업이 되면서 더이상 필요가 없어졌다고 한다. (version 1.5.0 이상에 해당) 이전까지는 쿠키 파서의 도움이 필요했으나 현재는 이 모듈이 res
/req
를 통해서 다이렉트로 쿠키를 읽고 쓸 수 있게 되었다. 하지만! 이 모듈과 쿠키 파서를 함께 사용하면서 secret
값이 일치하지 않으면 문제를 일으킬 수도 있다고 하니 주의하시길...
세션과 쿠키에 줄 수 있는 옵션이 많지만, 일단 내가 사용한 옵션들과 그 밖의 옵션들에 대해서 설명을 해보겠다. (사실 첨부터 다 알고 쓴 건 아니었어요 머쓱머쓱)
기본적으로 많이 사용하는 옵션들은 이런 것들이 있다.
secret
: 쿠키에 서명을 추가하는 것으로 cookie-parser의 secret 값과 일치하지 않을 시 에러를 낼 수 있다고 한 것이 바로 이 부분!
resave
: 요청이 왔을 때 세션에 수정사항이 생기지 않더라도 세션을 다시 저장할지 설정하는 옵션(이라고 한다. 많이들 false
로 주길래 줘봄...)
saveUninitialized
: 세션에 저장할 내용이 없더라도 처음부터 세션을 설정할지 결정하는 옵션(이라고 한다2)
cookie
: 쿠키에 대한 설정을 하는 옵션. 쿠키 옵션은 아래에 따로 다룰 예정!
store
: 외부 저장소에 연결을 할 때 사용하는 옵션.
다른 건 다 알겠는데 resave와 saveUninitialized 옵션은 잘 모르면서 사용한 것이 영 맘에 걸린다. 그래서 오늘은 여기까지 알아보고 쿠키 옵션 이후로는 다음 퇴근 시간이나 주말에 더 공부를...
saveUninitialized
세션이 처음 만들어질 당시에 따로 세션을 수정하지 않는다면 그 세션은 uninitialized(초기화 되지 않은) 상태라고 한다. → 이 세션을 강제로 저장소에 저장 할거냐에 대한 옵션!
세션 초기화는 세션 식별자를 생성(sessionID)하고, 서버에서 세션 데이터를 저장하기 위한 저장소를 설정하는 등의 작업을 포함한다. 그 세션을 저장할 것인지(true
), 저장하지 않을 것인지(false
)에 대한 옵션이다.
뭔가 여기까지만 들으면 이 옵션을 true로 주는 것이 맞지 않나...? 싶었는데 권장은 false
다. (사람들이 많이 쓰는 건 다 이유가 있는 것임 역시!) 그런데 기본값은 true
니까 내가 사용하기에 적합한 것을 선택하여 사용하는 것이 좋을 것 같음.
왜냐하면 false를 선택하는 것이 로그인 세션을 구현하거나, 서버 저장소 사용량을 줄이거나, 쿠키를 설정하기 전에 권한이 필요한 법률을 준수하는데 유리하기 때문이라고 한다. 또한 false를 선택하는 것이 클라이언트가 세션 없이 병렬 요청을 하는 경쟁 조건에도 도움이 된다고 한다.
saveUninitialized 옵션이 true이거나 false일 때 어떻게 동작하는지 궁금한 사람은 이 블로그를 참고하면 도움이 될 것 같다.
resave
세션 정보는 변경이 될 수도 있다. true
일 경우 요청 중에 세션이 수정되지 않은 경우에도 세션을 세션 저장소에 다시 저장하도록 하는 옵션이다. false
라면 세션 정보가 변경되었을 경우에만 저장한다.
마찬가지로 기본값은 true이지만 일반적으로 false를 사용한다. 그러면 변경 사항이 없는 세션이 매번 다시 저장되는 것을 막아서 효율을 높여준다. 또한 동시에 두 가지의 req
를 처리할 때에도, 한 쪽에서의 세션 변경 사항과 다른 한 쪽의 세션 변경 사항의 충돌을 막아준다고 한다.
만일 세션을 저장하는 store에 touch method가 없다면 true로 해놓아야 한다는데 그래서 이 touch method가 머냐... maxAge 옵션을 업데이트 해 주는 기능이라고 한다. (세션 유지를 위해) 보통 자동으로 이 기능이 실행된다. 그러니까 그냥 false 쓰시면 됩니다!
그 외의 옵션들...
genid
: 새로운 세션 아이디를 생성하기 위해 함수를 호출하는 옵션. 기본적으로는 uid-safe
라이브러리가 사용된다고 한다.
name
: 세션아이디를 담은 쿠키의 이름. 따로 설정해주지 않아도 connect.sid
라는 이름으로 프론트에 세션값을 보낸다.
proxy
: 노드 서버가 프록시 뒤에 있다면 설정해주어야 하는 옵션
rolling
: 로그인 상태에서 다른 페이지로 이동할 때마다 세션 값에 변화를 줄 것인지 결정하는 옵션
unset
: 세팅되지 않은 req.session
의 결과를 컨트롤하는 옵션
후 내일 나머지 옵션들이나 더 공부해봐야지... 우울탱
지난번에 까먹고 쓰지 않은 설정들... 전체 코드는 아래와 같다.
import cookieParser from 'cookie-parser';
import session from 'express-session';
import FileStoreModule from 'session-file-store';
import cors from 'cors';
const FileStore = FileStoreModule(session);
app.use(
cors({
origin: true,
credentials: true,
}),
);
app.use(cookieParser(process.env.SESSION_SECRET_KEY));
app.use(
session({
secret: process.env.SESSION_SECRET_KEY,
resave: false,
saveUninitialized: false,
cookie: {
sameSite: 'lax',
secure: false,
httpOnly: true,
maxAge: 1000 * 60 * 60,
path: '/api',
},
store: new FileStore({
reapInterval: 3000,
}),
}),
);
세션을 통한 로그인 구현에 필요한 설정들만 모은 것이다.
cors 설정을 해주지 않으면 프론트서버와 통신이 불가하니 유의해야 한다. (이 문제로 한창 곤란했음...) 프론트에서도 해줘야 하는 설정이니 주의!
참고한 문서 및 블로그
- express-session 공식 문서 https://www.npmjs.com/package/express-session#saveuninitialized
- https://velog.io/@dev2820/nodejs%EC%9D%98-%EC%84%B8%EC%85%98
- https://webaura.tistory.com/entry/Nodejs-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-%EC%84%B8%EC%85%98%EC%9C%A0%EC%A7%80%ED%8E%B8
- 세션과 쿠키 옵션 관련 https://velog.io/@ehdgus8054/Express-Session
- resave, saveUninitialized 관련 https://obstinate-developer.tistory.com/entry/express-session-option-%EC%84%A4%EB%AA%85