FrontEND/vue

Vue에서 JWT + Refresh Token 인증 흐름 구현하기 (with Axios)

mingmingIT 2025. 4. 18. 10:14

SPA(Single Page Application) 구조의 Vue 프로젝트에서는 백엔드(Spring Boot 등)로부터 발급받은 AccessTokenRefreshToken을 이용해 인증을 처리할 수 있습니다. 이 포스팅에서는 AccessToken 자동 재발급 흐름을 구현하는 방법을 중심으로 다룹니다.


📦 전체 인증 구조 요약

[Login]
  ⬇
AccessToken + RefreshToken 발급
  ⬇
API 요청 시 → AccessToken 사용
  ⬇
AccessToken 만료 → RefreshToken 전송
  ⬇
서버에서 AccessToken 재발급 → 재요청

📁 프로젝트 구조 예시

src/
├── api/
│   └── axios.js         # Axios 설정 (인터셉터 포함)
├── auth/
│   └── tokenService.js  # 토큰 저장/관리 유틸
└── views/
    └── LoginView.vue

1️⃣ 토큰 관리 유틸 (tokenService.js)

js

// src/auth/tokenService.js
export default {
  getAccessToken() {
    return localStorage.getItem("accessToken");
  },
  getRefreshToken() {
    return localStorage.getItem("refreshToken");
  },
  saveTokens({ accessToken, refreshToken }) {
    localStorage.setItem("accessToken", accessToken);
    localStorage.setItem("refreshToken", refreshToken);
  },
  clear() {
    localStorage.removeItem("accessToken");
    localStorage.removeItem("refreshToken");
  },
};

2️⃣ Axios 인터셉터 설정 (axios.js)

js

// src/api/axios.js
import axios from "axios";
import tokenService from "@/auth/tokenService";

const api = axios.create({
  baseURL: "http://localhost:8080", // 백엔드 주소
});

// 요청에 AccessToken 자동 삽입
api.interceptors.request.use((config) => {
  const token = tokenService.getAccessToken();
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 응답 에러 처리 (AccessToken 만료 시)
api.interceptors.response.use(
  (res) => res,
  async (error) => {
    const originalRequest = error.config;

    // AccessToken이 만료된 경우
    if (error.response && error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        const refreshRes = await axios.post("http://localhost:8080/auth/refresh", null, {
          headers: {
            Authorization: `Bearer ${tokenService.getRefreshToken()}`,
          },
        });

        const newAccessToken = refreshRes.data.accessToken;
        tokenService.saveTokens({
          accessToken: newAccessToken,
          refreshToken: tokenService.getRefreshToken(), // 그대로 유지 or 서버에서 갱신
        });

        // 원래 요청 재시도
        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
        return api(originalRequest);
      } catch (e) {
        tokenService.clear();
        window.location.href = "/login";
        return Promise.reject(e);
      }
    }

    return Promise.reject(error);
  }
);

export default api;

3️⃣ 로그인 처리 예시 (LoginView.vue)

vue

<template>
  <div>
    <input v-model="username" />
    <input v-model="password" type="password" />
    <button @click="login">로그인</button>
  </div>
</template>

<script>
import axios from "@/api/axios";
import tokenService from "@/auth/tokenService";

export default {
  data() {
    return {
      username: "",
      password: "",
    };
  },
  methods: {
    async login() {
      const res = await axios.post("/auth/login", {
        username: this.username,
        password: this.password,
      });

      tokenService.saveTokens(res.data);
      this.$router.push("/home");
    },
  },
};
</script>

🧠 다이어그램: 자동 토큰 재발급 흐름


🛡️ 보안 팁

항목 권장 방식
RefreshToken 저장 위치 httpOnly 쿠키 (XSS 방지) or localStorage
토큰 만료 시간 Access: 15분, Refresh: 7일 이상
인터셉터 처리 401 시 자동 Refresh → 재시도
로그아웃 처리 localStorage 초기화 & 서버 저장소 RefreshToken 삭제

✨ 마무리

  • Axios 인터셉터를 사용하면 사용자 경험을 해치지 않고 자동으로 AccessToken을 재발급할 수 있습니다.
  • RefreshToken은 민감한 정보이므로 httpOnly 쿠키 저장 방식을 고려하면 더 안전합니다.
  • 서버와 클라이언트가 함께 설계된 토큰 구조를 갖추면, 안정적인 인증 시스템을 만들 수 있습니다.