들어가며
세션, 참 많이 들어본 단어입니다. 하지만 세션이 무엇이고, 어떻게 활용할 수 있는지 전혀 알지 못했습니다. 지금이라도 세션이 무엇이며, 세션을 어떻게 활용할 수 있는지 알아야겠다고 생각했습니다. 더 늦기 전에, 웹의 기초에 대해 쌓고 싶어서 개념을 정리해보고자 합니다. 무지를 반성하는 마음으로 이 글을 적습니다.
세션(Session)
세션이란 일정 시간 동안 같은 사용자(정확하게 브라우저를 말한다)로 부터 들어오는 일련의 요구를 하나의 상태로 보고 그 상태를 일정하게 유지시키는 기술입니다. 여기서 일정 시간이란 방문자가 웹 브라우저를 통해 웹 서버에 접속한 시점으로부터 웹 브라우저를 종료함으로써 연결을 끝내는 시점을 말합니다.
HTTP에서 Session은 다음과 같이 동작합니다.
1) 클라이언트(사용자)가 서버로 접속(http 요청)을 시도한다.
2) 서버는 접근한 클라이언트의 request-header field인 cookie를 확인해 클라이언트가 해당 session-id를 보내왔는지 확인한다.
3) 만약 클라이언트로부터 발송된 session-id가 없다면, 서버는 session-id를 생성해 클라이언트에게 response-header field인 set-cookie 값으로 session-id(임의의 긴 문자열)를 발행(응답)한다.
그렇다면, node.js의 express에서 Session을 활용하는 방법에 대해 알아보겠습니다.
express-session
node.js의 express에서 session을 활용하는 방법에 대해 알아보겠습니다.
express에서 session 설정하기
먼저 다음과 같은 명령어로 express-session을 설치할 수 있습니다.
npm i express-session
설치가 완료됐다면, express-session 미들웨어를 사용해보겠습니다.
const express = require('express');
const app = express();
const session = require('express-session'); //세션관리용 미들웨어
app.use(session({
httpOnly: true, //자바스크립트를 통해 세션 쿠키를 사용할 수 없도록 함
secure: ture, //https 환경에서만 session 정보를 주고받도록처리
secret: 'secret key', //암호화하는 데 쓰일 키
resave: false, //세션을 언제나 저장할지 설정함
saveUninitialized: true, //세션이 저장되기 전 uninitialized 상태로 미리 만들어 저장
cookie: { //세션 쿠키 설정 (세션 관리 시 클라이언트에 보내는 쿠키)
httpOnly: true,
Secure: true
}
}));
세션은 서버 메모리(MemoryStore)에 저장됩니다. 서버 메모리에 데이터가 저장되면 휘발성이 있기 때문에, 서버가 꺼지게 된다면 데이터는 모두 초기화돼서 없어집니다. 그래서 store 프로퍼티를 활용해서 세션을 저장할 저장소를 따로 지정할 수 있는데, 실제 서비스 배포 시에는 데이터베이스를 연결해서 세션을 유지하면 좋습니다. 이때 보통 Redis 데이터베이스를 연결해서 사용한다고 합니다. 만약 Redis나 MongoDB에 저장할 거라면 fortune-session를 활용할 수 있습니다. Session Stores 설정과 관련해서는 API 문서를 보고 고르면 됩니다.
하지만 이번 세션에 대한 설명에서는 일단 session file store를 설치해서 활용해보겠습니다.
npm i session-file-store
file store를 설치했다면, 모듈을 불러오고, fileStore 변수에 모듈 값을 할당하겠습니다.
const express = require('express');
const app = express();
const session = require('express-session'); //세션관리용 미들웨어
const fileStore = require('session-file-store')(session);
app.use(session({
httpOnly: true, //자바스크립트를 통해 세션 쿠키를 사용할 수 없도록 함
secure: ture, //https 환경에서만 session 정보를 주고받도록 처리
secret: 'secret key', //암호화하는 데 쓰일 키
resave: false, //세션을 언제나 저장할지 설정함
saveUninitialized: true, //세션이 저장되기 전 uninitialized 상태로 미리 만들어 저장
cookie: { //세션 쿠키 설정 (세션 관리 시 클라이언트에 보내는 쿠키)
httpOnly: true,
secure: true
},
store: new fileStore()
}));
이렇게 성장하고 서버를 시작하면 sessions 디렉터리가 생깁니다. 그리고 특정 사용자가 서버에 접속하면 sessions 디렉터리에 세션 파일이 생성됩니다. session 미들웨어를 사용할 때, session은 매개변수로 객체를 하나 받습니다. 이 객체 안에는 secret, resave, saveUninitialized, cookie, store 등 여러 가지 옵션이 있습니다. 이 옵션들에 대해 살펴보겠습니다.
- secret
하나의 문자열을 받을 수도 있고, 여러 secret들의 배열을 받을 수도 있습니다. 만약 배열이 주어질 경우 첫 번째 요소만 세션 ID 쿠키에 sign 하는 데 쓰인다고 합니다.
- resave
resave를 false로 하면 세션 데이터가 변경되기 전까지는 세션 저장소의 값을 저장하지 않습니다. 만약 true로 설정하면 값이 변경되지 않아도 계속 저장소에 저장한다는 뜻입니다.
- saveUninitialized
saveUninitialized를 false로 설정하면 세션을 항상 구동시킵니다. 하지만 true로 설정하면 세션이 필요하기 전까지는 세션을 구동시키지 않습니다. 만약 false로 세션을 항상 구동시킨다면, 서버에 큰 부담을 줄 수 있습니다.
- cookie
cookie 설정을 통해 세션 ID 쿠키 객체를 설정합니다. 기본값은 path: '/', httpOnly: true, secure: false, maxAge: null입니다. 위에서 본 것과 같이 domain, expires, httpOnly 등 쿠키의 옵션을 설정해줄 수 있습니다.
- seucre
false지만 true를 권장하고, Node 서버가 프록시 뒤에 있다면 app.use(session({}))을 하기 전에 app.set('trust proxy', 1)을 설정해주는 게 필요하다고 합니다.
위의 옵션 외에도 세션을 설정할 때 다양한 옵션이 있으니 더 추가해야 한다면 API 문서를 참고해서 설정하면 됩니다.
express에서 session 접근하기
만약 session 객체에 접근하고 싶다면 req.session을 통해 접근이 가능합니다. 가령 사용자가 이 페이지에 몇 번이나 들어왔는지 보여주려면 다음과 같은 앱을 구성할 수도 있습니다.
app.get('/', (req, res, next) => {
if (req.session.num === undefined)
req.session.num = 1;
else
req.session.num += 1;
res.send(`${req.session.num}번 접속`);
});
같은 세션을 유지하고 있는 한 루트 path로 접속을 할 때마다 req.session.num이 1씩 늘어나게 됩니다. 만약 처음 접속했다면, session에 num 프로퍼티를 추가하고, 프로퍼티 값으로 1을 추가합니다. 만약 session객체에 num 프로퍼티가 존재한다면 num 프로퍼티 값을 1씩 추가합니다.
만약 로그아웃을 해서 세션을 유지할 필요가 없어진다면 req.session.destroy를 하면 됩니다.
req.session.destroy(err => {
if (err) throw err;
res.redirect(302, '/');
});
만약 세션에 특정 값을 저장하고, 리다이렉션을 했는데, session store가 부하가 걸렸다면, 특정 데이터는 저장되지 않은 상태에서 리다이렉션을 먼저 해주게 됩니다. 그렇다면 리다이렉션 했을 때 바로 원하는 결과가 반영되지 않을 수도 있습니다. 그럴 때는 req.session.save(err => {})를 통해 session 데이터를 먼저 저장하고, 저장한 후에 리다이렉션을 시켜주는 방법이 있습니다.
if (email === authData.email && pwd === authData.pwd) {
req.session.is_logined = true;
req.session.nickname = authData.nickname;
req.session.save(err => {
if (err) throw err;
res.redirect(302, '/');
});
} else {
res.end("Who?");
}
세션(Session) 안전하게 관리하기
세션도 완벽해 보이긴 하지만, 세션을 서버에 저장한다고 치면 동시접속자가 많아질 때 서버에 그만큼 세션이 많이 쌓이게 되므로 과부하가 올 수 있다는 단점이 있습니다. 또한 세션 하이재킹이라고 세션을 인증하기 위한 세션 id 자체를 빼앗는 세션 가로채기 공격도 있습니다. 예를 들면 홈페이지 관리자의 세션 아이디를 탈취해서 쿠키값을 관리자의 세션 아이디로 변경하는 안 좋은 상황이 생길 수도 있습니다. 이 경우에는 세션 id를 뺏으면 ID와 암호를 몰라도 로그인이 가능하기 때문에 위험할 수 있습니다. 이 문제를 예방하기 위해 세션에 로그인했을 때의 IP 값을 저장하고, 페이지를 이동할 때마다 현재 IP와 세션의 IP/브라우저 정보가 같은지 검사하는 방법이 있다고 합니다. 그렇다면, 세션 관리를 어떻게 해야 할까요? 이에 대해 알아보겠습니다.
세션 ID 추측 | 세션 ID 훔치기 | 세션 ID 고정 |
- 세션 ID 생성 방법이 부적절한 경우 제3자가 추측 가능하여 세션 하이재킹이 가능함 | - 네트워크상에서 패킷 스니핑을 통한 세션 ID 약탈 | - 공격자가 사용자의 브라우저에 세션 ID를 설정하는 것이 가능하다면, 공격자는 이미 사용자의 세션 ID를 알고 있는 상태가 될 수 있으므로 세션 하이재킹이 가능하게 됨 |
- XSS(크로스 사이트 스크립트)등 애플리케이션 취약점에 의한 유출 | ||
- URL에 가지고 있는 Redirect를 이용하거나, 브라우저의 취약점을 통해 세션 ID 약탈 |
세션 관리 5가지 진단
1) "세션 ID 추측이 가능한가?"
먼저 사이트에 시간대별로 여러 번 접속하여 할당되는 세션 ID가 추측이 가능한지 확인해야 합니다. 이는 수동으로 진행하기 어렵기 때문에 툴을 사용해 테스트할 수 있습니다. 오픈소스 프록시 툴인 WebScarab의 SessionID Analysis Plugin을 활용합니다. 만약 세션 ID가 추측이 가능하다면, 세션 ID를 공격자가 추측하기 어렵게 길고 랜덤 하게 생성하는 알고리즘을 사용해야 합니다.
2) "쿠키값을 조작해 세션 훔치기가 가능한가?"
사이트 접속 후 cooxie툴을 이용해 현재 세션 값을 편집해서 세션 훔치기가 가능한지 확인해야 합니다. 그리고 로그인한 세션 ID 정보를 패킷 스니핑이나 XSS 취약한 사이트를 이용해 획득한 뒤, 해당 세션 ID 정보를 이용해 세션 훔치기를 시도해보는 방법도 있습니다. 만약 웹서버가 클라이언트의 인증정보를 세션 ID에 저장된 사용자 정보만으로 확인하는 로직을 가지고 있다면 공격자는 세션 ID 조작을 통해서 손쉽게 HTTP 세션 하이재킹을 성공할 수 있습니다. 이 문제를 해결하기 위해서는 세션 ID가 포함된 세션 쿠키가 안전하게 송수신되도록 설정해야 합니다. 자바스크립트에서 세션 ID 정보를 읽을 수 없도록 세션 쿠키에 httponly 속성을 true로 설정해야 하며, 패킷 스니핑을 통해 세션 ID 정보가 노출되지 않도록 암호화된 채널을 통해 전송하도록 세션 쿠키에 secure 속성을 true로 설정해야 합니다.
3) "세션 ID 고정 공격이 가능한가?"
진단하고자 하는 사이트에 A 브라우저로 접속하여 얻은 세션 ID를 사용하여 로그인을 요청할 수 있도록 요청 URL을 조립하고, B 브라우저를 이용하여 해당 URL을 클릭, 로그인을 수행했을 때 B 브라우저가 로그인된 세션 ID와 A 브라우저의 세션 ID 가 동일지 확인하는 과정을 통해 세션 ID를 확인하는 방법이 있습니다. 만약 이 문제를 해결하기 위해서는 조작하여 요청 보낼 수 없도록 URL rewrite 기능을 사용하지 않고, 로그인 성공 시 세션 ID를 재할당해야 하며, 이 과정에서 URL rewrite와 같은 기능을 구현하지 않는 것이 좋습니다. 그리고 URL rewrite로 이동되는 페이지는 세션 ID 정보가 URL에 포함되므로 공격자들이 쉽게 세션 ID 고정 공격을 수행할 수 있어 로그인 성공 시 세션 ID를 재할당 하도록 프로그램을 작성해야 합니다.
if(loginSuccess){
session.invalidate();
session=request.getSession();
session.setAttribute("user", user);
}
4) "자동 로그아웃 기능이 있는가?"
만약 세션 타임아웃이 적절하게 수행되지 않은 경우, 사용자가 자리를 비운 사이 악의적인 사용자가 해당 웹 브라우저를 이용하여 로그인한 사용자의 계정 정보를 이용하여 임의의 작업을 수행할 수 있습니다. 이 문제를 해결하기 위해서는 서버 환경설정을 통해 자동 로그아웃 기능을 설정해야 합니다.
<session-config>
<session-timeout>300</session-timeout>
</session-config>
5) "로그아웃 후 다시 브라우저로 페이지에 접속할 경우 세션이 종료되어 있는가?"
로그아웃을 수행할 때 세션을 완전히 삭제하고 로그아웃을 수행하는지 확인해야 합니다. 로그아웃을 수행하고 브라우저를 종료하지 않는 경우 세션 ID가 지속적으로 사용되어 이전 사용자 정보가 삭제되지 않고 유효한지 확인합니다. 이 문제를 해결하기 위해서는 로그아웃 요청 처리 시 세션 정보를 완전히 삭제해야 합니다.
@RequestMapping("/logout.do")
public String logout(HttpSession session){
session.invalidate();
return "redirect:login.do";
}
마치며
공부를 하며 그동안 아주 기초적인 것들을 제대로 공부하지 않았다는 것을 깨닫습니다. 좋은 기술을 배우는 것도 좋지만, 기술의 기반이 되는 기초적인 지식을 먼저 쌓는 것이 중요하다는 것을 다시금 깨달았습니다. 기초가 튼튼한 개발자가 되고 싶습니다.
출처
'Web' 카테고리의 다른 글
[Web] 다중 서버에서 세션을 관리해보자 - 2 (feat 세션 불일치) (0) | 2022.01.05 |
---|---|
[Web] 다중 서버에서 세션을 관리해보자 - 1 (feat Scale-up, Scale-out) (0) | 2021.12.29 |
[Web] 로컬 스토리지와 세션 스토리지 (0) | 2021.12.11 |
[Web] 쿠키를 알아보자 (0) | 2021.12.11 |
[Web] 비트와 바이트, 문자 인코딩이란? (0) | 2021.06.09 |