쿠키와 세션은 서로 어떤 관계이며, 각각이 인증에 있어서 어떤 목적으로 존재하는지 이해하기 위하여 스프린트를 진행해보았다.
스프린트 시작 전 아래와 같이 상황을 설명하고 있다.
쿠키를 학습하고, 브라우저에 상태를 저장할 수 있다는 사실을 알게 된 주니어 개발자 김코딩은, 쿠키에 인증 정보를 넣어 로그인 상태를 유지할 수 있음을 알게 되었습니다.
그러나, 선배 개발자 박코딩은, 쿠키만으로 인증을 구현하는 것은 언제든 쿠키가 클라이언트에 의해 변조가 가능하기 때문에 위험하다는 사실을 알려줍니다. 이에 따라 서버에도 쿠키를 검증할 수 있는 수단을 마련하는 세션 인증 방식을 제안합니다.
쿠키옵션들을 지정한 다음(domain, httpOnly, sameSite ..등등) 서버에서 클라이언트로 쿠키를 처음 전송하게 된다면 헤더에 Set-Cookie라는 프로퍼티에 쿠키를 담아 쿠키를 전송하게 된다.
이후 클라이언트 혹은 서버에서 쿠키를 전송해야 한다면 클라이언트는 헤더에 Cookie라는 프로퍼티에 쿠키를 담아 서버에 쿠키를 전송하게 된다.
CORS 및 세션 옵션을 설정하였다.
const express = require('express');
const cors = require('cors');
const session = require('express-session');//세션관리용 미들웨어
const logger = require('morgan');
const fs = require('fs');
const https = require('https');
const usersRouter = require('./routes/user');
const app = express();
const PORT = process.env.PORT || 4000;
///////// 세션 옵션을 아래와 같이 설정하였다
app.use(
session({
secret: '@codestates',
resave: false,
saveUninitialized: true,
cookie: {
domain: 'localhost',
path: '/',
maxAge: 24 * 6 * 60 * 10000,
sameSite: 'None',
httpOnly: 'true',
secure: 'true',
},
})
);
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
//////////CORS의 default 옵션이 아닌 아래와 같이 세부 옵션을 설정해주었다
const options = {
origin: "http://localhost:3000",
methods: "GET,POST,OPTIONS",
credentials: true // 다른 도메인 간 쿠키 주고받을 수 있게 (서버에서 설정)
};
app.use(cors(options));
app.use('/users', usersRouter);
///////////인증서 파일 유무를 기준으로 https or http 프로토콜 사용여부를 판단한다.
let server;
// 인증서 파일들이 존재하는 경우에만 https 프로토콜을 사용하는 서버를 실행
// 만약 인증서 파일이 존재하지 않는경우, http 프로토콜을 사용하는 서버를 실행
// 파일 존재여부를 확인하는 폴더는 서버 폴더의 package.json이 위치한 곳입니다.
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)
}
module.exports = server;
아래와 같은 요청을 처리하는 코드를 작성하였다.
1. controller/login.js (POST /users/login)
userId
속성 생성한다) req.session.save(function () {
req.session.userId = userInfo.userId;
res.json({ data: userInfo, message: 'ok' }); // save 비동기라서 여기안에 있어야함
});
2. controller/logout.js (POST /users/logout)
req.session.destroy();
res.json({ data: null, message: 'ok' });
3. controller/userinfo.js (POST /users/userinfo)
{ userId: req.session.userId }
를 작성한다)package.json
파일의 FILL_ME_IN을 서버에서 사용했던 인증서 파일의 상대경로로 변경"start": "HTTPS=true SSL_CRT_FILE=../server-cookie.cert.pem SSL_KEY_FILE=../server-cookie.key.pem react-scripts start"
,AJAX 요청 시에 쿠키를 같이 보낼건지를 결정하는 옵션인 withCredentials: true
를 꼭 명시해준다.
loginRequestHandler() {
//로그인 POST 요청 보내기
axios.post('https://localhost:4000/users/login', {
userId: this.state.username,
password: this.state.password
}, {
credentials: 'include',
withCredentials: true
})
.then(res => {
this.props.loginHandler(); //로그인 상태를 true로 변경시키는 함수 호출
})
.catch(err => {
console.log(err);
})
//userinfo 받아오는 GET 요청 보내기
axios.get('https://localhost:4000/users/userinfo', {
credentials: 'include',
withCredentials: true
})
.then(res => {
this.props.setUserInfo(res.data.userInfo); //userData를 업데이트하는 setState 함수 호출
})
.catch(err => {
console.log(err);
})
}
axios.post('https://localhost:4000/users/logout', {}, {
credentials: 'include',
withCredentials: true
})
.then(res => {
props.logoutHandler() //로그인 상태를 false로 변경시키는 함수 호출
})
.catch(err => {
console.log(err);
})
(사진: 로그인 화면)
(사진: 로그인 성공 시 화면)