๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป ํ”„๋กœ์ ํŠธ/๐Ÿ ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๐Ÿ 

[Spring, Vue.js] ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ๋งŒ๋“ค๊ธฐโ‘ก - Vue๋กœ ํ”„๋ก ํŠธ์—”๋“œ ์ž‘์„ฑํ•˜๊ธฐ (REST API)

by hyeong._.ing 2025. 9. 27.

 

 

 

[Spring,Vue.js] ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ๋งŒ๋“ค๊ธฐ โ‘  - ๊ตฌ์กฐ์™€ ํ๋ฆ„ ํŒŒ์•…ํ•˜๊ณ  ์นด์นด์˜ค ๋””๋ฒจ๋กœํผ์Šค ์„ค์ •ํ•˜๊ธฐ

์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ์„ ๋งŒ๋“ค์–ด๋ณด์ž!๋จผ์ € ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค์–ด์•ผํ•˜๋Š”์ง€ ๊ตฌ์กฐ์™€ ํ๋ฆ„์„ ํŒŒ์•…ํ•˜๊ณ ์นด์นด์˜ค ๋””๋ฒจ๋กœํผ์Šค๋ฅผ ์„ค์ •ํ•˜์ž. 1. ์นด์นด์˜ค ๋””๋ฒจ๋กœํผ์Šค - ์นด์นด์˜ค API ์‹œ์ž‘ํ•˜๊ธฐ Kakao Developers์นด์นด์˜ค API๋ฅผ ํ™œ

post-this.tistory.com

 

 

 

 

์นด์นด์˜ค ๋””๋ฒจ๋กœํผ์Šค์—์„œ ํ•„์š”ํ•œ ๊ฑด ๋ชจ๋‘ ์„ค์ •ํ–ˆ๋‹ค.
์ด์ œ ์„ค์ •ํ•œ ๊ฑธ ํ† ๋Œ€๋กœ, ํ”„๋ก ํŠธ๋ฅผ ์ž‘์„ฑํ•ด ๋ณด์ž!

 

 

 

 

1. ํ™”๋ฉด

  • ๋กœ๊ทธ์ธ 

 

  • ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ 

 

 

 

 

 


 

 

 

2. ์ „์ฒด ์ฝ”๋“œ 

  • LoginView.vue
<script>

import axios from "axios";

