250212 포트폴리오 프로젝트(29)

geenee·2025년 2월 12일
0

Portfolio

목록 보기
35/38
post-thumbnail

더이상 미룰 수 없다 나의 개인프로젝트 디벨롭...
퇴사 후 2024/12~2025/02 동안 코드 수정한 부분들 정리해봄

코드 리팩토링

로그인 세션 로직 수정

코드를 다시 보니 어떻게 짰는지 기억도 안나고...그냥 한숨만...ㅎ
기존에는 세션에 냅다 사용자 정보를 저장해서 썼었는데 로그인 했을때 세션처리하는 부분 수정함

  1. 로그인 됐는지 true/false로 확인
  2. 사용자 정보 요청 API 추가

이때도 계속 nodejs에 세션이 저장 안됐는데 이유가 config.js에 로컬주소를 localhost가 아니라 127.0.0.1로 적어서 안됐었음...ㅋ 이거때문에 몇날며칠을 구글링하고 오만거 다해봤는데ㅠㅠ 허탈했다..^^

App.js 코드도 요상했는데 로그인 세션 확인해서 true면 사용자 정보를 가져와 모든 페이지에서 사용할 수 있도록 context를 사용하는 코드로 수정했다!

API 분리

페이지 코드마다 axios로 백엔드에 요청하는 부분이 있는데 그 부분을 분리하였음
중복되는 요청과 코드 수정을 용이하게 하기 위해서...!
api 폴더를 만들어주고 페이지 별로 사용되는 api를 함수화 하였음~

import axios from "axios";
import { BASE_URL } from "../config";

axios.defaults.withCredentials = true;

/**
 * 로그인/로그인세션/회원탈퇴
 */

// 로그인 요청
export const getLogin = async (user_id, user_pw) => {
  try {
    const response = await axios({
      url: `${BASE_URL}/auth/login`,
      method: "POST",
      data: {
        id: user_id,
        pw: user_pw,
      },
    });

    console.log("로그인 : ", response.data);
    return response.data;
  } catch (error) {
    console.error("로그인 실패:", error);
    return { code: "error", message: "axios error", redirect: "" };
  }
};

// 로그인 세션 확인
export const getLoginSession = async () => {
  try {
    const response = await axios(`${BASE_URL}/auth/authcheck`);
    console.log("로그인 세션 : ", response.data.isLogin);

    return response.data.isLogin;
  } catch (error) {
    console.error("로그인 세션 가져오기 실패:", error);
    return false;
  }
};

// 로그아웃 요청
export const getLogout = async () => {
  try {
    const response = await axios(`${BASE_URL}/auth/logout`);
    console.log("로그아웃 세션 : ", response.data.isLogin);
    return response.data.isLogin;
  } catch (error) {
    console.error("로그아웃 실패 :", error);
    return false;
  }
};

...

코드 일부 이런식으로 정리함

