DB 유저이름=""
DB 패스워드=""
Database 이름=""
이번 스프린트에서 시퀄라이즈(Sequelize)를 사용해 데이터베이스를 조작합니다.
시퀄라이즈는 데이터베이스와 자바스크립트 코드로 작성된 서버의 데이터를 호환시켜주는 ORM 툴입니다.
시퀄라이즈와 같은 ORM을 사용해 코드 상에서 객체 형태로 되어있는 데이터와 데이터 베이스에 저장된 데이터의 타입을 호환시킬 수 있습니다.
ORM - 포스팅 정리
여기서는 시퀄라이즈를 사용해 데이터베이스 마이그레이션을 진행합니다. 데이베이스 마이그레이션(database migration)은 시퀄라이즈와 같은 라이브러리를 사용해 작성된 데이터베이스 스키마를 데이터베이스에 주입하는 것을 의미합니다.
1. config.js 확인 (db접속정보,아이피 등 확인)
const dotenv = require('dotenv');
dotenv.config();
module.exports = {
development: {
username: 'root',
password: process.env.DATABASE_PASSWORD,
database: 'authentication',
host: '127.0.0.1',
dialect: 'mysql',
logging: false
},
test: {
username: 'root',
password: process.env.DATABASE_PASSWORD,
database: 'authentication',
host: '127.0.0.1',
dialect: 'mysql',
},
production: {
username: 'root',
password: process.env.DATABASE_PASSWORD,
database: 'authentication',
host: '127.0.0.1',
dialect: 'mysql',
},
};
npm install >> package.json에 작성된 모듈들 설치
npx sequelize-cli db:migrate >> sequeliz-cli를 사용해 데이터베이스 마이그레이션 진행
다음과 같이 테이블을 만들고 지울 수 있는 기능으로 보인다.
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},userId: {
type: Sequelize.STRING,
allowNull: false,
},
email: {
type: Sequelize.STRING,
allowNull: false,
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Users');
},
};
3. npx sequelize-cli db:seed:all
다음 코드와 같이 데이터를 insert할 때 사용하는 것으로 보인다.
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
/**
* 시쿼라이즈가 실행할 시드 명령어를 작성합니다..
*
* 예시:
* await queryInterface.bulkInsert('People', [{
* name: 'John Doe',
* isBetaMember: false
* }], {});
*/
return queryInterface.bulkInsert('Users', [
{
id: '0',
userId: 'kimcoding',
password: '1234',
email: 'kimcoding@authstates.com',
createdAt: new Date(),
updatedAt: new Date(),
},
]);
},
down: async (queryInterface, Sequelize) => {
/**
* 시드를 취소하기 위한 명령어를 작성합니다.
*
* 예시:
* await queryInterface.bulkDelete('People', null, {});
*/
return queryInterface.bulkDelete('Users', null, {});
},
};
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;
// TODO: express-session 라이브러리를 이용해 쿠키 설정을 해줄 수 있습니다.
app.use(
session({
secret: "@codestates",
resave: false,
saveUninitialized: true,
cookie: {
domain: "localhost",
path: "/",
maxAge: 24 * 6 * 60 * 10000,
//Cookie의 SameSite 속성의 기본 값이 "None" 항상 쿠키를 보내줄 수 있음, 다만 쿠키옵션중 Secure옵션이 필요함
sameSite: "none",
// 자바스크립트로 쿠키 접근 못하게하려면 true 설정
httpOnly: true,
// 쿠키 secure 옵션
secure: true,
},
})
);
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// TODO: CORS 설정이 필요합니다. 클라이언트가 어떤 origin인지에 따라 달리 설정할 수 있습니다.
// 메서드는 GET, POST, OPTIONS를 허용합니다.
app.use(cors());
/**
* /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;
1. request로부터 받은 userId와 password와 일치하는 유저가 db에 있는지 확인
2. 일치하는 유저가 없을 경우 > 로그인 요청 거절
3. 일치하는 유저가 있을 경우 > 세션에 userId를 저장
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 },
});
// 결과가 존재하는 경우 세션 객체에 userId가 저장되어야 합니다.
// userInfo에 정보가 없으면 404 코드를 반환하고 not authorized 반환
if (!userInfo) {
return res.status(404).send({ message: "not authorized" });
} else {
//그외에 userInfo가 있으면 다음과 같이 세션을 저장한다.
// 이후 data에는 userInfo를 담고 message에는 ok를 담는다.
req.session.save(function () {
req.session.userId = userInfo.userId;
return res.json({ data: userInfo, message: "ok" });
});
}
},
};
1. 세션 객체에 저장한 값이 존재하면 세션을 삭제하기 ( 자동으로 클라이언트 쿠키는 갱신된다.)
module.exports = {
post: (req, res) => {
// TODO: 세션 아이디를 통해 고유한 세션 객체에 접근할 수 있습니다.
// 앞서 로그인시 세션 객체에 저장했던 값이 존재할 경우, 이미 로그인한 상태로 판단할 수 있습니다.
// 세션 객체에 담긴 값의 존재 여부에 따라 응답을 구현하세요.
if (!req.session.userId) {
res.status(400).send();
} else {
// your code here
// TODO: 로그아웃 요청은 세션을 삭제하는 과정을 포함해야 합니다.
//저장된 세션 삭제
req.session.destroy();
//로그인 이후 로그아웃을 요청할 때 상태코드 200을 리턴해야함
res.status(200).send();
}
},
};
1.세션 객체에 저장한 값이 존재하면 사용자 정보를 데이터베이스에서 조회한 후 응답으로 전달합니다.
2.세션 객체에 저장한 값이 존재하지 않으면 요청을 거절합니다.
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 {
//옵션1 유저정보를 찾아서 같이 반환
const result = await Users.findOne({
where: { userId: req.session.userId },
});
res.status(200).json({ data: result, message: "ok" });
//옵션 2 상태코드와 메세지 ok만 반환
// res.status(200).send({ message: "ok" });
// console.log("result", result);
// TODO: 데이터베이스에서 로그인한 사용자의 정보를 조회한 후 응답합니다.
}
},
};
import React from "react";
import axios from "axios";
function Mypage(props) {
const handleLogout = () => {
// TODO: 서버에 로그아웃 요청을 보낸다음 요청이 성공하면 props.logoutHandler를 호출하여 로그인 상태를 업데이트 해야 합니다.
axios
.post("https://localhost:4000/users/logout", {
withCredentials: true,
})
.then(() => {
props.logoutHandler();
});
};
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;
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 });
}
async loginRequestHandler() {
// TODO: 로그인 요청을 보내세요.
// 로그인에 성공하면
// - props로 전달받은 함수를 호출해, 로그인 상태를 변경하세요.
// - GET /users/userinfo 를 통해 사용자 정보를 요청하세요
// 사용자 정보를 받아온 후
// - props로 전달받은 함수를 호출해, 사용자 정보를 변경하세요.
try {
await axios({
url: "https://localhost:4000/users/login", // 통신할 웹문서
method: "post", // 통신할 방식
headers: {
accept: "application/json",
},
data: {
// 인자로 보낼 데이터
userId: `${this.state.username}`,
password: `${this.state.password}`,
},
withCredentials: true,
});
this.props.loginHandler();
const result = await axios({
method: "get",
url: "https://localhost:4000/users/userinfo",
headers: {
accept: "application/json",
},
withCredentials: true,
});
const { userId, email } = result.data.data;
this.props.setUserInfo({
userId,
email,
});
} catch (err) {
alert(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;
import React, { Component } from 'react';
import Login from './components/Login';
import Mypage from './components/Mypage';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLogin: false,
userData: null,
};
this.loginHandler = this.loginHandler.bind(this);
this.logoutHandler = this.logoutHandler.bind(this);
this.setUserInfo = this.setUserInfo.bind(this);
}
loginHandler() {
this.setState({
isLogin: true,
});
}
setUserInfo(object) {
this.setState({ userData: object });
}
logoutHandler() {
this.setState({
isLogin: false,
});
}
render() {
const { isLogin } = this.state;
return (
<div className='App'>
{isLogin ? (
<Mypage
logoutHandler={this.logoutHandler}
userData={this.state.userData}
/>
) : (
<Login
loginHandler={this.loginHandler}
setUserInfo={this.setUserInfo}
/>
)}
</div>
);
}
}
export default App;
OAuth 2.0(Open Authorization 2.0,) (0) | 2023.01.05 |
---|---|
토큰 기반 인증 - NodeJS (0) | 2023.01.03 |
세션 기반 인증 - Session-based Authentication (0) | 2023.01.03 |
Stateless를 Stateful하게 만들어주는 Cookie! (0) | 2023.01.02 |
사설 인증서 발급 및 HTTPS 서버 구현 - mkcert 이용 (0) | 2022.12.31 |