드디어 Session에 대한 내용이다. 앞선 포스팅에서 Hash, Salt, 대칭키와 비대칭키, HTTPS까지 정리하면서 세션에 필요한 대부분의 선행 지식을 정리했다. 이번 포스팅은 세션의 고유한 특징에 집중해서 작성하려고 한다.
세션(session)이란 웹 사이트 안의 여러 페이지에 걸쳐 사용되는 사용자 정보(ID)를 저장하는 방법을 의미합니다.
로그인 상태를 유지하기 위한 방법 중 하나다. 인증 정보는 서버에 저장되고 클라이언트는 쿠키에 자신의 세션 ID만 받아서 보안 통신을 유지한다.
그림만으로는 다른 사용자가 세션 ID를 가지고 인증된 행세를 낼 수 있어 위험해 보인다. 하지만 세션은 HTTPS 환경에서 이루어지기 때문에 탈취의 위험에서 안전하다. 쿠키로 Session ID를 주고 받는 시점에선 HTTPS 대칭키가 생성되어 통신하고 있다는 전제로 시작한다.
httpOnly
, secure
옵션을 활성화한다. 클라이언트가 처음 통신을 보내면 서버가 공개키를 보내준다.
클라이언트 웹 브라우저에서 세션키를 만들고 공개키로 암호화해서 서버로 보낸다.
서버는 공개키로 세션키를 확인하고 암호화 통신을 시작한다.
이후 토큰에 세션ID가 있는지 확인한다.
express-session
모듈로 세션을 구현한다. 모듈이 대부분의 동작을 처리해줘서 코드상에는 인증서 파일과 설정 파일이 주로 적혀있다.
// TODO: express-session 라이브러리를 이용해 쿠키 설정을 해줄 수 있습니다.
app.use(
session({
secret: '@codestates', // 암호화로 쓰이는 키
resave: false, // 세션을 항상 저장할지 설정
saveUninitialized: true, // 세션이 저장되기 전 uninitialized 상태로 저장
cookie: { // 세션 쿠키 설정
domain: 'localhost', // 쿠키를 보내는 사이트(서버)를 명시
path: '/', // URL 경로. 하위 경로까지 모두 포함.
maxAge: 24 * 6 * 60 * 10000, // 쿠키 만료 시간. 초단위.
// XSRF(cross-site request forgery) 공격 방어 옵션
sameSite: 'None', // 사이트 외부에서 요청을 보낼 때 쿠키 정책
// Strict : 외부 도메인 쿠키 차단
// Lax : 외부 도메인 쿠키 일부 허용(HTTP get method / a href / link href)
// None : 외부 도메인 쿠키 모두 허용. 강제로 origin 옵션과 같이 써야함
httpOnly: true, // 클라이언트 스크립트가 쿠키를 못보게 함 (document.cookie)
secure: true, // HTTPS 환경만 쿠키를 전송
},
})
);
// CORS 쿠키 설정 활성화
app.use(
cors({
// origin: 'https://localhost:3000',
origin: true,
methods: ['GET', 'POST', 'OPTIONS'],
credentials: true, // 다른 도메인간 자격증명(credential, 쿠키 포함)을 전송할지 여부
})
);
세션 통신은 HTTPS 환경에서 이루어진다.
if (fs.existsSync('./key.pem') && fs.existsSync('./cert.pem')) {
server = https
.createServer(
{
key: fs.readFileSync(__dirname + `/` + 'key.pem', 'utf-8'),
cert: fs.readFileSync(__dirname + `/` + 'cert.pem', 'utf-8'),
},
app
)
.listen(PORT);
} else {
server = app.listen(PORT);
}
req.session
객체에 userId키를 만들고 세션 ID를 부여한다connect.sid
라는 이름으로 쿠키에 탑재된다. req.session.userId
값을 확인 후 이루어진다.// 해당 모델의 인스턴스를 models/index.js에서 가져옵니다.
const { Users } = require('../../models');
module.exports = {
post: async (req, res) => {
// userInfo는 유저정보가 데이터베이스에 존재하고, 완벽히 일치하는 경우에만 데이터가 존재합니다.
// 만약 userInfo가 NULL 혹은 빈 객체라면 전달받은 유저정보가 데이터베이스에 존재하는지 확인해 보세요
const userInfo = await Users.findOne({
where: { userId: req.body.userId, password: req.body.password },
});
// console.log(userInfo);
// TODO: userInfo 결과 존재 여부에 따라 응답을 구현하세요.
if (!userInfo) {
// TODO: 유저 정보가 존재하지 않는 경우
// your code here
res.status(400).json({ message: 'not authorized' }).end();
} else {
// your code here
// TODO: 결과가 존재하는 경우 세션 객체에 userId가 저장되어야 합니다.
// HINT: req.session을 사용하세요.
req.session.userId = req.body.userId;
//console.log(req.session);
// TODO: 유저 정보가 존재하는 경우
res.status(200).json({ data: userInfo, message: 'ok' }).end();
}
},
};
세션을 파기할 때는 req.session.destroy()
메소드를 사용한다.
if (!req.session.userId) {
res.status(400).send({ data: null, message: 'not authorized' });
} else {
req.session.destroy();
res.json({ data: null, message: 'ok' });
https://dev-dain.tistory.com/68
https://tiptopsecurity.com/how-does-https-work-rsa-encryption-explained/
https://cron-tab.github.io/2018/05/31/http-cors-credentials/