Vue.js에서 로그인한 사용자 정보를 전역적으로 보관하고 사용하는 여러 방법을 제공해드리겠습니다. Vue 3에서는 **Pinia**, **Composables**, **provide/inject** 등을 활용할 수 있습니다.
## 방법 1: Pinia 상태 관리 (권장)
### 1. Pinia 설치 및 설정
```bash
npm install pinia
```
**main.js**
```javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import axios from 'axios'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
// Axios 설정
axios.defaults.baseURL = 'http://localhost:8080'
app.mount('#app')
```
### 2. 사용자 정보 Store 생성
**stores/auth.js**
```javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import axios from 'axios'
import { useRouter } from 'vue-router'
export const useAuthStore = defineStore('auth', () => {
// 상태 (State)
const user = ref(null)
const token = ref(localStorage.getItem('jwt-token'))
const loading = ref(false)
const error = ref('')
// 계산된 속성 (Getters)
const isAuthenticated = computed(() => !!token.value && !!user.value)
const userEmail = computed(() => user.value?.email || '')
const userName = computed(() => user.value?.name || '')
const userRoles = computed(() => user.value?.roles || [])
const isAdmin = computed(() => userRoles.value.includes('ROLE_ADMIN'))
const isManager = computed(() => userRoles.value.includes('ROLE_MANAGER'))
// 액션 (Actions)
const login = async (credentials) => {
loading.value = true
error.value = ''
try {
const response = await axios.post('/api/authenticate', credentials)
if (response.data.jwt) {
// 토큰 저장
token.value = response.data.jwt
localStorage.setItem('jwt-token', response.data.jwt)
// axios 헤더 설정
setAuthToken(response.data.jwt)
// 사용자 정보 저장
user.value = {
email: response.data.email || credentials.email,
name: response.data.name || '',
roles: response.data.roles || ['ROLE_USER'],
id: response.data.userId || null
}
// 사용자 정보를 localStorage에도 저장 (새로고침 대응)
localStorage.setItem('user-info', JSON.stringify(user.value))
return { success: true }
}
} catch (err) {
error.value = err.response?.status === 401
? '이메일 또는 비밀번호가 올바르지 않습니다.'
: '로그인 중 오류가 발생했습니다.'
return { success: false, error: error.value }
} finally {
loading.value = false
}
}
const logout = async () => {
loading.value = true
try {
// 서버에 로그아웃 요청
await axios.post('/api/logout')
} catch (err) {
console.error('로그아웃 API 호출 실패:', err)
} finally {
// 로컬 상태 정리
clearAuthData()
loading.value = false
}
}
const fetchUserProfile = async () => {
if (!token.value) return
loading.value = true
try {
const response = await axios.get('/api/user/profile')
user.value = {
...user.value,
...response.data
}
localStorage.setItem('user-info', JSON.stringify(user.value))
} catch (err) {
console.error('사용자 프로필 가져오기 실패:', err)
if (err.response?.status === 401) {
clearAuthData()
}
} finally {
loading.value = false
}
}
const updateUserProfile = async (profileData) => {
loading.value = true
try {
const response = await axios.put('/api/user/profile', profileData)
user.value = { ...user.value, ...response.data }
localStorage.setItem('user-info', JSON.stringify(user.value))
return { success: true }
} catch (err) {
error.value = '프로필 업데이트 중 오류가 발생했습니다.'
return { success: false, error: error.value }
} finally {
loading.value = false
}
}
const clearAuthData = () => {
user.value = null
token.value = null
error.value = ''
localStorage.removeItem('jwt-token')
localStorage.removeItem('user-info')
delete axios.defaults.headers.common['Authorization']
}
const setAuthToken = (authToken) => {
if (authToken) {
axios.defaults.headers.common['Authorization'] = `Bearer ${authToken}`
} else {
delete axios.defaults.headers.common['Authorization']
}
}
const initializeAuth = () => {
const savedToken = localStorage.getItem('jwt-token')
const savedUser = localStorage.getItem('user-info')
if (savedToken && savedUser) {
token.value = savedToken
user.value = JSON.parse(savedUser)
setAuthToken(savedToken)
// 사용자 정보 갱신 (선택적)
fetchUserProfile()
}
}
return {
// 상태
user,
token,
loading,
error,
// 계산된 속성
isAuthenticated,
userEmail,
userName,
userRoles,
isAdmin,
isManager,
// 액션
login,
logout,
fetchUserProfile,
updateUserProfile,
clearAuthData,
initializeAuth
}
})
```
### 3. 컴포넌트에서 사용하기
**Login.vue**
```vue
<template>
<div class="login-container">
<div class="login-form">
<h2>로그인</h2>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label for="email">이메일:</label>
<input
type="email"
id="email"
v-model="loginForm.email"
required
placeholder="이메일을 입력하세요"
/>
</div>
<div class="form-group">
<label for="password">비밀번호:</label>
<input
type="password"
id="password"
v-model="loginForm.password"
required
placeholder="비밀번호를 입력하세요"
/>
</div>
<button type="submit" :disabled="authStore.loading" class="login-btn">
{{ authStore.loading ? '로그인 중...' : '로그인' }}
</button>
</form>
<div v-if="authStore.error" class="error-message">
{{ authStore.error }}
</div>
</div>
</div>
</template>
<script setup>
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const authStore = useAuthStore()
const loginForm = reactive({
email: '',
password: ''
})
const handleLogin = async () => {
const result = await authStore.login(loginForm)
if (result.success) {
await router.push('/dashboard')
}
}
</script>
```
**Dashboard.vue**
```vue
<template>
<div class="dashboard-container">
<header class="dashboard-header">
<div class="user-info">
<h1>대시보드</h1>
<p>환영합니다, {{ authStore.userName || authStore.userEmail }}님!</p>
</div>
<div class="header-actions">
<span class="user-roles">
권한: {{ authStore.userRoles.join(', ') }}
</span>
<button @click="handleLogout" class="logout-btn" :disabled="authStore.loading">
{{ authStore.loading ? '로그아웃 중...' : '로그아웃' }}
</button>
</div>
</header>
<main class="dashboard-content">
<div class="user-profile">
<h3>사용자 정보</h3>
<p><strong>이메일:</strong> {{ authStore.userEmail }}</p>
<p><strong>이름:</strong> {{ authStore.userName }}</p>
<p><strong>권한:</strong> {{ authStore.userRoles.join(', ') }}</p>
<p><strong>관리자 여부:</strong> {{ authStore.isAdmin ? 'Yes' : 'No' }}</p>
<p><strong>매니저 여부:</strong> {{ authStore.isManager ? 'Yes' : 'No' }}</p>
</div>
<!-- 관리자만 볼 수 있는 섹션 -->
<div v-if="authStore.isAdmin" class="admin-section">
<h3>관리자 전용 기능</h3>
<button @click="adminFunction" class="admin-btn">
관리자 기능 실행
</button>
</div>
<!-- 일반 사용자 기능 -->
<div class="user-actions">
<button @click="fetchProfile" class="action-btn" :disabled="authStore.loading">
프로필 새로고침
</button>
<button @click="updateProfile" class="action-btn">
프로필 수정
</button>
</div>
</main>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const authStore = useAuthStore()
const handleLogout = async () => {
await authStore.logout()
router.push('/login')
}
const fetchProfile = async () => {
await authStore.fetchUserProfile()
}
const updateProfile = async () => {
// 프로필 수정 로직
const profileData = {
name: '새로운 이름'
}
const result = await authStore.updateUserProfile(profileData)
if (result.success) {
alert('프로필이 업데이트되었습니다.')
}
}
const adminFunction = () => {
alert('관리자 기능이 실행되었습니다!')
}
</script>
```
### 4. 라우터 가드에서 활용
**router/index.js**
```javascript
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import Login from '@/components/Login.vue'
import Dashboard from '@/components/Dashboard.vue'
import AdminPanel from '@/components/AdminPanel.vue'
const routes = [
{
path: '/',
redirect: '/dashboard'
},
{
path: '/login',
name: 'Login',
component: Login,
meta: { requiresAuth: false }
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: { requiresAuth: true }
},
{
path: '/admin',
name: 'AdminPanel',
component: AdminPanel,
meta: {
requiresAuth: true,
requiresAdmin: true
}
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
// 인증이 필요한 페이지
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next('/login')
return
}
// 관리자 권한이 필요한 페이지
if (to.meta.requiresAdmin && !authStore.isAdmin) {
next('/dashboard') // 또는 권한 없음 페이지
return
}
// 이미 로그인한 사용자가 로그인 페이지 접근 시
if (to.path === '/login' && authStore.isAuthenticated) {
next('/dashboard')
return
}
next()
})
export default router
```
### 5. App.vue에서 초기화
**App.vue**
```vue
<template>
<div id="app">
<router-view />
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth'
const authStore = useAuthStore()
onMounted(() => {
// 앱 시작 시 인증 상태 초기화
authStore.initializeAuth()
})
</script>
```
## 방법 2: Composables 방식 (더 간단한 방법)
**composables/useAuthStore.js**
```javascript
import { ref, computed, reactive } from 'vue'
import axios from 'axios'
// 전역 상태
const state = reactive({
user: JSON.parse(localStorage.getItem('user-info')) || null,
token: localStorage.getItem('jwt-token') || null,
loading: false,
error: ''
})
export function useAuthStore() {
// 계산된 속성
const isAuthenticated = computed(() => !!state.token && !!state.user)
const userInfo = computed(() => state.user)
const isAdmin = computed(() => state.user?.roles?.includes('ROLE_ADMIN'))
// 사용자 정보 업데이트
const setUser = (userData) => {
state.user = userData
localStorage.setItem('user-info', JSON.stringify(userData))
}
// 토큰 설정
const setToken = (token) => {
state.token = token
if (token) {
localStorage.setItem('jwt-token', token)
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
} else {
localStorage.removeItem('jwt-token')
delete axios.defaults.headers.common['Authorization']
}
}
// 로그인
const login = async (credentials) => {
state.loading = true
state.error = ''
try {
const response = await axios.post('/api/authenticate', credentials)
if (response.data.jwt) {
setToken(response.data.jwt)
setUser({
email: response.data.email || credentials.email,
name: response.data.name || '',
roles: response.data.roles || ['ROLE_USER']
})
return { success: true }
}
} catch (error) {
state.error = '로그인에 실패했습니다.'
return { success: false, error: state.error }
} finally {
state.loading = false
}
}
// 로그아웃
const logout = () => {
setToken(null)
setUser(null)
state.error = ''
}
return {
// 상태
user: computed(() => state.user),
loading: computed(() => state.loading),
error: computed(() => state.error),
// 계산된 속성
isAuthenticated,
userInfo,
isAdmin,
// 메서드
login,
logout,
setUser,
setToken
}
}
```
## 방법 3: provide/inject 방식
**App.vue**
```vue
<template>
<div id="app">
<router-view />
</div>
</template>
<script setup>
import { provide, reactive, computed } from 'vue'
const authState = reactive({
user: JSON.parse(localStorage.getItem('user-info')) || null,
token: localStorage.getItem('jwt-token') || null
})
const auth = {
state: authState,
isAuthenticated: computed(() => !!authState.token && !!authState.user),
userInfo: computed(() => authState.user),
setUser(userData) {
authState.user = userData
localStorage.setItem('user-info', JSON.stringify(userData))
},
clearAuth() {
authState.user = null
authState.token = null
localStorage.removeItem('user-info')
localStorage.removeItem('jwt-token')
}
}
provide('auth', auth)
</script>
```
**컴포넌트에서 사용**
```vue
<script setup>
import { inject } from 'vue'
const auth = inject('auth')
// 사용 예시
console.log(auth.userInfo.value)
console.log(auth.isAuthenticated.value)
</script>
```
## 권장사항
**Pinia 사용을 권장하는 이유:**
- Vue 3 공식 상태 관리 라이브러리
- 타입스크립트 지원 우수
- 개발자 도구 지원
- 모듈화된 상태 관리
- SSR 지원
**사용자 정보 보관 전략:**
- 민감하지 않은 정보만 클라이언트에 저장
- 토큰은 httpOnly 쿠키 사용 고려
- 새로고침 시 상태 복구 로직 구현
- 권한 기반 UI 렌더링
이렇게 구현하면 Vue.js 애플리케이션 전체에서 로그인한 사용자 정보를 쉽게 접근하고 활용할 수 있습니다.
출처
카테고리 없음
로그인한 정보를 vue에 보관했다가 필요할 때 사용하려면 어떻게 해?
반응형
반응형