우선, Login.js에 있던 API부터 리팩토링을 진행
async function loginSubmit(e) {
e.preventDefault();
// 데이터베이스의 암호화된 비밀번호를 대입 예정
let SALT_pw;
// 폼에 입력한 ID를 세션 스토리지에 저장 (회원정보 띄울용)
sessionStorage.setItem("ID", id);
try {
await axios.get("api/pw").then((res) => {
console.log("입력한 아이디", id);
{
for (let i = 0; i < res.data.length; i++) {
// 아이디에 해당하는 암호화된 비밀번호를 가져와 SALT_pw에 대입
// 닉네임을 기입한 유저라면 닉네임도 함께 가져와 NickName에 대입
res.data[i].아이디 == id && (SALT_pw = res.data[i].패스워드);
res.data[i].아이디 == id && (NickName = res.data[i].닉네임);
}
}
sessionStorage.setItem("Nickname", NickName);
// bcrypt.compare를 위해 암호화 비밀번호와 폼입력 비밀번호 둘 모두 전달
const body = {
id: id,
pw: SALT_pw,
form_pw: pw,
};
axios.post("api/login", body).then((res) => {
if (res.data == "아이디미존재") {
alert("아이디를 잘못 입력하셨습니다.");
} else if (res.data == "비번미존재") {
alert("비밀번호가 틀렸습니다.");
} else {
// 세션에 토큰 저장
// res.data = 서버로 부터 전송된 Json Web Token
sessionStorage.setItem("JWT", res.data);
// 토큰 발급 안 됐을시 제자리
{
sessionStorage.JWT != null && navigate("/main");
}
// 이렇게 하면 헤더가 사라지긴 하는데 로그아웃엔 헤더 사라지지 않음
location.reload();
}
});
});
} catch (err) {
console.log(err);
}
}
async function loginSubmit(e) {
e.preventDefault();
// 아이디 존재여부 확인
try {
const response = await axios.get("api/pw");
const data = response.data;
const user = data.find((item) => item.아이디 === id);
if (!user) {
alert("존재하지 않는 아이디입니다.");
return;
}
// /api/pw 로부터 전달된 암호화된 패스워드, 닉네임
const { 패스워드, 닉네임 } = user;
sessionStorage.setItem("Nickname", 닉네임);
const body = {
id: id,
pw: 패스워드,
form_pw: pw,
};
const loginResponse = await axios.post("api/login", body);
const loginData = loginResponse.data;
if (loginData === "아이디미존재") {
alert("아이디를 잘못 입력하셨습니다.");
} else if (loginData === "비번미존재") {
alert("비밀번호가 틀렸습니다.");
} else {
sessionStorage.setItem("JWT", loginData);
{
sessionStorage.JWT != null && navigate("/main");
}
// location.reload();
}
} catch (error) {
console.log(error);
}
}
기존 코드를 보면, 메서드를 활용하지 않은 불필요한 반복문 그리고 어지러운 변수명들을 볼 수 있다.
const user = data.find((item) => item.아이디 === id);
메서드를 활용하여 간단하게 바꾼 후 user란 변수에 일치하는 데이터들을 모두 담아온 후
/api/pw 로부터 전달된 암호화된 패스워드, 닉네임
const { 패스워드, 닉네임 } = user;
디스트럭쳐링 할당을 통해 저장, 그 후 닉네임은 session 토큰에 저장해 헤더에 띄운다, 그 후로 이제 login API를 통해 본격적으로 로그인을 할 차례
// 로그인
app.post("/api/login", function (요청, 응답) {
db.collection("login").findOne({ 아이디: 요청.body.id }, function (에러, 아이디결과) {
// if (에러) return console.log(에러);
if (아이디결과) {
db.collection("login").findOne({ 패스워드: 요청.body.pw }, function (에러, 비번결과) {
// console.log("폼 입력 비번", 요청.body.form_pw);
// console.log("암호화 비번", 요청.body.pw);
if (비번결과) {
// 폼 입력 비번을 암호화 된 비밀번호와 Compare
bcrypt.compare(요청.body.form_pw, 요청.body.pw).then((result) => {
if (result) {
jwt.sign({ foo: "bar" }, "secret-key", { expiresIn: "1d" }, (err, token) => {
if (err) res.status(400).json({ error: "에러요" });
// console.log(token);
// 생성된 토큰 전송
응답.json(token);
});
} else 응답.json("비번미존재");
});
}
});
} else {
응답.json("아이디미존재");
}
});
});
기존 코드인데 보시다시피 JWT도 겉맛 보이게 되어있고 여러가지 조건문을 통해 가독성이 매우 안 좋다.
을 진행하면 다음과 같다.
// 로그인
app.post("/api/login", async function (req, res) {
try {
// 입력한 아이디 조회
const resultId = await db.collection("login").findOne({ 아이디: req.body.id });
if (resultId) {
// 입력한 패스워드로 사용자의 패스워드 조회
const resultPw = await db.collection("login").findOne({ 패스워드: req.body.pw });
if (resultPw) {
// 입력한 폼 비밀번호와 저장된 암호화 비밀번호 비교
const result = await bcrypt.compare(req.body.form_pw, req.body.pw);
if (result) {
// 인증 성공시 JWT 토큰 생성
jwt.sign({ id: resultId._id, nickname: resultId.nickname }, secretKey, { expiresIn: "1d" }, (err, token) => {
if (err) {
console.log(err);
res.status(500).json({ error: "서버 에러 발생" });
} else {
// 토큰 전달
res.json(token);
}
});
} else {
// 비밀번호 미일치
res.json("Not_Exist_Pw");
}
}
} else {
// 아이디 미존재
res.json("Not_Exist_Id");
}
} catch (err) {
console.log(err);
res.status(500).json({ error: "서버 에러 발생" });
}
});
비동기처리를 통해 더욱 가독성을 높이고 JWT키를 정상적으로 발급하였다.
// JWT
const jwt = require("jsonwebtoken");
const secretKey = process.env.SECRET_KEY;
../lib/api에 API를 따로 저장
여기서 한 가지 색다른 걸 볼 수 있다.
auth.js는 React 함수 컴포넌트 내에서가 아닌 단순 JS파일이다.
이 js파일엔 navigate를 활용할 수 없으므로
Login.js에서 전달을 해줘야만한다.
이벤트 처리 : 사용자가 로그인 버튼을 클릭하거나 다른 이벤트가 발생했을 때 handleLogin 함수가 호출되게끔 이벤트 핸들러로 등록할 수 있기 때문. 그래서 사용자가 로그인 버튼을 누르거나 특정 키를 누를 때 로그인 요청이 처리됨
비동기 처리 : 로그인 요청은 비동기적으로 처리돼야 함. handleLogin 함수 안에서 loginSubmit 함수를 호출하고 await 키워드를 써서 비동기 처리를 할 수 있기 때문에 로그인 요청이 완료되기 전에 다른 작업을 하지 않도록 할 수 있다.
코드를 모듈화하기 위해 : 로그인 처리를 담당하는 코드를 별도의 함수로 분리해서 모듈화할 수 있다. loginSubmit 함수는 로그인 요청에 관련된 로직을 처리하고, handleLogin 함수는 이벤트 핸들링과 loginSubmit 함수 호출을 담당 이렇게 해서 코드를 구조화하고 유지보수하기 쉽게 만들 수 있기 때문.
그래서 handleLogin 함수를 통해 로그인 요청을 보내는 건 이벤트 처리, 비동기 처리, 코드 모듈화 등을 고려한 설계적인 선택임
/* eslint-disable */
import { Link, useNavigate } from "react-router-dom";
import React, { useState } from "react";
import axios from "axios";
import logo from "../logo.png";
import { loginSubmit } from "../lib/api/auth";
function Login() {
let [id, setId] = useState("");
let [pw, setPw] = useState("");
const navigate = useNavigate();
const idHandler = (e) => {
e.preventDefault();
setId(e.target.value);
};
const pwHandler = (e) => {
e.preventDefault();
setPw(e.target.value);
};
const handleLogin = async (e) => {
e.preventDefault();
// 로그인 처리
await loginSubmit(id, pw, navigate);
};
return (
<div className="container col-md-8 col-sm-12 col-xs-12 m-2 bg-white rounded position-absolute top-50 start-50 translate-middle rounded-8 shadow-lg">
<div className="row p-5">
<div className="col-lg-8 col-12 mx-auto bg-white">
<div className="m-2 text-center">
<a href="/">
<img src={logo} className="img-fluid" alt="내일 지구가 끝나더라도 나는 오늘 밤 최고의 술자리를 가지겠어" width="300" />
</a>
</div>
<div className="p-2">
<div className="border rounded m-3 p-3">
<form onSubmit={handleLogin}>
<label className="p-3 font-500">ID</label>
<input type="text" className="form-control form-control-lg mb-3 rounded-pill" placeholder="아이디를 입력하세요." value={id} onChange={idHandler} />
<label className="p-3 font-500">Password</label>
<input type="password" className="form-control form-control-lg rounded-pill" placeholder="비밀번호를 입력하세요." value={pw} onChange={pwHandler} />
<div className="d-grid gap-2 col-11 mx-auto">
<button className="btn btn-lg press_btn mt-5" type="submit">
LOGIN
</button>
</div>
</form>
<div className="text-center pt-4">
<p className="m-3 text-secondary font-500">
아직 계정이 없으신가요?{" "}
<Link className="text-dark font-500" to="/Signup">
회원가입
</Link>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Login;
[서버]
// 데이터베이스 암호화 비밀번호 전달 API
app.get("/api/pw", function (req, res) {
// 배열로 전달 login 데이터베이스 정보 전달
db.collection("login")
.find()
.toArray(function (err, result) {
if (err) {
console.log(err);
res.status(500).json({ error: "서버 에러 발생" });
} else {
res.json(result);
// console.log(result);
}
});
});
// 로그인
app.post("/api/login", async function (req, res) {
try {
// 입력한 아이디 조회
const resultId = await db.collection("login").findOne({ 아이디: req.body.id });
if (resultId) {
// 입력한 패스워드로 사용자의 패스워드 조회
const resultPw = await db.collection("login").findOne({ 패스워드: req.body.pw });
if (resultPw) {
// 입력한 폼 비밀번호와 저장된 암호화 비밀번호 비교
const result = await bcrypt.compare(req.body.form_pw, req.body.pw);
if (result) {
// 인증 성공시 JWT 토큰 생성
jwt.sign({ id: resultId._id, nickname: resultId.nickname }, secretKey, { expiresIn: "1d" }, (err, token) => {
if (err) {
console.log(err);
res.status(500).json({ error: "서버 에러 발생" });
} else {
// 토큰 전달
res.json(token);
}
});
} else {
// 비밀번호 미일치
res.json("Not_Exist_Pw");
}
}
} else {
// 아이디 미존재
res.json("Not_Exist_Id");
}
} catch (err) {
console.log(err);
res.status(500).json({ error: "서버 에러 발생" });
}
});