Access Token, Refresh Token (액세스 토큰, 리프레시 토큰)
1. src\stores\useLoginStore.js 파일을 수정합니다.
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import axios from 'axios';
export const useLoginStore = defineStore('login', () => {
const access_token = ref("");
const refresh_token = ref("");
// 로그인 여부를 가져옵니다.
const isLogin = computed(() => {
return refresh_token.value == "" ? false : true;
});
// 로그인합니다.
async function doLogin(memberInfo) {
let result = false;
let resultErr = null;
try {
const res = await axios.post("http://127.0.0.1:9000/api/v1/member/login", memberInfo);
if (res.data.code == 200) {
console.log("로그인되었습니다.");
access_token.value = res.data.access_token;
refresh_token.value = res.data.refresh_token;
result = true;
}
} catch(err) {
// 400, 401, 500 에러 처리
console.log(err);
if (!err.response) {
err.response = {
data:{"result":"fail", "code":401, "message":"사용자가 없습니다.", access_token: "", refresh_token: ""}
};
}
resultErr = err.response.data;
}
return new Promise((resolve, reject) => {
if (result) {
resolve();
} else {
reject(resultErr);
}
});
}
return {
access_token,
refresh_token,
isLogin,
doLogin
}
});
2. src\views\LunchMenu\LunchMenuListView.vue 파일을 수정합니다.
<script>
:
export default {
name : 'LunchMenuListView',
setup() {
:
function getLunchMenuList() {
console.log(loginStore.access_token);
axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/", {
headers: {
"Authorization" : "Bearer " + loginStore.access_token
}
}).then((res) => {
console.log(res);
lunchMenuList.value = res.data.data;
}).catch((err) => {
console.log(err);
});
}
:
}
};
</script>
3. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list)

JWT 확인

iat(Issued At Time, 발급된 시간)
exp(Expiration Time, 만료 시간)
Access Token 재발급(갱신)
1. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list) → Access Token 만료 (10분 후)

Access Token의 만료 확인 시점
Access Token을 사용할 경우
요청하기 전에 Access Token이 만료되었는지 확인 후 Refresh Token를 사용하여 Access Token이 재발급 → Access Token의 payload에 있는 exp(expiration, 만료 시간)로 확인 → 매번 확인
요청 후 응답에서 Access Token이 만료되었는지 확인 후 Refresh Token를 사용하여 Access Token이 재발급 → 응답 코드와 응답 메시지로 확인 → 실패일 경우만 처리
1. src\stores\useLoginStore.js 파일을 수정합니다. - Access Token 만료 확인과 재발급(갱신) 함수 추가
:
// Access Token이 만료되었는지 확인합니다.
function isAccessTokenExpire() {
let expire = false;
if (access_token.value != "") {
// access_token에서 .(도트)로 분리하여 payload를 가져옵니다.
let base64Payload = access_token.value.split('.')[1];
// URL과 호환되지 않는 문자를 base64 표준 문자로 교체합니다.
base64Payload = base64Payload.replace(/-/g, '+').replace(/_/g, '/');
// atob() 메소드로 복호화합니다.
base64Payload = atob(base64Payload);
// JSON 객체로 변환합니다.
var payloadObject = JSON.parse(base64Payload);
// access_token의 만료 시간을 확인합니다.
var currentDate = new Date().getTime() / 1000;
if (payloadObject.exp <= currentDate) {
console.log("token expired");
expire = true;
} else {
console.log("token valid");
}
}
return expire;
}
2. src\stores\useLoginStore.js 파일을 수정합니다.
// Access Token를 갱신합니다.
async function doRefreshToken() {
let result = false;
let resultErr = null;
if (access_token.value != "" && refresh_token.value != "") {
try {
const tokenInfo = { refresh_token: refresh_token.value };
const res = await axios.post("http://127.0.0.1:9000/api/v1/member/refresh", tokenInfo);
if (res.data.code == 200) {
console.log("Access Token을 재발급(갱신)하였습니다.");
console.log(res.data);
access_token.value = res.data.access_token;
refresh_token.value = res.data.refresh_token;
result = true;
}
} catch(err) {
// 400, 401, 500 에러 처리
console.log(err);
if (!err.response) {
err.response = {
data: {"result":"fail", "code":401, "message":"Access Token을 재발급(갱신)할 수 없습니다.", access_token: "", refresh_token: ""}
};
}
}
} else {
resultErr = {"result":"fail", "code":401, "message":"Access Token이 없습니다.", access_token: "", refresh_token: ""}
}
return new Promise((resolve, reject) => {
if (result) {
resolve();
} else {
reject(resultErr);
}
});
}
return {
access_token,
refresh_token,
isLogin,
doLogin,
isAccessTokenExpire,
doRefreshToken
}
:
3. src\views\LunchMenu\LunchMenuListView.vue 파일을 수정합니다.
<script>
:
import { useRouter, useRoute } from 'vue-router';
:
export default {
name : 'LunchMenuListView',
setup() {
const router = useRouter();
const route = useRoute();
:
async function getLunchMenuList() {
console.log(loginStore.access_token);
if (loginStore.isAccessTokenExpire()) {
console.log("Access Token이 만료되었습니다.");
try {
await loginStore.doRefreshToken();
} catch(err) {
console.log(err);
alert("인증이 만료되었습니다.\n다시 로그인 해주시기 바랍니다.");
router.replace({path: '/login', query: { returnUrl: route.fullPath }});
return;
}
}
try {
const res = await axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/", {
headers: {
"Authorization" : "Bearer " + loginStore.access_token
}
});
console.log(res);
lunchMenuList.value = res.data.data;
} catch(err) {
console.log(err);
}
}
:
4. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list) → Access Token 만료 (10분 후) → Access Token 재발급(갱신)

5. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list) → Access Token 만료 (15분 후) → Refresh Token 만료 → 재발급(갱신) 불가


6. 웹 브라우저에서 확인하기 (http://localhost:8080/lunchmenu/list) → Access Token 만료 (15분 후) → Refresh Token 만료 → 재발급(갱신) 불가 → 로그인

'Vue.js 3 & NodeJS > 사내교육 - 점심 메뉴' 카테고리의 다른 글
| [17] Frontend 개발 - Login 관리, 모듈화 (0) | 2026.08.05 |
|---|---|
| [16] Frontend 개발 - Login 관리, Axios Interceptor (0) | 2026.08.05 |
| [14] Backend 개발 - 사용자 API 개발, 라우트 별 인증 처리, 인가 처리 (0) | 2026.08.05 |
| [13] Backend 개발 - 사용자 API 개발, Access Token, Refresh Token (0) | 2026.08.05 |
| [12] Frontend 개발 - 상태 관리, Login 관리, ID Token (0) | 2026.08.04 |