Vue.js 3 & NodeJS/사내교육 - 점심 메뉴

[15] Frontend 개발 - Login 관리, Access Token, Refresh Token

carrotweb 2026. 8. 5. 18:12
728x90
반응형

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 만료 → 재발급(갱신) 불가  로그인

 

 

728x90
반응형