드디어 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/