설명
express를 비롯한 Node.js의 모듈들과, React로 구현한 로그인, 회원가입 예제입니다.
React를 사용하지 않고 구현한 예제는 아래와 같습니다.
https://sirius7.tistory.com/59
MySQL을 데이터베이스로 사용하였기 때문에 MySQL을 설치하셔야 사용 가능합니다. (사용방법 링크)
사용하시면서 궁금하신 점이나 문제점이 있다면 댓글 달아주세요.
필요 모듈
1. mysql2
2. express
3. express-session
4. express-mysql-session
5. body-parser
6. bcrypt
npm을 이용하여 다운로드하시면 됩니다.
mysql2 대신 mysql 모듈을 사용하셔도 무방하며,
express-mysql-session 모듈은 세션을 다른 방식으로 저장하실 경우 사용하지 않으셔도 됩니다.
body-parser 역시 편의를 위한 것으로, 소스코드에서 req.body 부분만 수정하시고 사용하셔도 됩니다.
bcrypt는 비밀번호를 해시알고리즘으로 암호화해서 저장하는데 필요한 모듈로,
사용을 원하지 않으시면 다른 모듈로 대체하셔도 됩니다.
npx create-react-app .
npm install express
npm install express-session
npm install express-mysql-session
npm install mysql2
npm install bcrypt
npm install body-parser
MySQL 세팅
사용을 원하시는 database에서 아래와 같이 테이블을 만들어 주시면 됩니다.
MySQL이 익숙하지 않으시면 링크 를 참고해 주세요
CREATE TABLE userTable (
id int(12) NOT NULL AUTO_INCREMENT,
username varchar(50) NOT NULL,
password varchar(255) NOT NULL,
PRIMARY KEY(id)
) charset=utf8;
파일 생성 및 정리
npx create-react-app . 명령어를 입력해서 리액트를 설치하고,
npm install express 명령어를 입력해서 express를 설치한 다음에 1개의 폴더와 3개의 파일을 만들어주세요
(위 사진에서 노란색으로 표시된 부분)
프로젝트 최상단 폴더에 server.js와 lib 폴더를 만들고,
/lib 폴더 내에는 db.js와 sessionOption.js 파일을 만들어 준 다음, 아래의 소스코드를 입력하면 됩니다.
/src 폴더 내에는 App.css와 App.js 파일이 react 설치와 함께 생성되어 있습니다.
해당 파일의 소스코드를 지운 다음 아래의 소스코드를 입력해 주세요.
server.js
const express = require('express')
const session = require('express-session')
const path = require('path');
const app = express()
const port = 3001
const db = require('./lib/db');
const sessionOption = require('./lib/sessionOption');
const bodyParser = require("body-parser");
const bcrypt = require('bcrypt');
app.use(express.static(path.join(__dirname, '/build')));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
var MySQLStore = require('express-mysql-session')(session);
var sessionStore = new MySQLStore(sessionOption);
app.use(session({
key: 'session_cookie_name',
secret: '~',
store: sessionStore,
resave: false,
saveUninitialized: false
}))
app.get('/', (req, res) => {
req.sendFile(path.join(__dirname, '/build/index.html'));
})
app.get('/authcheck', (req, res) => {
const sendData = { isLogin: "" };
if (req.session.is_logined) {
sendData.isLogin = "True"
} else {
sendData.isLogin = "False"
}
res.send(sendData);
})
app.get('/logout', function (req, res) {
req.session.destroy(function (err) {
res.redirect('/');
});
});
app.post("/login", (req, res) => { // 데이터 받아서 결과 전송
const username = req.body.userId;
const password = req.body.userPassword;
const sendData = { isLogin: "" };
if (username && password) { // id와 pw가 입력되었는지 확인
db.query('SELECT * FROM userTable WHERE username = ?', [username], function (error, results, fields) {
if (error) throw error;
if (results.length > 0) { // db에서의 반환값이 있다 = 일치하는 아이디가 있다.
bcrypt.compare(password , results[0].password, (err, result) => { // 입력된 비밀번호가 해시된 저장값과 같은 값인지 비교
if (result === true) { // 비밀번호가 일치하면
req.session.is_logined = true; // 세션 정보 갱신
req.session.nickname = username;
req.session.save(function () {
sendData.isLogin = "True"
res.send(sendData);
});
db.query(`INSERT INTO logTable (created, username, action, command, actiondetail) VALUES (NOW(), ?, 'login' , ?, ?)`
, [req.session.nickname, '-', `React 로그인 테스트`], function (error, result) { });
}
else{ // 비밀번호가 다른 경우
sendData.isLogin = "로그인 정보가 일치하지 않습니다."
res.send(sendData);
}
})
} else { // db에 해당 아이디가 없는 경우
sendData.isLogin = "아이디 정보가 일치하지 않습니다."
res.send(sendData);
}
});
} else { // 아이디, 비밀번호 중 입력되지 않은 값이 있는 경우
sendData.isLogin = "아이디와 비밀번호를 입력하세요!"
res.send(sendData);
}
});
app.post("/signin", (req, res) => { // 데이터 받아서 결과 전송
const username = req.body.userId;
const password = req.body.userPassword;
const password2 = req.body.userPassword2;
const sendData = { isSuccess: "" };
if (username && password && password2) {
db.query('SELECT * FROM userTable WHERE username = ?', [username], function(error, results, fields) { // DB에 같은 이름의 회원아이디가 있는지 확인
if (error) throw error;
if (results.length <= 0 && password == password2) { // DB에 같은 이름의 회원아이디가 없고, 비밀번호가 올바르게 입력된 경우
const hasedPassword = bcrypt.hashSync(password, 10); // 입력된 비밀번호를 해시한 값
db.query('INSERT INTO userTable (username, password) VALUES(?,?)', [username, hasedPassword], function (error, data) {
if (error) throw error;
req.session.save(function () {
sendData.isSuccess = "True"
res.send(sendData);
});
});
} else if (password != password2) { // 비밀번호가 올바르게 입력되지 않은 경우
sendData.isSuccess = "입력된 비밀번호가 서로 다릅니다."
res.send(sendData);
}
else { // DB에 같은 이름의 회원아이디가 있는 경우
sendData.isSuccess = "이미 존재하는 아이디 입니다!"
res.send(sendData);
}
});
} else {
sendData.isSuccess = "아이디와 비밀번호를 입력하세요!"
res.send(sendData);
}
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
express로 서버를 실행시키는 server.js 파일입니다.
이름은 main.js 등 원하는 대로 작성하셔도 됩니다.
페이지의 root에 접속했을 때 react로 작성된 build/index.html을 보여줍니다.
로그인과 회원가입을 위해 이곳에서 mysql 데이터베이스에 접근해 로그인 과정을 처리하며, 회원가입 과정도 처리합니다.
세션은 express-mysql-session 모듈을 이용하여 MySQL 데이터베이스에 저장하는 방식으로 구현되어 있습니다.
App.js
import './App.css';
import { useState } from 'react';
import { useEffect } from 'react';
function Login(props) {
const [id, setId] = useState("");
const [password, setPassword] = useState("");
return <>
<h2>로그인</h2>
<div className="form">
<p><input className="login" type="text" name="username" placeholder="아이디" onChange={event => {
setId(event.target.value);
}} /></p>
<p><input className="login" type="password" name="pwd" placeholder="비밀번호" onChange={event => {
setPassword(event.target.value);
}} /></p>
<p><input className="btn" type="submit" value="로그인" onClick={() => {
const userData = {
userId: id,
userPassword: password,
};
fetch("http://localhost:3001/login", { //auth 주소에서 받을 예정
method: "post", // method :통신방법
headers: { // headers: API 응답에 대한 정보를 담음
"content-type": "application/json",
},
body: JSON.stringify(userData), //userData라는 객체를 보냄
})
.then((res) => res.json())
.then((json) => {
if(json.isLogin==="True"){
props.setMode("WELCOME");
}
else {
alert(json.isLogin)
}
});
}} /></p>
</div>
<p>계정이 없으신가요? <button onClick={() => {
props.setMode("SIGNIN");
}}>회원가입</button></p>
</>
}
function Signin(props) {
const [id, setId] = useState("");
const [password, setPassword] = useState("");
const [password2, setPassword2] = useState("");
return <>
<h2>회원가입</h2>
<div className="form">
<p><input className="login" type="text" placeholder="아이디" onChange={event => {
setId(event.target.value);
}} /></p>
<p><input className="login" type="password" placeholder="비밀번호" onChange={event => {
setPassword(event.target.value);
}} /></p>
<p><input className="login" type="password" placeholder="비밀번호 확인" onChange={event => {
setPassword2(event.target.value);
}} /></p>
<p><input className="btn" type="submit" value="회원가입" onClick={() => {
const userData = {
userId: id,
userPassword: password,
userPassword2: password2,
};
fetch("http://localhost:3001/signin", { //signin 주소에서 받을 예정
method: "post", // method :통신방법
headers: { // headers: API 응답에 대한 정보를 담음
"content-type": "application/json",
},
body: JSON.stringify(userData), //userData라는 객체를 보냄
})
.then((res) => res.json())
.then((json) => {
if(json.isSuccess==="True"){
alert('회원가입이 완료되었습니다!')
props.setMode("LOGIN");
}
else{
alert(json.isSuccess)
}
});
}} /></p>
</div>
<p>로그인화면으로 돌아가기 <button onClick={() => {
props.setMode("LOGIN");
}}>로그인</button></p>
</>
}
function App() {
const [mode, setMode] = useState("");
useEffect(() => {
fetch("http://localhost:3001/authcheck")
.then((res) => res.json())
.then((json) => {
if (json.isLogin === "True") {
setMode("WELCOME");
}
else {
setMode("LOGIN");
}
});
}, []);
let content = null;
if(mode==="LOGIN"){
content = <Login setMode={setMode}></Login>
}
else if (mode === 'SIGNIN') {
content = <Signin setMode={setMode}></Signin>
}
else if (mode === 'WELCOME') {
content = <>
<h2>메인 페이지에 오신 것을 환영합니다</h2>
<p>로그인에 성공하셨습니다.</p>
<a href="/logout">로그아웃</a>
</>
}
return (
<>
<div className="background">
{content}
</div>
</>
);
}
export default App;
프론트엔드 영역에 해당하는 App.js입니다.
/src 폴더 안에 파일로, 리액트를 이용하여 작성했습니다.
fetch 함수로 Node.js 서버와 통신해서 로그인과 회원가입을 진행합니다.
mode라는 이름의 state를 이용해서 로그인/회원가입/로그인 성공 페이지를 전환합니다.
처음 페이지가 로드될 때, 로그인 여부를 체크한 다음,
로그인되어있으면 mode를 'WELCOME'으로, 아니면 'LOGIN'으로 설정합니다.
로그아웃은 /logout 페이지로 이동하는 방식으로 구현되어 있습니다.
App.css
@import url(http://fonts.googleapis.com/earlyaccess/notosanskr.css);
body {
font-family: 'Noto Sans KR', sans-serif;
background-color: rgb(150, 209, 148);
margin: 50px;
}
.background {
background-color: white;
height: auto;
width: 90%;
max-width: 450px;
padding: 10px;
margin: 0 auto;
border-radius: 5px;
box-shadow: 0px 40px 30px -20px rgba(0, 0, 0, 0.3);
text-align: center;
}
.form {
display: flex;
padding: 30px;
flex-direction: column;
}
.login {
border: none;
border-bottom: 2px solid #D1D1D4;
background: none;
padding: 10px;
font-weight: 700;
transition: .2s;
width: 75%;
}
.login:active,
.login:focus,
.login:hover {
outline: none;
border-bottom-color: rgb(68, 112, 67);
}
.btn {
border: none;
width: 75%;
background-color: rgb(68, 112, 67);
color: white;
padding: 15px 0;
font-weight: 600;
border-radius: 5px;
cursor: pointer;
transition: .2s;
}
.btn:hover {
background-color: rgb(49, 82, 48);
}
App.js의 페이지를 꾸며주는 css 파일입니다.
db.js
var mysql = require('mysql2');
var db = mysql.createConnection({
host: '',
user: '',
password: '',
database: ''
});
db.connect();
module.exports = db;
MySQL에 접속하기 위한 정보를 담고 있는 db.js 파일입니다. mysql2 모듈을 사용하고 있으나 mysql 모듈을 사용하셔도 변경하실 내용은 없습니다. 본인이 원하는 대로 host, user, password, database의 내용을 채워서 사용하시면 됩니다.
sessionOption.js
// MySQL에 저장할 세션의 옵션
var options = {
host: '',
user: '',
password: '',
database: '',
port: 3306,
clearExpired : true , // 만료된 세션 자동 확인 및 지우기 여부
checkExpirationInterval: 10000, // 만료된 세션이 지워지는 빈도 (milliseconds)
expiration: 1000*60*60*2, // 유효한 세션의 최대 기간 2시간으로 설정 (milliseconds)
};
module.exports = options;
MySQL에 세션을 저장하기 위한 옵션을 저장하는 파일입니다.
db.js와 마찬가지로 원하는 대로 host, user, password, database의 내용을 채워서 사용하시면 됩니다.
세션의 지속시간 등은 편의에 따라 수정하시면 됩니다.
db.js와 sessionOption.js을 별도의 파일로 저장하는 이유는
두 파일이 저장하는 내용이 보안에 민감한 내용이기 때문입니다.
github 등을 이용하여 소스코드를 push 할 경우, database의 비밀번호 등이 github에 업로드되는 것을 방지하기 위해
파일을 분리하여 작성한 다음, .gitignore에 db.js와 sessionOption.js을 추가하면 github 등에 업로드 되지 않습니다.
실행하기
npm run build
node server.js
위의 소스코드를 모두 작성한 다음,
npm run build를 이용해서 리액트 소스를 빌드하면 build 폴더에 index.html 등의 파일이 생성됩니다.
그런 다음 node server.js를 입력하여 서버를 실행시키면 위의 코드들로 구현된 로그인/회원가입 페이지가
http://localhost:3001/ 에서 열리게 됩니다.
해당 프로젝트는 아래 깃허브에도 업로드해놓았으니 참고하셔도 좋습니다. (lib 폴더 내 파일은 업로드 X)
https://github.com/Jw705/react-nodejs-login
React를 사용하였다는 점을 제외하면 기존에 작성한 아래 게시글과 겹치는 내용이 많습니다.
React 없이 구현을 원하시면 참고해 주세요
(MySQL, Express 등에 대해 참고할 포스트역시 아래 게시글에서 확인할 수 있습니다)
https://sirius7.tistory.com/59
그 외에 React의 사용법 등에 대해서 제가 작성한 포스트를 참고하셔도 좋습니다.
(부정확한 내용이 많아 권장하지는 않습니다)
https://sirius7.tistory.com/95
https://sirius7.tistory.com/97
https://sirius7.tistory.com/100
'개발일지 > ✅ 예약 시스템' 카테고리의 다른 글
Node.js와 MySQL을 이용한 로그인/회원가입 예제 (소스코드) (9) | 2022.08.09 |
---|