로그인 요청이 성공적으로 인증되었다면 세션 id를 반환하고 이를 쿠키로 클라쪽에 내려준다.
< 로그아웃 >
서버의 세션 정보를 삭제
클라이언트의 쿠키를 갱신
세션 정보를 삭제하는 메서드로는 req.session.destroy(). 세션 정보가 무효화되면 자동으로 쿠키도 무효가 되므로 세션 정보를 삭제만 하면 된다
https://github.com/expressjs/session#reqsession --- 세션 객체 담기, 불러오기, 파괴하기
< 초기 셋팅 >
1. 환경 변수 설정 -- env.example를 .env로 변경
2. 데이터베이스 생성 -- Mysql 접속 후 .env의 DB NAME 참고하여 DB 생성
3. DB 마이그레이션 -- DB 업데이트 추적 관리 위해 migration 설정.
- 서버 디렉토리로 이동 후 cli 열어 npx sequelize-cli db:migrate
4. mkcert로 발급받은 https 인증서 두개를 서버 폴더에 복사
https://sequelize.org/master/manual/migrations.html -- DB 마이그레이션 참고
server-session/users/login.js
// 해당 모델의 인스턴스를 models/index.js에서 가져옵니다.
const { Users } = require('../../models');
module.exports = {
post: async (req, res) => {
// userInfo는 유저정보가 데이터베이스에 존재하고, 완벽히 일치하는 경우에만 데이터가 존재합니다.
// 만약 userInfo가 NULL 혹은 빈 객체라면 전달받은 유저정보가 데이터베이스에 존재하는지 확인해 보세요
//데이터베이스에 묻고 답 가져오는데 시간이 걸리므로 결과 나올때까지 await걸어줌
//없으면 아직 찾지도 않았는데 다음 코드 진행해버림
const userInfo = await Users.findOne({
where: { userId: req.body.userId, password: req.body.password },
});
// TODO: userInfo 결과 존재 여부에 따라 응답을 구현하세요.
// 결과가 존재하는 경우 세션 객체에 userId가 저장되어야 합니다.
if (!userInfo) {
res.status(404).send({ message: "not authorized" });
} else {
// your code here
// HINT: req.session을 사용하세요.
//세션에 userId, 바디에는 userInfo를 실어 보내기
req.session.save(function () {
req.session.userId = userInfo.userId;
res.json({ data: userInfo, message: "ok" });
});
}
}
}
server-session/users/logout.js
module.exports = {
post: (req, res) => {
// TODO: 세션 아이디를 통해 고유한 세션 객체에 접근할 수 있습니다.
// 앞서 로그인시 세션 객체에 저장했던 값이 존재할 경우, 이미 로그인한 상태로 판단할 수 있습니다.
// 세션 객체에 담긴 값의 존재 여부에 따라 응답을 구현하세요.
if (!req.session.userId) { // //로그인된 상태에서 세션에 userId가 없는 경우 (세션 만료된 상황)
res.status(400).send();
} else {
// your code here
// TODO: 로그아웃 요청은 세션을 삭제하는 과정을 포함해야 합니다.
res.status(200).send();
req.session.destroy() // //로그아웃시 세션 자체가 사라져야 함
}
},
};
server-session/users/userinfo.js
const { Users } = require('../../models');
module.exports = {
get: async (req, res) => {
// console.log("req.session", req.session);
// TODO: 세션 객체에 담긴 값의 존재 여부에 따라 응답을 구현하세요.
// HINT: 세션 객체에 담긴 정보가 궁금하다면 req.session을 콘솔로 출력해보세요
if (!req.session.userId) {
res.status(400).send({ message: "not authorized" });
} else {
const result = await Users.findOne({
where: { userId: req.session.userId },
});
res.status(200).json({ data: result, message: "ok" });
// console.log("result", result);
// TODO: 데이터베이스에서 로그인한 사용자의 정보를 조회한 후 응답합니다.
}
},
};
server-session/index.js
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 FILL_ME_IN = 'FILL_ME_IN';
const PORT = process.env.PORT || 4000;
// TODO: express-session 라이브러리를 이용해 쿠키 설정을 해줄 수 있습니다.
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 }));
// TODO: CORS 설정이 필요합니다. 클라이언트가 어떤 origin인지에 따라 달리 설정할 수 있습니다.
// 메서드는 GET, POST, OPTIONS를 허용합니다.
const corsOptions = {
origin: 'https://localhost:3001',
methods: 'GET, POST, OPTIONS',
credentials: true
//cors규정때문에 블락당하지 않기 위해 셋팅
//쿠키값 받기위해 credentials를 true로
};
app.use(cors(corsOptions));
/**
* /users 요청에 대해서 라우터를 이용하기 때문에,
* 반드시 아래와 같은 주소와 메서드로 요청을 보내야 합니다.
*
* POST https://localhost:4000/users/login,
* POST https://localhost:4000/users/logout,
* GET https://localhost:4000/users/userinfo
*/
app.use('/users', usersRouter);
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;
client-session/scr/components/Login.js
import React, { Component } from "react";
import axios from "axios";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
};
this.inputHandler = this.inputHandler.bind(this);
this.loginRequestHandler = this.loginRequestHandler.bind(this);
}
inputHandler(e) {
this.setState({ [e.target.name]: e.target.value });
}
loginRequestHandler() {
// TODO: 로그인 요청을 보내세요.
// 로그인에 성공하면
// - props로 전달받은 함수를 호출해, 로그인 상태를 변경하세요.
// - GET /users/userinfo 를 통해 사용자 정보를 요청하세요
// 사용자 정보를 받아온 후
// - props로 전달받은 함수를 호출해, 사용자 정보를 변경하세요.
//로그인 POST 요청 보내기
axios
.post(
"https://localhost:4000/users/login",
{
userId: this.state.username,
password: this.state.password,
},
{ withCredentials: true }
)
.then((res) => {
// console.log(res.data);
// if (res.data.message === "ok") {
this.props.loginHandler();
// }
//결과값 then으로 넘겨주기 위해 return
//역시 쿠키 보내기 위해 withCredentials는 true
return axios.get("https://localhost:4000/users/userinfo", {
withCredentials: true,
});
})
.then((res) => {
// console.log(res.data.data);
let { userId, email } = res.data.data;
this.props.setUserInfo({ userId, email });
})
.catch((err) => {
console.error(err);
throw err;
});
}
render() {
return (
<div className="loginContainer">
<div className="inputField">
<div>Username</div>
<input
name="username"
onChange={(e) => this.inputHandler(e)}
value={this.state.username}
type="text"
/>
</div>
<div className="inputField">
<div>Password</div>
<input
name="password"
onChange={(e) => this.inputHandler(e)}
value={this.state.password}
type="password"
/>
</div>
<div className="passwordField">
<button onClick={this.loginRequestHandler} className="loginBtn">
Login
</button>
</div>
</div>
);
}
}
export default Login;
client-session/scr/components/Mypage.js
import React from 'react';
import axios from "axios";
const FILL_ME_IN = 'FILL_ME_IN';
function Mypage(props) {
const handleLogout = () => {
// TODO: 서버에 로그아웃 요청을 보낸다음 요청이 성공하면 props.logoutHandler를 호출하여 로그인 상태를 업데이트 해야 합니다.
axios
.post("https://localhost:4000/users/logout", null, {
withCredentials: true, //쿠키 보내줘야 하기 때문에 withCredentials 쓰기
})
.then(() => props.logoutHandler())
.catch((err) => {
throw err;
});
};
return props.userData === null ? (
<div>Loading...</div>
) : (
<div>
<div className="mypageContainer">
<div>
<span className="title">Mypage</span>
<button className="logoutBtn" onClick={handleLogout}>
logout
</button>
</div>
<hr />
<div>
안녕하세요. <span className="name">{props.userData.userId}</span>
님! 로그인이 완료되었습니다.
</div>
<br />
<div className="item">나의 유저 네임: {props.userData.userId}</div>
<div className="item">나의 이메일 주소: {props.userData.email}</div>
</div>
</div>
);
}
export default Mypage;
client-session/package.json
..... 중략
"scripts": {
"start": "HTTPS=true SSL_CRT_FILE='../server-session/cert.pem' SSL_KEY_FILE='../server-session/key.pem' react-scripts start",
"build": "react-scripts build",
"test": "./run.test.sh",
"report": "mocha --require @babel/register '__tests__/client.test.js' --reporter @mochajs/json-file-reporter",
"submit": "codestates"
}
..... 중략