export default {
  name: 'LoginView',
  data() {
    return {
      userId: "",
      pw: ""
    };
  },
  methods: {
    async login() {
      try {
        const res = await axios.post("http://localhost:8080/api/login", {
          userId: this.userId,
          pw: this.pw
        });
        localStorage.setItem("displayName", this.userId());
        alert("๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        this.$router.replace("/main");
      } catch (e) {
        alert(e.response.data);
      }
    },
    // ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์ฐฝ์ด ๋ณด์ด๋„๋ก ํ•˜๋Š” ๋ฉ”์„œ๋“œ
    kakaoLogin() {
      // REST_API_KEY๋Š” ๋”ฐ๋กœ ์ €์žฅ ํ•ด๋‘ . ๊ทธ ์œ„์น˜๋ฅผ ์ ์—ˆ์Œ
      const REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY;
      //${window.location.origin}์€ ์ง€๊ธˆ ์ด ํŽ˜์ด์ง€๊ฐ€ ๋œฌ ์ถœ์ฒ˜๋ฅผ ์ž๋™์ ์œผ๋กœ ๋„ฃ๋Š”๋‹ค.
      // ์„ค์ •ํ•ด๋‘” REDIRECT_URI ๋„ฃ์–ด๋„ ์ƒ๊ด€์—†์Œ
      const REDIRECT_URI = `${window.location.origin}/oauth/code/kakao`;

      if (!REST_API_KEY) {
        alert('REST_API_KEY ๋น„์–ด์žˆ์Œ!');
        return;
      }

      // state ๊ฐ’ ์ƒ์„ฑํ•˜๊ณ  ๋ณด๊ด€ํ–ˆ๋‹ค.
      const state = btoa(String(Date.now()));
      // state ๋ณด๊ด€
      sessionStorage.setItem("kakao_oauth_state", state);

      // ๊ฐœ๋ฐœ์ž๊ฐ€ ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋‹ด์•ผํ•  ์ •๋ณด๋“ค
      const authUrl =
          `https://kauth.kakao.com/oauth/authorize?response_type=code` +
          `&client_id=${encodeURIComponent(REST_API_KEY)}` +
          `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
          `&state=${encodeURIComponent(state)}`;

      // authUrl๋กœ ํŽ˜์ด์ง€ ์ด๋™ํ•˜๊ธฐ
      window.location.href = authUrl;

    }
  }
}

</script>



<template>
  <div class="login-view">

    <div class="center-icon">
      <img src="../assets/wings.png" alt="๋‚ ๊ฐœ์•„์ด์ฝ˜" class="wings"
           @click="$router.push('/main')" style="cursor:pointer;"/>
      <div class="create">
        <p @click="$router.push('/main')">Sign In </p>
     </div>
   </div>

    <div class="login-container">
      <div class="id-box">
        <p>เท† ID </p>
        <input v-model="userId" @input="userId" type="text" name="userId" placeholder="id"/>
      </div>
      <div class="pw-box">
        <p>เท† PW </p>
        <input v-model="pw" type="password" name="password" placeholder="password"/>
      </div>
        <button class="login-text" @click="login"> SIGN IN </button>
    </div>

    <!-- ์นด์นด์˜ค ๋ฒ„ํŠผ -->
    <div class="kakao">
      <img src="../assets/kakao.png" alt="Kakao" @click="kakaoLogin"/>
    </div>

  </div>
</template>



<style>
.login-view {
  position: relative;
  width: 100vw;
  height: 100vh;
  display: flex;
  background: white;
  justify-content: center;
}

.center-icon{
  position: absolute;
  display: flex;
  top: 7%;
  flex-direction: column;
  align-items: center;
}

.wings{
  width: 60px;
  height: 60px;
}

.create {
  margin-top: -5px;
  font-size: 1.4rem;
  color: black;
  font-family: 'Cafe24Meongi-B-v1.0';
  letter-spacing: 2px;
  text-shadow: 1px 1px 5px #fff8;
}

.create p:hover{
  transition: color 0.2s, font-weight 0.2s;
  font-weight: bold;
  color: #00ff80;
  cursor: pointer;
}

.login-container {
  position: absolute;
  width: 33vw;
  height: 30vh;
  top: 25%;
  left: 50%;
  transform: translateX(-50%);
  flex-direction: column;
  justify-content: center;
  display: flex;
  background: black;
  border-radius: 30px;
  box-sizing: border-box;
}

.id-box{
  position: absolute;
  width: 33vw;
  height: 10vh;
  left: 18%;
  top: 10%;
  align-items: center;
  flex-direction: row;
  display: flex;
  gap: 9.3%;
  font-size: 1.0rem;
  font-weight: bold;
  font-family: 'GowunBatang-Regular', serif;
  color: white;
}
.id-box input{
  width: 15vw;
  height: 4vh;
  padding: 3px 13px 2px 4px;
  font-size: 1rem;
  transition: border-bottom 0.2s ;
}

.pw-box{
  position: absolute;
  width: 33vw;
  height: 10vh;
  left: 18%;
  top: 30%;
  align-items: center;
  flex-direction: row;
  display: flex;
  gap: 7.3%;
  font-size: 1.0rem;
  font-weight: bold;
  font-family: 'GowunBatang-Regular', serif;
  color: white;
}
.pw-box input{
  width: 15vw;
  height: 4vh;
  padding: 3px 13px 2px 4px;
  font-size: 1rem;
  transition: border-bottom 0.2s ;
}


.login-text {
  position: absolute;
  width: 20vw;
  height: 5.3vh;
  bottom: 17%;
  left: 50%;
  transform: translateX(-50%);
  font-size: 1rem;
  font-weight: bold;
  text-align: center;
  cursor: pointer;
  transition: background-color 0.4s ease, border 0.4s ease, color 0.4s ease;
}

.login-text:hover{
  background-color: #00ff80;
  border: #00ff80;
  color: white;
  -webkit-text-stroke: 0.8px black;
}

/* ์นด์นด์˜ค ๋ฒ„ํŠผ */
.kakao{
  position:  absolute;
  top:60%;
  left: 50%;
  transform: translateX(-50%);
  cursor: pointer;
}

</style>

 

 

  • KakaoCallback.vue
<script>
import axios from "axios";

export default {
  // ๋ฐฑ์—”๋“œ์—์„œ ํ† ํฐ ๊ตํ™˜์ด ์ด๋ค„์ง€๋„๋ก ๋ฐ›์•„์˜จ ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๋ณด๋‚ด๋Š” ์—ญํ• ์„ ํ•˜๋Š” ํด๋ž˜์Šค
  name: "KakaoCallback",
  async created() {
  
    // 1) ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์—์„œ code์™€ state ์ถ”์ถœํ•œ๋‹ค.
    // ์—ฌ๊ธฐ url์— ์ธ๊ฐ€์ฝ”๋“œ๊ฐ€ ๋“ค์–ด์žˆ๋‹ค. ์ธ๊ฐ€์ฝ”๋“œ๋ฅผ code ๋ถ€๋ถ„์—์„œ ๋นผ์˜ค๋Š” ๊ฒƒ!
    const url = new URL(window.location.href);
    // ?code=…์ด๋Ÿฐ ํ˜•ํƒœ๋กœ ๋˜์–ด์žˆ์–ด์„œ, code ๋’ค์˜ ๊ฐ’์„ ์ฝ๋Š” ๊ฒƒ์ด๋‹ค.
    const code = url.searchParams.get("code");
    const state = url.searchParams.get("state");

    // 2) state ๊ฒ€์ฆ
    // ๋ฐ›์•„์˜จ state๊ฐ€ saved์™€ ๋งž๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
    const saved = sessionStorage.getItem("kakao_oauth_state");

    // ํ•œ ๋ฒˆ ์“ด sessionStorage๋ฅผ ์žฌ์‚ฌ์šฉ ๋ชปํ•˜๋„๋ก ์ œ๊ฑฐํ•œ๋‹ค.
    sessionStorage.removeItem("kakao_oauth_state");

    // ์นด์นด์˜ค๊ฐ€ ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์•ˆ์คฌ๊ฑฐ๋‚˜ || ์นด์นด์˜ค๊ฐ€ state๋ฅผ ์•ˆ์คฌ๊ฑฐ๋‚˜ || ์šฐ๋ฆฌ๊ฐ€ ์ €์žฅํ•œ state๊ฐ€ ์—†๋‹ค๊ฑฐ๋‚˜ || ์นด์นด์˜ค๊ฐ€ ์ค€ state์™€ ์ €์žฅํ•œ state๊ฐ€ ๋‹ค๋ฅด๊ฑฐ๋‚˜
    if (!code || !state || !saved || state !== saved) {
      alert("์ž˜๋ชป๋œ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค. (state ๊ฒ€์ฆ ์‹คํŒจ)");
      // ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ๋Œ์•„๊ฐ€๋ฉฐ, ์‹คํŒจ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€์ง€ ์•Š๋Š”๋‹ค.
      this.$router.replace("/login");
      return;
    }

    try {
      // ์ด์ œ ํ† ํฐ์„ ๊ตํ™˜ํ•˜๋Š” ๊ฒƒ์€ spring์—์„œ ํ•ด์ค€๋‹ค. ๊ทธ๋ž˜์„œ code๋ฅผ ์Šคํ”„๋ง์œผ๋กœ post๋กœ ๋ณด๋‚ธ๋‹ค.
      const { data } = await axios.post("http://localhost:8080/oauth/kakao", { code });

      // displayname์— ๋ณด์ด๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด localStrage์— ์ €์žฅํ•ด์ค€๋‹ค.
      if (data?.nickname) {
        localStorage.setItem("displayName", data.nickname);
        alert(`ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, ${data.nickname}๋‹˜!`);
      } else {
        alert("๋‹‰๋„ค์ž„์„ ๋ฐ›์ง€ ๋ชปํ–ˆ์–ด์š”: " + JSON.stringify(data));
      }

      // ๋ฉ”์ธ์œผ๋กœ ์ด๋™
      this.$router.replace("/main");
    } catch (e) {
      console.error(e);
      alert(e?.response?.data ?? "์นด์นด์˜ค ๋กœ๊ทธ์ธ ์‹คํŒจ");
      this.$router.replace("/");
    }
  }
}

</script>

<template>

</template>

<style scoped>

</style>
๋‹ค ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์—์„œ ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ˆ˜์ •์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์–ด๋–ค ์ฝ”๋“œ์ธ์ง€ ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…์„ ์ ์–ด์™”์Šต๋‹ˆ๋‹ค. 

 

 

 

 


 

 

 

 

3. ์นด์นด์˜ค ๋””์ž์ธ, REST API KEY ๋ณด๊ด€ํ•จ

  • aasets

์นด์นด์˜ค ๋””์ž์ธ ๊ฐ€์ด๋“œ์—์„œ ๋‹ค์šด๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ src ํ•˜์œ„ ํด๋” assets์— ๋„ฃ์–ด ์ €์žฅํ•œ๋‹ค.

 

 

  • .env.local File ๋งŒ๋“ค๊ธฐ_ REST API KEY ๋ณด๊ด€์šฉ; ํ•ด๋„ ๋˜๊ณ  ์•ˆ ํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.

ํ•ด๋‹น ํŒŒ์ผ์€ ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋Š” ์œ„์˜ ์‚ฌ์ง„์—์„œ join-app์„ ๋œปํ•œ๋‹ค. join-app์— ์ปค์„œ๋ฅผ ๋Œ€๊ณ  ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅด๋ฉด File์ด ๋ณด์ผ ๊ฒƒ์ด๋‹ค. File์„ ๋ˆŒ๋Ÿฌ .env.local์„ ์ ์–ด์ค€๋‹ค. ๋ฐ˜๋“œ์‹œ ํŒŒ์ผ๋ช…๊ณผ ์œ„์น˜๋Š” ๊ฐ™์•„์•ผ ํ•œ๋‹ค.

 

VITE_KAKAO_REST_API_KEY = ๋ฐœ๊ธ‰ ๋ฐ›์€ REST API ํ‚ค๋ฅผ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค.
์ด๋ ‡๊ฒŒ ํ•ด๋„ ๋˜๊ณ , ๊ท€์ฐฎ์œผ๋ฉด ์ด๋”ฐ๊ฐ€ ์ธ๊ฐ€์ฝ”๋“œ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด ์ž‘์„ฑํ•œ url์— ์ง์ ‘ ๋„ฃ์–ด๋„ ๋œ๋‹ค. 

 

 

 

 


 

 

 

 

4. LoginView.vue  

  • template ๋ถ€๋ถ„
    <div class="kakao">
      <img src="../assets/kakao.png" alt="Kakao" @click="kakaoLogin"/>
    </div>
kakao ํด๋ž˜์Šค ๋งŒ๋“ค๊ณ  ์ด๋ฏธ์ง€๋ฅผ ๋„์šด๋‹ค. ์ด๋ฏธ์ง€์˜ ๊ฒฝ๋กœ๋ฅผ ์ ์–ด์ฃผ๊ณ  ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ์ˆ˜ํ–‰ํ•  ๋ฉ”์„œ๋“œ๋ฅผ ์ ๋Š”๋‹ค. kakaoLogin์ด script์— ์ž‘์„ฑ๋  ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด ๋œ๋‹ค.

 

 

  • style ๋ถ€๋ถ„
.kakao{
  position:  absolute;
  top:60%;
  left: 50%;
  transform: translateX(-50%);
  cursor: pointer;
}

 

 

  • script ๋ถ€๋ถ„
 kakaoLogin() {
 }
์นด์นด์˜ค ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ•˜๋ฉด ์‹คํ–‰๋  ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ดํ›„์˜ ์ฝ”๋“œ๋Š” ๋ชจ๋‘ kakaoLogin ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์— ์ ์–ด์ค€๋‹ค. (ํ—ท๊ฐˆ๋ฆฐ๋‹ค๋ฉด ์œ„์˜ ์ „์ฒด ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”!)

 

      // โ‘ 
      const REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY;
      // โ‘ก
      const REDIRECT_URI = `${window.location.origin}/oauth/code/kakao`;

      // โ‘ข
      if (!REST_API_KEY) {
        alert('REST_API_KEY ๋น„์–ด์žˆ์Œ!');
        return;
      }
โ‘ 
const๋ฅผ ๋ถ™์ธ ๋ณ€์ˆ˜๋Š” ๋‹ค๋ฅธ ๊ณณ์—์„œ ๋ณ€๊ฒฝ์ด ๋ถˆ๊ฐ€ํ•˜๋„๋ก ๋ง‰๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ๋‹ค๋ฅธ ํด๋ž˜์Šค์—์„œ REST_API_KEY์— ๊ฐ’์„ ์„ค์ •ํ•˜๋ ค๊ณ  ํ•œ๋‹ค๋ฉด, ์˜ค๋ฅ˜๊ฐ€ ์ถœ๋ ฅ๋  ๊ฒƒ์ด๋‹ค. ๋ณดํ†ต ํ”„๋กœ์ ํŠธ๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ์ง„ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ณ€๊ฒฝ๋˜์–ด์„œ๋Š” ์•ˆ ๋˜๋Š” ์ค‘์š”ํ•œ ๋ณ€์ˆ˜์˜ ๊ฒฝ์šฐ const๋ฅผ ๋ถ™์—ฌ ๋ง‰๋Š” ๊ฒŒ ์ข‹๋‹ค. ์‚ฌ์‹ค ์ง€๊ธˆ์€ ๊ผญ ์จ์•ผ ํ•  ์ด์œ ๋Š” ์—†์ง€๋งŒ ๊ทธ๋ž˜๋„ ๋‚˜๋ฆ„ ์ค‘์š”ํ•˜๋‹ค๊ณ  ํ‹ฐ๋ฅผ ๋‚ด๋ณด๊ฒ ๋‹ค.

REST_API_KEY๋Š” import๋ฅผ ์จ์„œ ํ˜„์žฌ ์žˆ๋Š” ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋ƒˆ๋‹ค. import์˜ ์—ญํ• ์€ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค๋ฅธ ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ์•„๊นŒ .env.local์„ ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์žˆ๋„๋ก ์„ค์ •ํ–ˆ๋‹ค. ๊ทธ ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋‚ด์–ด ๊ฐ€์ ธ์˜ค๋„๋ก ํ•œ ๊ฒƒ์ด๋‹ค. ๋งŒ์•ฝ ์ด ํŒŒ์ผ์„ ๋งŒ๋“ค์ง€ ์•Š์•˜๋‹ค๋ฉด, ๊ฑฑ์ • ๋ง๊ณ  ์นด์นด์˜ค ๋””๋ฒจ๋กœํผ์Šค์—์„œ ๋ฐœ๊ธ‰๋ฐ›์•˜๋˜ REST_API_KEY๋ฅผ = ์˜†์— ๊ทธ๋Œ€๋กœ ๋ถ™์—ฌ์ฃผ๋ฉด ๋œ๋‹ค. ํ˜น์‹œ๋ผ๋„ ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, .env.local์˜ ์œ„์น˜๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค. ๊ผญ ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค. ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ ์•ˆ์— ์žˆ๋Š” ๋‹ค๋ฅธ ๋””๋ ‰ํ† ๋ฆฌ์— ๋„ฃ์œผ๋ฉด vue๊ฐ€ ์ธ์‹ํ•˜์ง€ ๋ชปํ•œ๋‹ค.

โ‘ก
REDIRECT_URI๋Š” ์นด์นด์˜ค๊ฐ€ ์ธ๊ฐ€์ฝ”๋“œ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ณณ์ด๋‹ค. REST_API_KEY์™€ ๊ฐ™์ด ์ง์ ‘ ๊ฐ’์„ ๋„ฃ์–ด๋„ ๋œ๋‹ค. ํ˜น์€ .env.local์— ์ž‘์„ฑํ•ด๋„ ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ${window.location.origin}์„ ๋ถ™์—ฌ ์ง€๊ธˆ ์ด ํŽ˜์ด์ง€๊ฐ€ ๋œฌ ์ถœ์ฒ˜๋ฅผ ๋ฐ›์•„์™€ ์ž๋™์œผ๋กœ ๋งž๋Š” ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URI๋ฅผ ๋งŒ๋“ค๋„๋ก ์ž‘์„ฑํ–ˆ๋‹ค. ๋งŒ์•ฝ ๋ฐฐํฌ๋ฅผ ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ์ฃผ์†Œ๊ฐ€ ๋‹ฌ๋ผ์ง€๊ฒŒ ๋œ๋‹ค.(์˜ˆ๋กœ, http://localhost:5173/oauth/code/kakao -> https://myapp.com/oauth/code/kakao) ๋ฌผ๋ก  ์ฃผ์†Œ๊ฐ€ ๋‹ฌ๋ผ์ง€๋ฉด ์นด์นด์˜ค ๋””๋ฒจ๋กœํผ์Šค์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URI์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ vue๋ฅผ ์—ด์–ด์„œ REDIRECT_URI ์ฝ”๋“œ๋Š” ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.  ์•Œ์•„์„œ ์ ‘์† ์ค‘์ธ ์˜ค๋ฆฌ์ง„์— ๋งž๊ฒŒ ์ž๋™์œผ๋กœ ์ˆ˜์ •ํ•  ๊ฒƒ์ด๋‹ค. 

โ‘ข
REST_API_KEY๋ฅผ .env.local ํŒŒ์ผ ๋‚ด์— ์ž‘์„ฑํ•˜์—ฌ ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ๋˜๋„๋ก ํ–ˆ๋‹ค. ๋ฌผ๋ก  ์ด ๋ฐฉ์‹์€ Vite์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์œผ๋กœ, VITE_KAKAO_REST_API_KEY์ฒ˜๋Ÿผ VITE_ ์ ‘๋‘์‚ฌ๋กœ ์ ์–ด๋‘๋ฉด, Vite๊ฐ€ ์‹คํ–‰ ์‹œ์ ์— ์ฝ์–ด์„œ ํ”„๋ก ํŠธ ์ฝ”๋“œ์—์„œ import.meta.env.VITE_KAKAO_REST_API_KEY๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. 

๊ฒฐ๊ตญ REST_API_KEY๋Š” LoginView.vue์— ์—†๊ณ , .env.local์— ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ .env.local ํŒŒ์ผ์— REST_API_KEY๊ฐ€ ์—†์„ ์ˆ˜ ์žˆ๊ณ  ๊ทธ๊ฑธ ๋†“์น  ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ ์กฐ๊ฑด๋ฌธ์„ ๋‹ฌ์•„์„œ ํ•ด๋‹น ํ‚ค๊ฐ€ ์—†์Œ์„ ๋‚˜ํƒ€๋‚˜๋„๋ก ํ–ˆ๋‹ค.

 

 

 

      // โ‘ 
      const state = btoa(String(Date.now()));
      // โ‘ก
      sessionStorage.setItem("kakao_oauth_state", state);
โ‘ 
state ์ฝ”๋“œ๋Š” ์–ด์ฐจํ”ผ ๋ฐฐํฌํ• ๊ฒŒ ์•„๋‹ˆ๋ผ์„œ ๊ผญ ํ•„์š”ํ•œ ์ฝ”๋“œ๋Š” ์•„๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ฐ„๋‹จํ•˜๊ฒŒ CSRF๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด๋ณด๊ฒ ๋‹ค. CSRF๋Š” ์‚ฌ์šฉ์ž ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์†์—ฌ์„œ, ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„ํ•˜์ง€ ์•Š์€ ์ •์ƒ ์š”์ฒญ์„ ๋ณด๋‚ด๋„๋ก ํ•œ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ์„ ํš๋“ํ•˜๊ฑฐ๋‚˜ ๊ถŒํ•œ์„ ์•…์šฉํ•œ๋‹ค.

์–ด๋–ป๊ฒŒ state๋ฅผ ๋งŒ๋“ค ๊ฑฐ๋ƒ๋ฉด, Date.now๋กœ ํ˜„์žฌ ์‹œ๊ฐ์„ ๋ฐ€๋ฆฌ์ดˆ ์ˆซ์ž๋กœ ๋ฐ›์•„์˜จ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ˆซ์ž๋ฅผ String์œผ๋กœ ๋ฎ์–ด์„œ ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊พผ๋‹ค. btoa๋Š” ๋ฌธ์ž์—ด์„ Base64๋กœ ์ธ์ฝ”๋”ฉํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ˆซ์ž๋กœ ๋˜์–ด์žˆ๋Š” ๋ฐ€๋ฆฌ์ดˆ 1756350123456๊ฐ€ MTc1NjM1MDEyMzQ1Ng== ์ด๋ ‡๊ฒŒ ์ตœ์ข…์ ์œผ๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค. ๋ฌผ๋ก  ์ด๋ ‡๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์—ฐ์Šต์šฉ์ด๋‹ค. (์‹ค์ œ๋กœ ํ•˜๋ ค๋ฉด crypto.randomUUID()์™€ ๊ฐ™์ด ์ง„์งœ ๋žœ๋ค์„ ๋Œ๋ฆฌ๋Š” ๊ฒŒ ๋” ๋‚ซ๋‹ค.)

โ‘ก
์ด๋ ‡๊ฒŒ ๋งŒ๋“  state๋ฅผ ๋ณด๊ด€ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค. ๊ทธ ๋ณด๊ด€์„ sessionStorage์— ํ•  ๊ฒƒ์ด๋‹ค. ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ ์œ„ํ•ด authUrl์— state๋ฅผ ๊ฐ™์ด ๋„ฃ์„ ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์นด์นด์˜ค๊ฐ€ authUrl์„ ๋ฐ›๊ณ  ๋‹ค์‹œ ๋Œ€๋‹ต์„ ๋ณด๋‚ผ ๋•Œ state ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋Œ๋ ค์ค„ ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿผ ๋‚ด๊ฐ€ ์•„๊นŒ ๋ณด๋‚ธ state ๊ฐ’๊ณผ ์นด์นด์˜ค๊ฐ€ ์ค€ state ๊ฐ’์„ ๋น„๊ตํ•˜์—ฌ ๋ˆ„๊ฐ€ ์ค‘๊ฐ„์— ๋ผ์›Œ ๋„ฃ์€ ๊ฒƒ์€ ์—†๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

sessionStorage๋Š” sessionStorage.setItem(key, value)์œผ๋กœ ๋˜์–ด์žˆ๋‹ค. "kakao_oauth_state"๋Š” key๊ฐ€ ๋˜๊ณ , state๋Š” ๊ฐ’์ด ๋œ๋‹ค. ๊ทธ๋Ÿผ sessionStorage๋Š” ๋ญ˜๊นŒ? sessionStorage๋Š” ํƒญ(์„ธ์…˜) ๋‹จ์œ„ ์ €์žฅ์œผ๋กœ ํƒญ์„ ๋‹ซ์œผ๋ฉด ์‚ฌ๋ผ์ง„๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค๋ฅธ ํƒญ๊ณผ ๊ณต์œ ๋˜์ง€ ์•Š๋Š”๋‹ค. session์— ์ €์žฅํ•ด์•ผ ์ผํšŒ์„ฑ์œผ๋กœ ์ ํ•ฉํ•˜๋‹ค

 

 

      // โ‘ 
      const authUrl =
          `https://kauth.kakao.com/oauth/authorize?response_type=code` +
          `&client_id=${encodeURIComponent(REST_API_KEY)}` +
          `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
          `&state=${encodeURIComponent(state)}`;

      // โ‘ก
      window.location.href = authUrl;
โ‘ 
์นด์นด์˜ค๋กœ๋ถ€ํ„ฐ ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ •๋ณด๋“ค์„ ๋‹ด์•„์ค€๋‹ค. ์—ฌ๊ธฐ์„œ ์ง€๊ธˆ๊นŒ์ง€ ์ •์„ฑ๊ป ์ ์—ˆ๋˜ ๋ณ€์ˆ˜ ์•ž์— encodedURIComponent๊ฐ€ ์ ํ˜€์žˆ๋‹ค. encodedURIComponent๋Š” URL์—์„œ ํ”„๋กœํ† ์ฝœ, ๋„๋ฉ”์ธ, ๊ฒฝ๋กœ๊ตฌ๋ถ„์ž(/ : ? & #)๋“ฑ ํŠน์ˆ˜ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š” ๋ฌธ์ž๋“ค๋„ ์ธ์ฝ”๋”ฉํ•œ๋‹ค. ์‰ฝ๊ฒŒ ๋งํ•ด์„œ URI ๊ตฌ์„ฑ์š”์†Œ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ธ์ฝ”๋”ฉํ•ด์ฃผ๋Š” ์—ญํ• ์ด๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ํŠน์ˆ˜ ๋ฌธ์ž๋“ค์„ ์ธ์ฝ”๋”ฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— URL ์ „์ฒด๋ฅผ ์ธ์ฝ”๋”ฉํ•  ๋•Œ ์ ํ•ฉํ•˜์ง€ ์•Š๊ณ  ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ์ธ์ฝ”๋”ฉํ•ด์•ผ ํ•  ๊ฒฝ์šฐ ์œ ์šฉํ•˜๋‹ค. ์ „์ฒด URL์„ ์ธ์ฝ”๋”ฉํ•  ๋•Œ๋Š” encodeURI๊ฐ€ ์ ํ•ฉํ•˜๋‹ค.

โ‘ก
window.location.href๋Š” ๋ธŒ๋ผ์šฐ์ € ์ฃผ์†Œ์ฐฝ์— ์ƒˆ ๊ฐ’์œผ๋กœ ๋ฐ”๊พธ์–ด ํ•ด๋‹น ํŽ˜์ด์ง€๋ฅผ ์ด๋™์‹œํ‚จ๋‹ค. authUrl์ด ์ ํ˜€์žˆ์œผ๋‹ˆ, authUrl๋กœ ์™„์ „ํžˆ ์ด๋™ํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ์ฐฝ์ด ์ถœ๋ ฅ๋  ๊ฒƒ์ด๋‹ค.

window.location.href๋Š” ํžˆ์Šคํ† ๋ฆฌ์— ๊ธฐ๋ก๋˜์–ด ๋’ค๋กœ ๊ฐ€๊ธฐ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. window.location.assign(authUrl);๊ณผ ๋˜‘๊ฐ™์ด ์ž‘๋™ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ๋ฅผ window.location.assign(authUrl);์œผ๋กœ ๋ฐ”๊ฟ”๋„ ๋ฌด๊ด€ํ•˜๋‹ค.


๋งŒ์•ฝ ์—ฌ๊ธฐ๊นŒ์ง€ ์™”๋Š”๋ฐ ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ์ฐฝ์ด ์•ˆ ๋‚˜ํƒ€๋‚œ๋‹ค๋ฉด, ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋“ค์–ด๊ฐ„ REST_API_KEY์™€ REDIRECT_URI๋ฅผ ๋‹ค์‹œ ์‚ดํŽด๋ณด๊ณ , .env.local์˜ ์œ„์น˜๋ฅผ ํ™•์ธํ•ด ๋ณด์ž.

 

 

 

 


 

 

 

4. KakaoCallback.vue  

  • script
// โ‘ 
import axios from "axios";

// โ‘ก
export default {

}
โ‘ 
LoginView.vue์˜ ์ „์ฒด์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์œ„์˜ ์ฝ”๋“œ๊ฐ€ ๋˜‘๊ฐ™์ด ์ ํ˜€์žˆ๋‹ค.(์•ˆ ์ ์—ˆ๋‹ค๋ฉด ์ ์–ด์•ผ ํ•œ๋‹ค!) import axios๋Š” ๋ฐฑ์—”๋“œ์— ์ฝ”๋“œ๋ฅผ ์ „๋‹ฌํ•˜๊ณ ์ž ํ•  ๋•Œ ์ฆ‰, ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค.

โ‘ก
export default๋Š” Vue ์ปดํฌ๋„ŒํŠธ ๊ฐ์ฒด๋ฅผ ๋‚ด๋ณด๋‚ธ๋‹ค๋Š” ๋œป์ด๋‹ค. ์ง€๊ธˆ์€ ์•„๋ฌด๊ฒƒ๋„ ๋‚ด๋ณด๋‚ด์ง€ ์•Š๊ฒ ์ง€๋งŒ, ์ด์ œ ์ด ๋ฐ‘์œผ๋กœ ๋‚˜์˜ค๋Š” ์ฝ”๋“œ๋Š” ๋ชจ๋‘ export default ์•ˆ์— ์ž‘์„ฑํ•˜์—ฌ ๋‚ด๋ณด๋‚ผ ๊ฒƒ์ด๋‹ค.

 

 

  // โ‘ 
  name: "KakaoCallback",
  
  // โ‘ก
  async created() {
  
  }
โ‘ 
์ด ์ฝ”๋“œ๋Š” export default ์•ˆ์— ์ ๋Š”๋‹ค. name์€ ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด๋ถ€ ์‹๋ณ„ ์ด๋ฆ„์ด๋‹ค. ๊ผญ ํ•„์š”ํ•œ ๋ถ€๋ถ„์€ ์•„๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ๊ฒฝ๊ณ , ์—๋Ÿฌ๋กœ ์ด๋ฆ„์ด ์ฐํ˜€ ์ฝ๊ธฐ ์‰ฝ๊ฒŒ ๋„์™€์ฃผ๊ฑฐ๋‚˜ ๊ทธ ์™ธ์—์„œ ํŽธ๋ฆฌํ•จ์„ ๋•๋Š”๋‹ค.

โ‘ก
created๋ฅผ ์ œ๋Œ€๋กœ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ผ์ดํ”„์‚ฌ์ดํด์„ ์•Œ์•„์•ผ ํ•œ๋‹ค. ๊ทผ๋ฐ ๊ทธ๋Ÿฌ๋ฉด ๋„ˆ๋ฌด ๊ธธ์–ด์ง„๋‹ค. ๊ทธ๋ƒฅ ์‰ฝ๊ฒŒ ๋งํ•ด์„œ KakaoCallback ์ปดํฌ๋„ŒํŠธ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋œ๋‹ค. URL์„ ํŒŒ์‹ฑ ํ•˜๊ณ , state ๊ฒ€์ฆํ•˜๊ณ , ๋ฐฑ์—”๋“œ๋กœ ๋ณด๋‚ด๋Š” ์ •๋„์˜ ์ผ๋งŒ ์ˆ˜ํ–‰ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ DOM์„ ์กฐ์ž‘ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ์–ด์จŒ๋“  ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งŒ ์ฒ˜๋ฆฌํ•ด์„œ ์ตœ๋Œ€ํ•œ ๋นจ๋ฆฌ ์ฒ˜๋ฆฌํ•˜๋„๋ก created๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋” ์ข‹๋‹ค๋Š” ๊ฒƒ๋งŒ ์•Œ์•„๋‘์ž.

 

 

    // โ‘ 
    const url = new URL(window.location.href);

    // โ‘ก
    const code = url.searchParams.get("code");
    
    // โ‘ข
    const state = url.searchParams.get("state");
โ‘ 
์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ์€ ์ด๋ฏธ ์‚ฌ์šฉ์ž ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚˜์„œ, ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋™์˜ ํ•ญ๋ชฉ์„ ์ฒดํฌํ–ˆ์„ ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ถŒํ•œ ๋ฒ”์œ„๋Š” ์นด์นด์˜ค ์„œ๋ฒ„๊ฐ€ ์ž˜ ๊ฐ„์งํ•˜๊ณ  ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์นด์นด์˜ค๋Š” ๊ทธ ๋ณด์ฆ์œผ๋กœ ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๋Œ๋ ค์ค€๋‹ค. ์—ฌ๊ธฐ URL์— ์ธ๊ฐ€ ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด์žˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ๊ถŒํ•œ ๋ฒ”์œ„๋ฅผ ์„ค์ •ํ•˜๋ฉด ์นด์นด์˜ค๋Š” ์•Œ์•„์„œ authUrl์— ๋“ค์–ด์žˆ๋Š” ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URI๋กœ ๋Œ๋ ค๋ณด๋‚ธ๋‹ค. ๊ทธ๋‹ˆ๊นŒ ์ฃผ์†Œ๊ฐ€ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URI ์ฃผ์†Œ๋กœ ๋ฐ”๋€Œ๋Š” ๊ฒƒ์ด๋‹ค. ๊ฑฐ๊ธฐ์— ์นด์นด์˜ค๋Š” ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณด๋‚ด๊ณ  code์™€ state๋ฅผ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URI์—์„œ ๋ฝ‘์•„์•ผ ํ•˜๋‹ˆ window.location.href๋กœ ํ˜„์žฌ ์ฃผ์†Œ๋ฅผ ๊ฐ€์ ธ์˜ค๋„๋ก ํ–ˆ๋‹ค. window.location.href๋Š” ํ˜„์žฌ URL์„ ํ™•์ธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ URL๋กœ ์ด๋™ํ•˜์—ฌ ํŽ˜์ด์ง€๋ฅผ ์ „ํ™˜ํ•˜๋Š”๋ฐ ํ™œ์šฉํ•œ๋‹ค.

โ‘ก
์˜ˆ๋ฅผ ๋“ค์–ด searchParams๊ฐ€ ๋ญ”์ง€ ๊ถ๊ธˆํ•ด์„œ ๊ตฌ๊ธ€์— ๊ฒ€์ƒ‰์„ ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž. ๊ทธ๋Ÿผ URL์— https://www.google.com/search?q=searchParmas...๋ผ๊ณ  ์“ฐ์—ฌ์žˆ์„ ๊ฒƒ์ด๋‹ค. searchParams.get("q")๋ฅผ ์ ์œผ๋ฉด ?q=์˜ ๊ฐ’์„ get ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ code์—๋Š” searchParams๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

์‹ค์ œ ์ฝ”๋“œ๋Š” code๋ฅผ get ํ•˜๊ฒ ๋‹ค๊ณ  ๋˜์–ด์žˆ์œผ๋‹ˆ, ?code=๋ฅผ ์ฐพ์•„ ๊ฐ’์„ code(์ธ๊ฐ€์ฝ”๋“œ)์— ๋„ฃ๊ฒŒ ๋œ๋‹ค. ์ด๊ฒŒ ๊ฐ€๋Šฅํ•œ ์ด์œ ๋Š” searchParams๊ฐ€ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์œผ๋กœ ํ‚ค-๊ฐ’ ํ˜•ํƒœ๋กœ ๋‹ค๋ฃจ๊ฒŒ ํ•ด์ฃผ๋Š” ํ‘œ์ค€ API์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

โ‘ข
CSRF๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด state ๊ฐ’์„ ๋„ฃ์–ด authUrl๋กœ ๋ณด๋ƒˆ์—ˆ๋‹ค. ์นด์นด์˜ค๊ฐ€ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ๋ณด๋‚ด๋ฉด์„œ state ๊ฐ’์„ ๋Œ๋ ค์คฌ์œผ๋‹ˆ, ๋‚ด๊ฐ€ ๋ณด๋‚ธ ๊ฐ’๊ณผ ์นด์นด์˜ค๊ฐ€ ์ค€ ๊ฐ’์ด ๋งž๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด state ๊ฐ’์„ ๊บผ๋‚ผ ๊ฒƒ์ด๋‹ค.

 

 

    // โ‘ 
    const saved = sessionStorage.getItem("kakao_oauth_state");
    
    
    // โ‘ก
    sessionStorage.removeItem("kakao_oauth_state");
โ‘ 
LoginView.vue์—์„œ sessionStorage.setItem("kakao_oauth_state", state)๋ฅผ ํ–ˆ์—ˆ๋‹ค. ๋ฏธ๋ฆฌ ์ €์žฅํ•ด ๋‘” state ๊ฐ’์„ sessionStorage์—์„œ ๊บผ๋‚ด saved์— ๋„ฃ์„ ๊ฒƒ์ด๋‹ค.

โ‘ก
๊บผ๋‚ธ ๊ฐ’์€ ๋ฐ”๋กœ ์ง€์›Œ์ค€๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. sessionStorage์— ๋‚จ์•„์žˆ๊ฒŒ ๋˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋’ค๋กœ ๊ฐ€๊ธฐ๋ฅผ ํ•˜๊ฑฐ๋‚˜ ์•ž์œผ๋กœ ๊ฐ€๊ธฐ๋ฅผ ํ–ˆ์„ ๋•Œ ์ฝœ๋ฐฑ์ด ๋  ์ˆ˜ ์žˆ๋‹ค. sessionStorage๋ฅผ ์ง€์šฐ์ง€ ์•Š๊ณ  state๋ฅผ ๊ฒ€์ฆ์„ ํ†ต๊ณผํ•œ๋‹ค๋ฉด, ์‚ฌ์šฉ์ž๊ฐ€ ๋’ค๋กœ ๊ฐ€๊ธฐ ํ˜น์€ ์•ž์œผ๋กœ ๊ฐ€๊ธฐ, ์ƒˆ๋กœ๊ณ ์นจ์„ ์ˆ˜ํ–‰ํ–ˆ์„ ๋•Œ sessionStorage์˜ ๊ฐ’์„ ๋‹ค์‹œ ๊บผ๋‚ด์™€ ๋˜ ๊ฒ€์ฆ์„ ํ•˜๊ณ  ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•  ์ˆ˜ ์žˆ๋‹ค.(์ผํšŒ์„ฑ ์‚ฌ์šฉ์„ ๋ชฉํ‘œ๋กœ ํ–ˆ๋Š”๋ฐ ๋ชปํ•˜๊ฒŒ ๋จ) ๊ทธ๋ž˜์„œ ์ด๋Ÿฐ ์ƒํ™ฉ์„ ๋ฐฉ์ง€ํ•˜๊ณ ์ž ๋ฐ”๋กœ ๊ฐ’์„ ์ง€์›Œ์ฃผ๋Š” ๊ฒŒ ์ข‹๋‹ค.

 

 

    // โ‘ 
    if (!code || !state || !saved || state !== saved) {
      alert("์ž˜๋ชป๋œ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค. (state ๊ฒ€์ฆ ์‹คํŒจ)");
      this.$router.replace("/login");
      return;
    }
โ‘ 
if๋ฌธ์œผ๋กœ ์ธ๊ฐ€ ์ฝ”๋“œ์™€ ์ƒํƒœ๊ฐ€ ์ œ๋Œ€๋กœ ์™”๋Š”์ง€ ๊ฒ€์ฆํ•  ๊ฒƒ์ด๋‹ค. if๋ฌธ ์•ˆ์˜ ์กฐ๊ฑด๋ฌธ์€ (์นด์นด์˜ค๊ฐ€ ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์•ˆ ์คฌ๊ฑฐ๋‚˜ || ์นด์นด์˜ค๊ฐ€ state๋ฅผ ์•ˆ์คฌ๊ฑฐ๋‚˜ || state๋ฅผ ๊บผ๋‚ด์˜จ ๊ฒŒ ์—†๊ฑฐ๋‚˜ || ์นด์นด์˜ค๊ฐ€ ์ค€ state์™€ ์ €์žฅํ•œ state๊ฐ€ ๋‹ค๋ฅด๊ฑฐ๋‚˜)์ด๋‹ค. ๋งŒ์•ฝ ์กฐ๊ฑด๋ฌธ์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•˜๋ฉด alert์„ ๋ณด๋‚ด๊ณ  login ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ธ๋‹ค. ์ด๋•Œ replace๋Š” ์‹คํŒจํ•œ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€์ง€ ์•Š๋Š”๋‹ค.

 

 

    // โ‘ 
    try {

    } catch(e) {

    }
โ‘ 
์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋Š” try์— ๋„ฃ๊ณ , ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ฒ˜๋ฆฌํ•  ๋ฌธ์žฅ์„ catch์— ๋„ฃ๋Š”๋‹ค. ์ด ๋ฐ‘์— ๋‚˜์˜ค๋Š” ์ฝ”๋“œ๋Š” try์— ๋„ฃ์œผ๋ฉด ๋œ๋‹ค. 

 

 

   // โ‘ 
   const { data } = await axios.post("http://localhost:8080/oauth/kakao", { code });
โ‘ 
ํ† ํฐ ๊ตํ™˜์€ ๋ฐฑ์—”๋“œ์—์„œ ์ฒ˜๋ฆฌํ•  ๊ฑฐ๋‹ค. ํ† ํฐ์ด ์žˆ์œผ๋ฉด ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ์–ป๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ์ค‘์š”ํ•œ ์ž‘์—…์˜ ๊ฒฝ์šฐ ๋ฐฑ์—”๋“œ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒŒ ์ข‹๋‹ค. (์ฐธ๊ณ ํ•œ ์ฝ”๋“œ๊ฐ€ ์ด๋ ‡๊ฒŒ ์ง„ํ–‰ํ•ด์„œ ์ธ๊ฐ€์ฝ”๋“œ๋ฅผ ํ”„๋ก ํŠธ๋กœ ๋ฐ›๊ธดํ–ˆ์ง€๋งŒ, ๋ฐฑ์—”๋“œ๋กœ ํ•˜๋Š”๊ฒŒ ๋” ์ข‹๋‹ค.) ๊ทธ๋ž˜์„œ ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๋ฐฑ์—”๋“œ๋กœ ๋ณด๋‚ด์ค˜์•ผ ํ•œ๋‹ค. ์ด ์ฝ”๋“œ๊ฐ€ ๋ฐฑ์—”๋“œ๋กœ ๋ณด๋‚ด๋Š” ์ฝ”๋“œ์ด๋‹ค. ์ธ๊ฐ€์ฝ”๋“œ๋Š” ํ† ํฐ์„ ๊ตํ™˜ํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋กœ ๋‚˜๋ฆ„ ๋ฏผ๊ฐ„ ํ•œ ๊ฐ’์ด๋ผ๊ณ  ์ƒ๊ฐํ•ด์„œ POST๋กœ ๋ณด๋‚ด ์ฟผ๋ฆฌ๋กœ ๋ณด๋‚ด์ง€ ์•Š๋„๋ก ํ–ˆ๋‹ค.

๋ฐฑ์—”๋“œ์—์„œ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ๊ฒƒ์ด๋‹ค.(ํ† ํฐ๋„ ๋ฐ›๊ณ , ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ๋„ ํ•˜๊ณ ~) ๊ทธ๋Ÿฌ๋ฉด ๊ทธ ์ฒ˜๋ฆฌ๋œ ๊ฒฐ๊ณผ๋Š” {data}๋กœ ๋ฐ›๊ฒŒ ๋œ๋‹ค.

 

 

      // โ‘ 
      if (data?.nickname) {
        localStorage.setItem("displayName", data.nickname);
        alert(`ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, ${data.nickname}๋‹˜!`);
      } else {
        alert("๋‹‰๋„ค์ž„์„ ๋ฐ›์ง€ ๋ชปํ–ˆ์–ด์š”: " + JSON.stringify(data));
      }
      
      // โ‘ก
      this.$router.replace("/main");
โ‘ 
์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ๋Š” ๋ฐฑ์—”๋“œ ์ฒ˜๋ฆฌ๊ฐ€ ๋‹ค ๋๋‚˜๊ณ , vue ์‚ฌ์šฉ์ž ๋‹‰๋„ค์ž„์„ ๋ณด์ด๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ๋‹‰๋„ค์ž„์„ ๋ฐ›์•„์˜ค๋Š” ์ฝ”๋“œ๊ฐ€ ๋œ๋‹ค. 

์กฐ๊ฑด๋ฌธ์„ ๋ณด๋ฉด data?.nickname์ด๋ผ๊ณ  ๋˜์–ด์žˆ๋‹ค. ์ด๊ฑธ ์˜ต์…”๋„ ์ฒด์ด๋‹์ด๋ผ๊ณ  ํ•œ๋‹ค. ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ›์€ ๊ฒฐ๊ณผ data์— data๊ฐ€ ์ž˜ ์™”๋Š”์ง€ ๊ทธ๋ฆฌ๊ณ  nickname์ด ์ž˜ ์™”๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. ์˜ต์…”๋„ ์ฒด์ด๋‹์„ ์“ฐ๋ฉด ์ž˜ ์™”๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ถ€๋ถ„์— ํŠน์ง•์ด ์žˆ๋‹ค. data๊ฐ€ null ํ˜น์€ undefined์ธ์ง€ ํ™•์ธํ•œ๋‹ค. null์ด๋‚˜ undefined๊ฐ€ ์˜ค๋ฉด ์—๋Ÿฌ๋ฅผ ๋ณด๋‚ด์ง€ ์•Š๊ณ  ๊ทธ๋ƒฅ ํ†ต๊ณผ์‹œํ‚จ๋‹ค. data๊ฐ€ ์ž˜ ์™”์œผ๋ฉด nickname์ด ๋นˆ ๋ฌธ์ž์—ด/0/false/null/undefined์ธ์ง€ ํ™•์ธํ•˜๊ณ  ์•„๋‹ˆ๋ฉด ์‹คํ–‰ํ•œ๋‹ค.

์ œ๋Œ€๋กœ ๋‹‰๋„ค์ž„์ด ํ”„๋ก ํŠธ์— ๋„์ฐฉํ–ˆ์œผ๋ฉด localStorage์— ์ €์žฅํ•œ๋‹ค. ์•„๊นŒ ์ „์— sessionStorage์— state๋ฅผ ์ €์žฅํ–ˆ๋˜๊ฑธ ๊ธฐ์–ตํ•˜๋ฉด sessionStorage๋Š” ํƒญ๋‹จ์œ„๋ผ๊ณ  ํ–ˆ๋‹ค. ํƒญ๋‹จ์œ„๋กœ ์ด๋ค„์ง€๋Š” ๊ฒƒ๊ณผ ๋‹ฌ๋ฆฌ localStorage๋Š” ์ƒˆ๋กœ๊ณ ์นจ, ํƒญ ์ด๋™ ํ›„์—๋„ ๊ณ„์† ์ €์žฅ๋˜์–ด ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด state์™€ ๋‹ฌ๋ฆฌ ๋‹‰๋„ค์ž„์€ localStorage์— ๋‘” ์ด์œ ๊ฐ€ ๋ญ˜๊นŒ? state๋Š” ์ผํšŒ์„ฑ์ฒ˜๋Ÿผ ์“ฐ์—ฌ์•ผ ํ•œ๋‹ค. ๋งŒ์•ฝ ๋‹‰๋„ค์ž„์„ ํƒญ๋‹จ์œ„๋กœ ์ €์žฅํ•˜๊ฒŒ ํ–ˆ๋‹ค๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กœ์šด ํƒญ์„ ์—ด์—ˆ์„ ๋•Œ, ๋‹‰๋„ค์ž„์ด ์ƒˆ ํƒญ์—์„œ๋Š” ๋ณด์ด์ง€ ์•Š๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ localStorage์— ์ €์žฅํ•œ ๊ฒƒ์ด๋‹ค. 

์•„๋งˆ ์ด ์ฝ”๋“œ์—์„œ ๊ฐ์ž์˜ ํ”„๋กœ์ ํŠธ์— ๋งž๊ฒŒ ์ˆ˜์ •์„ ํ•ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค. displayName์€ ๋ฉ”์ธ ํ™”๋ฉด์— ์„ค์ •ํ•ด ๋‘” ๊ฒƒ์œผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ํšŒ์›๊ฐ€์ž… ํ›„ ๋กœ๊ทธ์ธ์„ ํ•˜๋ฉด ์ด๋ฆ„์ด ๋ณด์ด๋„๋ก ํ–ˆ๊ณ , ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ์„ ํ•˜๋ฉด ์‚ฌ์šฉ์ž์˜ ๋‹‰๋„ค์ž„์ด ๋ณด์ด๋„๋ก ์„ค์ •ํ–ˆ๋‹ค. 

โ‘ก
๋‹‰๋„ค์ž„์„ ์ž˜ ๋ฐ›์•„์™”๊ณ  ๋ฌธ์ œ๊ฐ€ ์—†์œผ๋ฉด, main ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•œ๋‹ค. ์•„๊นŒ replace๋Š” /main์œผ๋กœ ๋ณ€๊ฒฝ๋˜๊ธฐ ์ „ ํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐˆ ์ˆ˜ ์—†๋‹ค๊ณ  ํ–ˆ๋‹ค. ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ์„ ์ œ๋Œ€๋กœ ์ˆ˜ํ–‰ํ–ˆ์œผ๋ฉด ๋’ค๋กœ ๊ฐ€๊ธฐ๋ฅผ ๋ˆŒ๋Ÿฌ ๋Œ์•„๊ฐˆ ์ˆ˜ ์—†๋„๋ก ํ–ˆ๋‹ค. 

ํ˜น์‹œ๋ผ๋„ ํ”„๋กœ์ ํŠธ์˜ ๊ตฌ์กฐ์— ๊ถ๊ธˆํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์„ค๋ช…์„ ์ข€ ๋” ๋ง๋ถ™์—ฌ๋ณด๊ฒ ๋‹ค. displayName์— ๊ฐ’์ด ๋“ค์–ด์˜ค๋ฉด ๋ฉ”์ธํ™”๋ฉด ์†์— ๋„ฃ์–ด๋‘” ์˜ค๋ฅธ์ชฝ ํŒจ๋„์ด CustomerRight ์ปดํฌ๋„ŒํŠธ๋กœ ๊ต์ฒด๋˜๋„๋ก ํ–ˆ๋‹ค.(๋กœ๊ทธ์ธ์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์œผ๋ฉด RightSide.vue๋กœ ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.) CustomerRight ์ปดํฌ๋„ŒํŠธ์—๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ๋ณด์ด๋ฉฐ ๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ์ด ํ•จ๊ป˜ ์žˆ๋‹ค. ๋กœ๊ทธ์•„์›ƒ์€ ์‹ค์ œ๋กœ ๋™์ž‘ํ•œ๋‹ค๊ธฐ๋ณด๋‹ค(?) ๊ทธ๋ ‡๊ฒŒ ๋ณด์ด๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์•„์›ƒ์„ ๋ˆ„๋ฅด๋ฉด localStorage์˜ ์ €์žฅ๋œ ๊ฐ’์ด remove ๋˜๊ณ  localStorage์— ๊ฐ’์ด ์—†์–ด์ง€๋‹ˆ ์˜ค๋ฅธ์ชฝ ํŒจ๋„์€ ๊ต์ฒด๋˜์–ด RightSide๋กœ ๋ณด์ด๋„๋ก ํ–ˆ๋‹ค.

 

 

    // โ‘ 
    catch (e) {
      console.error(e);
      alert(e?.response?.data ?? "์นด์นด์˜ค ๋กœ๊ทธ์ธ ์‹คํŒจ");
      this.$router.replace("/login");
    }
  }
โ‘ 
try๋Š” ๋๋‚ฌ๊ณ  catch๋ฅผ ์ž‘์„ฑํ•  ๊ฒƒ์ด๋‹ค. catch๋Š” ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ์ ๋Š”๋‹ค๊ณ  ํ–ˆ๋‹ค. console.error๋Š” ๊ฐœ๋ฐœ์ž ์ฝ˜์†”์— ์—๋Ÿฌ ๊ฐ์ฒด e๋ฅผ ๋นจ๊ฐ„์ƒ‰ ๋กœ๊ทธ๋กœ ์ฐ๋Š”๋‹ค.

e๊ฐ€ ์—†๊ฑฐ๋‚˜, e.response๊ฐ€ ์—†๋”๋ผ๋„ ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜ ์—†์ด undefined๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. e?.response?.data๊ฐ€ ์กด์žฌํ•˜๋ฉด ๊ทธ๊ฑธ ๊ทธ๋Œ€๋กœ ์•Œ๋ฆผ์œผ๋กœ ๋„์šฐ๊ณ , ์—†์œผ๋ฉด ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์‹คํŒจ๋ฅผ ๋„์šด๋‹ค.

 

 

 

 


 

 

 

5. index.js

const routes = [
    { path: '/main', name: 'Main', component: MainView },
    { path: '/simpleRegister', name: 'SimpleRegister', component: SimpleRegister },
    { path: '/basicRegister', name: 'BasicRegister', component: BasicRegister },
    { path: '/completeView', name: 'CompleteView', component: CompleteView },
    { path: '/login', name: 'LoginView', component: LoginView },
    // โ‘ 
    { path: '/oauth/code/kakao', name:'KakaoCallback', component: () => import("../components/loginview/KakaoCallback.vue")},
]
โ‘ 
index.js๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ํ•„์ž๋Š” src ํ•˜์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ์— router๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์— index.js๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. routes ๋ชฉ๋ก ์ค‘์— ๋งจ ๋ฐ‘์— KakaoCallback์ด ์žˆ๋‹ค. ์ด๊ฑธ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ ๋ถ™์—ฌ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.

์ด๊ฒŒ ์™œ ํ•„์š”ํ• ๊นŒ? ์นด์นด์˜ค๊ฐ€ http://localhost:5173/oauth/code/kakao?code=...&state=...์ด๋Ÿฐ ํ˜•ํƒœ๋กœ ๋Œ๋ ค์ฃผ๋ฉด, Vue Router๊ฐ€ ์ด ๊ฒฝ๋กœ๋ฅผ ๋ณด๊ณ  KakaoCallback ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋„์šด๋‹ค. path์— /oauth/code/kakao๋ฅผ ๊ฒฝ๋กœ๋ฅผ ์ ๊ณ  component: () => import("...")๋ฅผ ํ•˜๋ฉด ์ด ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ฌ ๋• KakaoCallback.vue๋ฅผ ๋„คํŠธ์›Œํฌ๋กœ ๊ฐ€์ ธ์™€์„œ ๋ Œ๋”๋ง ํ•œ๋‹ค. ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URI๋ฅผ ํ”„๋ก ํŠธ๋กœ ์žก์•„์„œ ๊ผญ ์ ์–ด์ค˜์•ผ ํ•œ๋‹ค.์•ˆ ์ ์–ด์ฃผ๋ฉด ๋ฐฑ์—”๋“œ๋กœ ์ธ๊ฐ€์ฝ”๋“œ๋ฅผ ๋ณด๋‚ด์•ผ ํ•˜๋Š”๋ฐ ๊ทธ๊ฑธ ๋ชปํ•œ๋‹ค. (์•ˆ๋ณด๋‚ด๋ฉด ํ† ํฐ๊ตํ™˜๋„ ๋ชปํ•จ!)

 

 

 


 

 

๋‹ค์Œ์€ ๋ฐฑ์—”๋“œ๋กœ ํ† ํฐ ๊ตํ™˜ํ•˜๋Š” ๋ถ€๋ถ„์„ ์ ์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ ๋‹ค ํ•˜๊ณ  ๋‚˜์„œ๋„ ์•ˆ ๋˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€์„ ๋‚จ๊ฒจ์ฃผ์„ธ์š”.

์ œ๊ฐ€ ๋นผ๋จน์€ ๋ถ€๋ถ„์ด ์žˆ์„์ง€๋„ ๋ชฐ๋ผ์š”,,, ํ—ท ๐Ÿฅ

! ํ™”์ดํŒ… !

 

 


 

'๐Ÿ’ป ํ”„๋กœ์ ํŠธ > ๐Ÿ ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๐Ÿ ' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Spring, Vue.js] ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ๋งŒ๋“ค๊ธฐโ‘ฃ - ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒํ•ด์„œ ๋‹‰๋„ค์ž„ ์ฝ์–ด์˜ค๊ธฐ (REST API)  (0) 2025.09.30
[Spring, Vue.js] [Spring, Vue.js] ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ๋งŒ๋“ค๊ธฐโ‘ข - Spring์œผ๋กœ ํ† ํฐ ๊ตํ™˜ํ•˜๊ธฐ (REST API)  (0) 2025.09.28
[Spring,Vue.js] ์นด์นด์˜ค ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ๋งŒ๋“ค๊ธฐ โ‘  - ๊ตฌ์กฐ์™€ ํ๋ฆ„ ํŒŒ์•…ํ•˜๊ณ  ์นด์นด์˜ค ๋””๋ฒจ๋กœํผ์Šค ์„ค์ •ํ•˜๊ธฐ (REST API)  (0) 2025.09.25
[Vue.js] ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ, localStorage๋ฅผ ์ด์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์•„์ด๋””๊ฐ€ ๋ฉ”์ธํ™”๋ฉด์— ๋ณด์ด๋„๋ก ์„ค์ •ํ•˜๊ธฐ!  (2) 2025.09.02
[SpringBoot, Vue.js] ๋กœ๊ทธ์ธ ํ™”๋ฉด ๋งŒ๋“ค๊ธฐ, passwordEncoder.matches๋ฅผ ํ†ตํ•ด ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ตํ•˜๊ธฐ.  (5) 2025.08.31