[리팩토링] 햄릿 증후군을 위한 술자리 결정 웹 서비스 #2 로그인

세나정·2023년 5월 23일
0
post-thumbnail

로그인

로그인 API (loginSumit 수정)

우선, 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를 통해 본격적으로 로그인을 할 차례

로그인 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도 겉맛 보이게 되어있고 여러가지 조건문을 통해 가독성이 매우 안 좋다.

  1. JWT의 정상 동작 (.env 파일을 활용한 mysecretkey 발급)
  2. 가독성 향상 (변수명 수정)

을 진행하면 다음과 같다.

// 로그인
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;

API 분할 (auth.js)

../lib/api에 API를 따로 저장

특이점

여기서 한 가지 색다른 걸 볼 수 있다.

왜 navigate 함수까지 파라미터로 받아와서 활용하나요?

auth.js는 React 함수 컴포넌트 내에서가 아닌 단순 JS파일이다.
이 js파일엔 navigate를 활용할 수 없으므로

Login.js에서 전달을 해줘야만한다.

handleLogin을 써서 요청을 보내는 이유

  1. 이벤트 처리 : 사용자가 로그인 버튼을 클릭하거나 다른 이벤트가 발생했을 때 handleLogin 함수가 호출되게끔 이벤트 핸들러로 등록할 수 있기 때문. 그래서 사용자가 로그인 버튼을 누르거나 특정 키를 누를 때 로그인 요청이 처리됨

  2. 비동기 처리 : 로그인 요청은 비동기적으로 처리돼야 함. handleLogin 함수 안에서 loginSubmit 함수를 호출하고 await 키워드를 써서 비동기 처리를 할 수 있기 때문에 로그인 요청이 완료되기 전에 다른 작업을 하지 않도록 할 수 있다.

  3. 코드를 모듈화하기 위해 : 로그인 처리를 담당하는 코드를 별도의 함수로 분리해서 모듈화할 수 있다. 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: "서버 에러 발생" });
  }
});
profile
압도적인 인풋을 넣는다면 불가능한 것은 없다. 🔥

0개의 댓글