[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๋ฅผ ํ๋ก ํธ๋ก ์ก์์ ๊ผญ ์ ์ด์ค์ผ ํ๋ค.์ ์ ์ด์ฃผ๋ฉด ๋ฐฑ์๋๋ก ์ธ๊ฐ์ฝ๋๋ฅผ ๋ณด๋ด์ผ ํ๋๋ฐ ๊ทธ๊ฑธ ๋ชปํ๋ค. (์๋ณด๋ด๋ฉด ํ ํฐ๊ตํ๋ ๋ชปํจ!)
๋ค์์ ๋ฐฑ์๋๋ก ํ ํฐ ๊ตํํ๋ ๋ถ๋ถ์ ์ ์ด๋ณด๊ฒ ์ต๋๋ค.
๋ง์ฝ ๋ค ํ๊ณ ๋์๋ ์ ๋๋ ๋ถ๋ถ์ด ์๋ค๋ฉด ๋๊ธ์ ๋จ๊ฒจ์ฃผ์ธ์.
์ ๊ฐ ๋นผ๋จน์ ๋ถ๋ถ์ด ์์์ง๋ ๋ชฐ๋ผ์,,, ํท ๐ฅ
! ํ์ดํ !