import React, { useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
// import context
import { SessionContext } from "../App";
// import api
import { getLogin } from "../api/Auth";
// import src
import logoimg from "../img/logo_oco.png";
// import css
import "./Login.css";

function Login() {
  const { loginSession, setLoginSession } = useContext(SessionContext);

  const navigation = useNavigate();
  const restapikey = process.env.REACT_APP_KAKAO_RESTAPI_KEY;
  const redirect_uri = process.env.REACT_APP_KAKAO_REDIRECT_URI;
  // const jskey = process.env.REACT_APP_KAKAOMAP_API_KEY;

  const kakaosnsloginlink = `https://kauth.kakao.com/oauth/authorize?client_id=${restapikey}&redirect_uri=${redirect_uri}&response_type=code`;

  const kakaologinHandler = () => {
    window.location.href = kakaosnsloginlink;
  };

  const [userlogin, setUserlogin] = useState({
    id: "",
    pw: "",
    msg1: "",
    msg2: "",
  });

  const IdvaluechangeHandler = (event) => {
    const data = event.target.value;
    if (data.length <= 12) {
      setUserlogin((prevState) => {
        return { ...prevState, id: data, msg1: "" };
      });
    }
  };

  const PwvaluechangeHandler = (event) => {
    const data = event.target.value;
    if (data.length <= 20) {
      setUserlogin((prevState) => {
        return { ...prevState, pw: data, msg2: "" };
      });
    }
  };

  const onSubmit = async (event) => {
    event.preventDefault();

    if (!userlogin.id) {
      setUserlogin((prevState) => {
        return { ...prevState, msg1: "아이디를 입력하세요.", msg2: "" };
      });
      return;
    }

    if (!userlogin.pw) {
      setUserlogin((prevState) => {
        return { ...prevState, msg2: "비밀번호를 입력하세요.", msg1: "" };
      });
      return;
    }

    const { code, message, redirect } = await getLogin(
      userlogin.id,
      userlogin.pw
    );

    if (code === "error") {
      alert(message);
      return;
    }

    setLoginSession(true);
    navigation(redirect);
  };

  return (
    <>
      {loginSession === false && (
        <div className="login-container">
          <form onSubmit={onSubmit} className="loginbox">
            <div className="first">
              <img
                src={logoimg}
                alt="logo이미지"
                onClick={() => navigation("/")}
              />
              <span className="title">로그인</span>
            </div>
            <div className="inputbox">
              <input
                type="text"
                maxLength={12}
                value={userlogin.id}
                name="id"
                placeholder="아이디"
                className="item"
                onChange={IdvaluechangeHandler}
              />
              <span className="msg">{userlogin.msg1}</span>
              <input
                type="password"
                maxLength={20}
                value={userlogin.pw}
                name="pw"
                placeholder="비밀번호"
                className="item"
                onChange={PwvaluechangeHandler}
              />
              <span className="msg">{userlogin.msg2}</span>
              <button className="loginbtn" type="submit">
                로그인
              </button>
            </div>
            <div className="more">
              <span onClick={() => navigation("/join")}>회원가입</span>
              <span onClick={() => navigation("/userfind", { state: "id" })}>
                아이디 찾기
              </span>
              <span onClick={() => navigation("/userfind", { state: "pw" })}>
                비밀번호 찾기
              </span>
            </div>
            <div className="hr-sect">SNS 간편 로그인</div>
            <div className="snslogin">
              <button type="button" onClick={kakaologinHandler}>
                카카오로 로그인하기
              </button>
            </div>
          </form>
        </div>
      )}
    </>
  );
}

export default Login;

페이지에서는 import하여 api 함수 사용

PrivateRoute 적용

로그인 세션에 따른 접근이 가능한 페이지 확인하는 로직도 이상했는데(ㅋㅋ)
구글링을 해보니 내가 요상하게 쓰던 방식이랑 대충 비슷한 로직으로 사용되는 방법 발견!

import React, { useContext } from "react";

import { SessionContext } from "../App";
import { Outlet, Navigate } from "react-router-dom";

const PublicRoutes = () => {
  const { loginSession, setLoginSession } = useContext(SessionContext);

  return loginSession ? <Navigate to="/" /> : <Outlet />;
};

export default PublicRoutes;

로그인 세션이 있으면 접근 할 수 없는 페이지 처리

import React, { useContext } from "react";

import { SessionContext } from "../App";
import { Outlet, Navigate } from "react-router-dom";

const PrivateRoutes = () => {
  const { loginSession, setLoginSession } = useContext(SessionContext);

  return loginSession ? <Outlet /> : <Navigate to="/login" />;
};

export default PrivateRoutes;

로그인 세션이 없으면 접근 할 수 없는 페이지 처리

import React, { useEffect } from "react";
import { Route, Routes } from "react-router-dom";
// import css
import "./App.css";
// import component
import Join from "./pages/Join";
import Login from "./pages/Login";
import UserFind from "./pages/my/UserFind";
import Main from "./pages/Main";
import Howtouse from "./pages/Howtouse";
import Mymate from "./pages/my/Mymate";
import Mytrip from "./pages/my/Mytrip";
import My from "./pages/my/My";
import Plan from "./pages/plan/Plan";
import Empty from "./pages/Empty";
import PrivateRoutes from "./utils/PrivateRoutes";
import PublicRoutes from "./utils/PublicRoutes";
// import api
import { getLoginSession } from "./api/Auth";

export const SessionContext = React.createContext({});

function App() {
  const seq = "";
  const [loginSession, setLoginSession] = React.useState();

  // 로그인 세션 정보 가져오기
  useEffect(() => {
    const fetchLoginSession = async () => {
      setLoginSession(await getLoginSession());
    };

    fetchLoginSession();
  }, []);

  return (
    <SessionContext.Provider
      value={{
        loginSession,
        setLoginSession,
      }}
    >
      {loginSession !== undefined && (
        <Routes>
          <Route exact path="/" element={<Main />} />
          <Route exact path="/howtouse" element={<Howtouse />} />

          <Route element={<PublicRoutes />}>
            <Route exact path="/join" element={<Join />} />
            <Route exact path="/login" element={<Login />} />
            <Route exact path="/userfind" element={<UserFind />} />
          </Route>

          <Route element={<PrivateRoutes />}>
            <Route exact path="/mymate" element={<Mymate />} />
            <Route exact path="/mytrip" element={<Mytrip />} />
            <Route exact path="/mytrip/:seq" element={<Plan seq={seq} />} />
            <Route exact path="/my" element={<My />} />
          </Route>

          <Route path="*" element={<Empty />} />
        </Routes>
      )}
    </SessionContext.Provider>
  );
}

export default App;

App.js에서 각각 public한 페이지 private한 페이지를 정의해주면 된다

각 페이지 코드 정리

로그인 세션과 사용자 정보를 받아오는 부분이 바뀌어서 페이지 전체를 수정해야했다..ㅎ
한 페이지씩 확인하면서 유저 정보 context를 받아오고 그에 맞춰 코드 흐름을 정리했다
입력값 있는 곳은 form 태그로 바꿔주고(이런 기본적인것도 안해놨던 코드 실화냐)
좀 복잡하게 컴포넌트도 많고 모달이 있어서 뭔가 코드들이 연결되어있는 곳은 최대한 냅뒀다..
그래서 코드가 완전 이쁘게 정리된것은 아님ㅠㅠ 괜히 건드렸다가..안되면 곤란해..~

이렇게 보면 크게 한게 없는데... 왜 이렇게 시간은 오래 걸렸을까...ㅎ?
이정도 퀄리티면 안될 것 같아서 소셜 로그인까지 추가하고있는데 머리 터질 것 같음..
상태관리툴까지 하려고 했던건 내 욕심이었다...쿡...

일단 소셜로그인까지만 해보고 포폴 만들어야지...
그래도 취업 안되면 프로젝트하나 더 추가하고 상태관리툴까지 해보는거루...하하

소셜로그인(카카오)

제일 최근 게시글 보면 알겠지만 axios로는 죽어도 cors 오류가 해결되지 않고
ajax로 요청 보낼 때 토큰이랑 사용자 정보를 받아 올 수 있었는데
이 문제는 아직도 해결하지 못했음...^^ 그래서 포폴에는 기능도 뺐었음...
구글링해서 게시글을 삼천개는 본 것 같은데 다 똑같음..다 axios로 토큰 요청하거나 인가코드 받아와서 백엔드로 넘겨주면 토큰 리턴..(근데 나는 그 백엔드도 내가 해야한다는 점;;)
도대체가 왜 kauth.kakao.com/oauth/token 주소로 요청할 때 cors 에러가 나는걸까..?
다 뒤져봤는데 kauth는 cors도 열려있다는데;;;;진짜 도저히 이건 해결이 안될 것 같아서 다른 방법으로 하기로 했다..

import React, { useEffect } from "react";
import axios from "axios";
import $ from "jquery";
import qs from "qs";

axios.defaults.withCredentials = true;

function KakaoRedirect() {
  const code = new URL(window.location.href).searchParams.get("code");

  const getKAKAO = async () => {
    const params = {
      grant_type: "authorization_code",
      client_id: process.env.REACT_APP_KAKAO_RESTAPI_KEY,
      redirect_uri: process.env.REACT_APP_KAKAO_REDIRECT_URI,
      code: code,
    };

    // axios({
    //   method: "POST",
    //   headers: {
    //     "content-type": "application/x-www-form-urlencoded;charset=utf-8",
    //   },
    //   url: "https://kauth.kakao.com/oauth/token",
    //   data: params,
    // })
    //   .then((res) => {
    //     console.log(res);
    //   })
    //   .catch((e) => console.log(e));

    $.ajax({
      async: false,
      type: "POST",
      url: "https://kauth.kakao.com/oauth/token",
      data: params,
      beforeSend: function (xhr) {
        xhr.setRequestHeader(
          "Content-type",
          "application/x-www-form-urlencoded;charset=utf-8"
        );
      },
      success: function (res) {
        console.log(res);

        // kakao Javascript SDK 초기화
        window.Kakao.init(process.env.REACT_APP_KAKAO_RESTAPI_KEY);
        window.Kakao.Auth.setAccessToken(res.access_token);
      },
    });

    const kakaoData = await window.Kakao.API.request({
      url: "/v2/user/me",
    });
    console.log("정보불러오기", kakaoData);
  };

  useEffect(() => {
    getKAKAO();
  }, []);

  return <div>카카오데이터받는곳</div>;
}

export default KakaoRedirect;

이렇게 ajax로 보내면(이거도 그당시 어떻게든 구글링해서 토큰 받아오게 만든 코드엿삼;;)

토큰과 사용자 정보가 잘 온다

근데 주석처리된 axios 부분으로 요청을 보내면

아 진짜 뭐 어쩌라고....짜증나....cors 내가 어떻게 해결해야하는데...
도대체 뭐가 문제일까 구글링하면 다 이렇게 보내는데 axios 형태도 다 바꿔서 해봄(저렇게만 해본거 아님)
왜 나만 안되는거야아아아아아아ㅏㅏ아아아아아아ㅏ아앙ㅇ아아ㅏ
제발 이 문제에 대한 해결책이나 경험이 있으신분은 알려주세요....ㅠㅠ

어쨌든 각설하고 프론트에서 토큰을 가져오는 방법은 포기하고(사실 되긴 되는거긴한데...저기만 뜬금없이 ajax 쓰는게 마음에 안들고 몰라 그냥 axios 안되는게 짜증남 코드 통일성이 없게 느껴진달까 ㅎ)
백엔드에서 토큰과 사용자 정보를 가져올 수 있는 방법으로 도전을 해보았다..
[React/Nodejs] 카카오 로그인 연결하기
이 글을 보고 3번을 시도

1.내 애플리케이션>제품 설정>카카오 로그인>Redirect URI에 URL 작성
2.redirect_uri는 인가코드 요청 시 입력하는 url = 토큰 요청 시 입력하는 url이 같아야한다

나는 2번에서 프론트 주소로 인가코드 요청은 잘 되는데 토큰 요청 시 안되는거엿음
그래서 인가코드&토큰 요청하는 주소를 백엔드 주소로 바꿈

Login.js (리액트 프론트)

  const restapikey = process.env.REACT_APP_KAKAO_RESTAPI_KEY;
  const redirect_uri = process.env.REACT_APP_KAKAO_REDIRECT_URI;
  // const jskey = process.env.REACT_APP_KAKAOMAP_API_KEY;

  const kakaosnsloginlink = `https://kauth.kakao.com/oauth/authorize?client_id=${restapikey}&redirect_uri=${redirect_uri}&response_type=code`;

  const kakaologinHandler = () => {
    window.location.href = kakaosnsloginlink;
  };

카카오 로그인 버튼을 누르면 파라미터 설정한 대로
localhost:5000/auth/kakaologin?code=코드값 이렇게 백엔드 주소로 연결되고

auth.js (nodejs 백엔드)

router.get("/kakaologin", async (req, res) => {
  const code = req.query.code;
  const KAKAO_OAUTH_TOKEN_API_URL = "https://kauth.kakao.com/oauth/token";
  const KAKAO_GRANT_TYPE = "authorization_code";
  const KAKAO_CLIENT_id = process.env.REACT_APP_KAKAO_RESTAPI_KEY;
  const KAKAO_REDIRECT_URL = process.env.REACT_APP_KAKAO_REDIRECT_URI;

  try {
    // 카카오 로그인 유저 토큰 요청
    axios
      .post(
        `${KAKAO_OAUTH_TOKEN_API_URL}?grant_type=${KAKAO_GRANT_TYPE}&client_id=${KAKAO_CLIENT_id}&redirect_uri=${KAKAO_REDIRECT_URL}&code=${code}`,
        {
          headers: {
            "Content-type": "application/x-www-form-urlencoded;charset=utf-8",
          },
        }
      )
      .then((response) => {
        // console.log(response.data);
        const access_token = response.data.access_token;
        const refresh_token = response.data.refresh_token;
        const scope = response.data.scope;

        // 사용자 정보 요청
        axios
          .get(`https://kapi.kakao.com/v2/user/me`, {
            headers: {
              Authorization: `Bearer ${access_token}`,
            },
          })
          .then((result) => {
            console.log("유저 정보 : ", result.data);

            // 유저 DB 조회
          })
          .catch((e) => console.log(e));

        // 리다이렉트 될 주소
        res.redirect("http://localhost:3000/");
      })
      .catch((e) => {
        console.log(e);
        res.send(e);
      });
  } catch (e) {
    console.log(e);
    res.send(e);
  }
});

백엔드에서는 code 값을 받아서 토큰과 사용자 정보를 요청할 수 있다
사용자 정보까지는 확인 함! 이제 이후 로직을 추가해주면 된다...
OAuth로 카카오 소셜 로그인 구현하기 - 1편
DB 조회해서 저장된 유저 정보가 없으면 저장해주고 로그인 세션 저장해주면 될듯?
회원정보에 필수인 값이 있는데 그건 사용자가 가입 시 선택을 안하면 안넘어오기때문에 그부분은 어떻게 처리할 지 고민임...

profile
코딩 공부 기록용

0개의 댓글