๐น ์๋ก
์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ก๊ทธ์ธ ๋ฐ ์ธ์ฆ ๋ณด์ ๊ฐํ๋ ํ์์ ๋๋ค. ์ ๋ ํ๋ก์ ํธ <๊ฑฐ์ง๋๊ธฐ : wallet Guardians>์ ํ๋ก ํธ์๋์์ ๋ก๊ทธ์ธ, ํ ํฐ ์ธ์ฆ, ์ฌ์ฉ์ ์ ๋ณด ๋ณดํธ, ์ ๊ทผ ์ ์ด ๋ฑ์ ๋ณด์ ๊ณผ์ ์ ์ค๊ณํ๋ฉด์ ๋ณด๋ค ์์ ํ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์๋๋ก ๊ฐ์ ํ์ต๋๋ค.
์ด๋ฒ ํฌ์คํ ์์๋ ์ฐ๋ฆฌ๊ฐ ์ ์ฉํ ๋ณด์ ์ค๊ณ ๊ณผ์ ์ ๋ํด ์ ๋ฆฌํฉ๋๋ค.
1๏ธโฃ JWT ๊ธฐ๋ฐ ์ธ์ฆ ์์คํ ๊ตฌ์ถ
โ ๋ก๊ทธ์ธ ์ ํ ํฐ ๋ฐ๊ธ ๋ฐ ์ ์ฅ
- ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ๋ฉด JWT(JSON Web Token) ๋ฅผ ๋ฐ๊ธํ์ฌ ์ธ์ฆ์ ๊ด๋ฆฌํฉ๋๋ค.
- accessToken๊ณผ refreshToken์ ์๋ฒ์์ ์๋ต๋ฐ์ ํด๋ผ์ด์ธํธ(LocalStorage)์ ์ ์ฅํฉ๋๋ค.๋น์ฐํ ์ด ๊ณผ์ ์ ๋ฐฑ์๋์ ์ํต์ผ๋ก์ ์ ํ๋ ๋ฐฉ์์ ๋๋ค.๋ก๊ทธ์ธ ํจ์ ๋ก์ง์ ๋๋ค.
const handleLogin = async (e) => {
e.preventDefault();
try {
const data = await login(email, password);
const { accessToken, refreshToken } = data.data;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
} catch (error) {
console.error('๋ก๊ทธ์ธ ์คํจ:', error);
}
};
โ ์์ฒญ ์ธํฐ์ ํฐ์์ ์ต์ ํ ํฐ ๋ฐ์ -> ์ฌ์ฌ์ฉ์ฑ ์ฆ๊ฐ
api ํต์ ์ ์๋ง์ ์ปดํฌ๋ํธ์์ ์งํํ๊ธฐ์ ํ๋์ axios ํต์ ๊ฐ์ฒด์ธ apiClient.jsx๋ฅผ ํ์ฉํ์ต๋๋ค.
apiClient ํต์ ๊ฐ์ฒด์ ์์ฑ ์์ , 2๊ฐ์ ์ธํฐ์ ํฐ
- Axios ์ธํฐ์ ํฐ๋ฅผ ํ์ฉํ์ฌ API ์์ฒญ ์ ํญ์ ์ต์ ํ ํฐ์ ์๋์ผ๋ก ์ถ๊ฐํ๋๋ก ์ค์
- ์ด๊ธฐ API ํด๋ผ์ด์ธํธ ์์ฑ ์ headers์ ํ ํฐ์ ์ถ๊ฐํ๋ ๊ฒ์ด ์๋๋ผ, ์ธํฐ์ ํฐ์์ ์์ฒญ ์์ ์ ์ถ๊ฐํ๋ ๋ฐฉ์์ ์ฌ์ฉ (ํ ํฐ ๊ฐฑ์ ์ ์ฆ์ ๋ฐ์ ๊ฐ๋ฅ)
- ์ด๊ธฐ ์์ ์ ์ถ๊ฐํด๋ฒ๋ฆฌ๋ฉด ์ค๋ฅ๊ฐ ๋ ๊ฐ๋ฅ์ฑ์ด ๋์. ๋์ ์ธ ์ฒ๋ฆฌ๋ฅผ ์ํด์ ๋งค ์์ฒญ๋ง๋ค ์ธํฐ์ ํธํ์ฌ ๊ด๋ฆฌํด์ผํจ
apiClient.interceptors.request.use(
(config) => {
const accessToken = localStorage.getItem('accessToken');
if (accessToken) {
config.headers['ACCESS-AUTH-KEY'] = `BEARER ${accessToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
2๏ธโฃ ๋ก๊ทธ์์ ๋ฐ ํ ํฐ ๊ด๋ฆฌ
โ ๋ก๊ทธ์์ ์ ํ ํฐ ์ ๊ฑฐ
- ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์์ํ๋ฉด ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ํ ํฐ์ ์ญ์ ํ๊ณ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
- ์ด๋ฅผ ํตํด ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ๋์ผํ ๋ธ๋ผ์ฐ์ ์์ ์ ๊ทผํ ์ํ์ ๋ฐฉ์งํฉ๋๋ค.
export const logout = async () => {
try {
await apiClient.delete('/auth/logout');
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
} catch (e) {
console.log('๋ก๊ทธ์์ ์ค ์๋ฌ ๋ฐ์:', e);
}
};
3๏ธโฃ ์ฌ์ฉ์ ์ ๋ณด ๋ณดํธ ๋ฐ ์ ๊ทผ ์ ์ด
โ ์ฌ์ฉ์ ์ ๋ณด ์กฐํ API ์ ์ฉ
- ๋ก๊ทธ์ธ ํ, ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์กฐํํ์ฌ ์ ์ ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
- ์ ์ ์ ์ญํ (Role) ๋ฐ ID๋ฅผ ํ์ฉํด ํ์ด์ง ์ ๊ทผ์ ์ ํํ ์ ์๋๋ก ์ค๊ณํฉ๋๋ค.
export const getUserInfo = async () => {
try {
const response = await apiClient.get('/auth/info');
return response.data.data;
} catch (error) {
console.error('์ ์ ์ ๋ณด ์กฐํ ์คํจ:', error);
throw error;
}
};
โ ๊ณตํต ๋ ์ด์์์ ์ ์ ์ ๋ณด ํ์
- Navbar์ ์ ์ ์ ๋ณด๋ฅผ ํ์ํ์ฌ ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์๋ฅผ ์ง๊ด์ ์ผ๋ก ์ ์ ์๋๋ก ๊ตฌํํ์ต๋๋ค.
import { useEffect, useState } from 'react';
import { getUserInfo } from '../api/authApi';
const UserInfoComponent = () => {
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
const fetchUserData = async () => {
try {
const data = await getUserInfo();
setUserInfo(data);
} catch (error) {
console.error('์ ์ ์ ๋ณด ์กฐํ ์คํจ:', error);
}
};
fetchUserData();
}, []);
return userInfo ? <p>๐ค {userInfo.username}</p> : <p>์ ์ ์ ๋ณด ์์</p>;
};
4๏ธโฃ ๋ณด์ ๊ฐํ๋ ์ ๊ทผ ์ ํ ๋ก์ง
โ AuthenticatedComponent๋ฅผ ํ์ฉํ ์ ๊ทผ ์ ํ
- ๋ก๊ทธ์ธ ๋ฐ ๋ชฉํ ๊ธ์ก ์ค์ ์ด ์๋ฃ๋ ๊ฒฝ์ฐ๋ง ๋ฉ์ธ ํ์ด์ง๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ํจ.
- ๋ก๊ทธ์ธ์ด ์ ๋ ๊ฒฝ์ฐ → /login์ผ๋ก ์ด๋
- ๋ชฉํ ๊ธ์ก์ด ์๋ ๊ฒฝ์ฐ → /goal-setting์ผ๋ก
import { useEffect, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { GoalContext } from '../context/GoalContext';
const AuthenticatedComponent = ({ children }) => {
const { goalAmount, loading } = useContext(GoalContext);
const navigate = useNavigate();
useEffect(() => {
if (loading) return;
const accessToken = localStorage.getItem('accessToken');
if (!accessToken) {
navigate('/login');
return;
}
if (goalAmount === null) {
navigate('/goal-setting');
}
}, [goalAmount, loading, navigate]);
return children;
};
export default AuthenticatedComponent;
โ App.jsx์ ๋ณด์ ์ ์ฉ
- ๋ก๊ทธ์ธ ๋ฐ ๋ชฉํ ๊ธ์ก ์ค์ ์ด ์๋ฃ๋ ๊ฒฝ์ฐ์๋ง ๋ฉ์ธ ํ๋ฉด ๋ฐ ์ฃผ์ ํ์ด์ง ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก ์ ์ฉ
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import StartPage from './pages/StartPage';
import LoginPage from './pages/LoginPage';
import ProfilePage from './pages/ProfilePage';
import NotFoundPage from './pages/NotFoundPage';
import IncomePage from './pages/IncomePage';
import MainPage from './pages/MainPage';
import ExpensePage from './pages/ExpensePage';
import InitialPage from './pages/InitialPage';
import GoalSettingPage from './pages/GoalSettingPage';
import GraphPage from './pages/GraphPage';
import InputEntryPage from './pages/InputEntryPage';
import ReceiptPicPage from './pages/ReceiptPicPage';
import { GoalProvider } from './context/GoalContext'; // โ
GoalContext import
import { SidebarProvider } from './context/SidebarContext';
import { FriendProvider } from './context/FriendContext';
import FriendModal from './components/FriendModal';
import AuthenticatedComponent from './components/AuthenticatedComponent'; // โ
์ธ์ฆ ์ปดํฌ๋ํธ ์ถ๊ฐ
import Layout from './components/Layout'; // โ
๊ณตํต ๋ ์ด์์ import
import './style/MainPage.scss';
const App = () => {
return (
<SidebarProvider>
<GoalProvider>
<FriendProvider>
<Routes>
{/* โ
์ธ์ฆ์ด ํ์ํ ํ์ด์ง๋ฅผ AuthenticatedComponent๋ก ๊ฐ์ธ๊ธฐ */}
<Route
element={
<AuthenticatedComponent>
<Layout />
</AuthenticatedComponent>
}
>
<Route path="/main" element={<MainPage />} />
<Route path="/profile" element={<ProfilePage />} />
<Route path="/income" element={<IncomePage />} />
<Route path="/expenses" element={<ExpensePage />} />
<Route path="/graph" element={<GraphPage />} />
<Route path="/input-entry/:date" element={<InputEntryPage />} />
<Route path="/receipt-picture" element={<ReceiptPicPage />} />
</Route>
{/* ๐น ๊ฐ๋ณ ํ์ด์ง (์ธ์ฆ ํ์ ์์) */}
<Route path="/initial" element={<InitialPage />} />
<Route path="/goal-setting" element={<GoalSettingPage />} />
<Route path="/" element={<StartPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="*" element={<NotFoundPage />} /> {/* 404 ํ์ด์ง */}
</Routes>
<FriendModal />
</FriendProvider>
</GoalProvider>
</SidebarProvider>
);
};
export default App;
โ ๊ฒฐ๋ก
๐น ํ ์์ . ์ค๊ณํ ๋ณด์ ์์คํ (ํ๋กํ ํ์ )
- JWT ๊ธฐ๋ฐ ์ธ์ฆ ์์คํ ์ ์ฉ (ํ ํฐ ๋ฐ๊ธ ๋ฐ ์์ฒญ ์ ์ต์ ํ ํฐ ๋ฐ์)
- ๋ก๊ทธ์์ ์ ํ ํฐ ์ ๊ฑฐ๋ก ๋ณด์ ๊ฐํ
- ์ ์ ์ ๋ณด ๋ณดํธ ๋ฐ ์ญํ (Role) ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด
- ๋ชฉํ ๊ธ์ก ์ค์ ์ฌ๋ถ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ ๊ทผ ์ ํ (AuthenticatedComponent ํ์ฉ)
๐น ๋ณด์ ํจ๊ณผ
- ๋ก๊ทธ์ธ ์ฌ๋ถ๋ฅผ ์ฒดํฌํ์ฌ ๋น์ธ๊ฐ ์ฌ์ฉ์ ์ฐจ๋จ
- ๋ชฉํ ๊ธ์ก์ ์ค์ ํ์ง ์์ผ๋ฉด ์ฃผ์ ๊ธฐ๋ฅ ์ ๊ทผ ์ ํ
- ์ต์ ํ ํฐ์ ๋ฐ์ํ์ฌ ์ธ์ฆ ์ค๋ฅ ๋ฐฉ์ง