๐ 05. ์ธ๋ถ ์ธ์ฆ ์๋ฒ ์ฐ๋๊ณผ ํ ํฐ ๊ด๋ฆฌ: "์๋ก๊ณ ์นจ ํ๋๊น ๋ก๊ทธ์์ ๋๋๋ฐ์?"
๐ ๊ฐ์
Zustand์ ์์์ฑ(Persist) ๋ฏธ๋ค์จ์ด๋ฅผ ํ์ฉํ ํด๋ผ์ด์ธํธ ์ธ์ฆ ์ํ ๋ฐ JWT ๋ณด์ ์ ๋ต
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- Zustand
persist๋ฏธ๋ค์จ์ด๋ฅผ ํตํด ๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ ์ธ์ ์คํ ๋ฆฌ์ง์ ์ํ๋ฅผ ์๋ ๋๊ธฐํํ ์ ์๋ค.- JWT (Access Token & Refresh Token) ์ธ์ฆ ํ๋ก์ฐ์์ ์ํ ๊ด๋ฆฌ์ ๋ณด์์ ํธ๋ ์ด๋์คํ๋ฅผ ์ดํดํ๋ค.
- ์ธ๋ถ ์ธ์ฆ ์๋ฒ(Next.js BFF/Spring Boot) ์ฐ๋ ์ Zustand์ ๋ด์์ผ ํ ์ ๋ณด์ ๋ด์ง ๋ง์์ผ ํ ์ ๋ณด๋ฅผ ์๋ฒฝํ ํต์ ํ ์ ์๋ค.
๐ ๋ชฉ์ฐจ
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ 1. ์คํ ์ด ์์์ฑ ๋ถ์ฌ:
persist๋ฏธ๋ค์จ์ด - ๐ก๏ธ 2. ์์ฒ ์ด์ ๋์ฐธ์ฌ: JWT ๋ณด์์ ๋ซ
- ๐ 3. ํ ํฐ ์คํ ๋ฆฌ์ง ๋ถ๋ฆฌ ์ ๋ต (์ค๋ฌด ์ํคํ ์ฒ)
- ๐ 4. ๋ฒ์ธ: Next.js ์ Hydration ๋ฏธ์ค๋งค์น ์ฃผ์
- ๐๏ธโโ๏ธ ์์ฒ ์ ํ ์คํธ ํ์ (Q&A)
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 15๋ถ / ํต์ฌ ํํธ: 10๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
์์ ์คํ ์ด์ ํ๋ฐ์ฑ ๋ฌธ์ โ persist ๋ฏธ๋ค์จ์ด ์ ์ฉ โ JWT ํ ํฐ์ ๋ณด์ ์ ์ฅ์ฒ ๋ถ๋ฐฐ ์ ๋ต (XSS ๋ฐฉ์ด)
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ๐ฃ ์์ฒ ( ์ ์ ): "๋ฆฌ๋ ๋! Zustand๋ก ์ ์ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์์๊ฒ ๋ง๋ค์ด์์ด์. ์์ ๋น ๋ฆ ๋๋ค! ๊ทผ๋ฐ... ์ฌ์ฉ์๊ฐ F5(์๋ก๊ณ ์นจ)๋ฅผ ๋๋ฅด๋๊น ๋ค ๋ ์๊ฐ๋ฉด์ ๋ค์ ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ํ๊ธฐ๋ค์."
- ๐ฆ ์ํธ ( ๋ฆฌ๋ ): "์์ฒ ๋, JS ๋ฉ๋ชจ๋ฆฌ๋ ๋ธ๋ผ์ฐ์ ์๋ช ์ฃผ๊ธฐ์ ์ข ์๋๋๊น ๋น์ฐํ ๋ค ๋ ์๊ฐ์ฃ . ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ๋ฐฑ์ ์ ํฉ์๋ค. ์, ์ ๊น ์์(PM) ๋์ด ๋ฐฑ์๋ ๋ณด์ ์ชฝ์์ ํ์ค ๋ง์์ด ์๋ ๋ณด๋ค์."
- ๐ ์์ ( PM / ๋ฐฑ์๋ ): "์์ฒ ์จ, ํ ํฐ ๊ด๋ฆฌ๋ฅผ ๋ง์๋๋ฆฌ๋ ๊ฑด๋ฐ์. ๋ฐฑ์๋์์ ๋ฐ๊ธํด๋๋ฆฐ Access Token์ด๋ Refresh Token์ ์ ๋ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ๋ฌดํฑ๋๊ณ ์ ์ฅํ์๋ฉด ์ ๋ฉ๋๋ค. ํด์ปค๋คํํ ๋ฏ๊ธฐ๋ฉด ์ฑ ์์ง์ค ๊ฑด๊ฐ์?"
๐ค ์ ์์์ผ ํ๋๊ฐ
Zustand ๊ฐ์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ณธ์ง์ ์ผ๋ก RAM ๋จธ์ , ์ฆ ๋ธ๋ผ์ฐ์ ์ JavaScript Memory Heap์ ์์ฃผํฉ๋๋ค. ๋ฐ๋ผ์ ํ์ด์ง๊ฐ ๋ฆฌ๋ก๋๋๋ฉด ๋ชจ๋ ์ํ๋ ์ด๊ธฐ ํ ๋น ๊ฐ์ผ๋ก ๋ฆฌ์ ๋๋ฉฐ, ์ด๋ '๋ก๊ทธ์ธ ์ ์ง'๋ผ๋ ๊ฐ์ฅ ํํ ์น ์ฑ ๊ธฐ๋ฏน์ ์น๋ช ์ ์ ๋๋ค.
์ด๋ฅผ ๋ง๊ธฐ ์ํด ์น ์คํ ๋ฆฌ์ง(LocalStorage / SessionStorage)์ ์ง์ setItem, getItem์ ๋์ง๋์ง ๋ฐ๋ฅด๋ฉด ์ฝ๋๊ฐ ์คํ๊ฒํฐ๊ฐ ๋ฉ๋๋ค. Zustand๋ ์ด๋ฅผ ์ํ ๋ด์ฅ persist ๋ฏธ๋ค์จ์ด๋ฅผ ์ ๊ณตํ์ฌ ํด๋ฆญ ํ ๋ฒ ์์ค์ผ๋ก ์์์ฑ์ ๋ถ์ฌํฉ๋๋ค.
ํ์ง๋ง ์์๊ฐ ๊ฒฝ๊ณ ํ๋ฏ, ์ฌ๊ธฐ์ JWT (JSON Web Token) ์ํคํ ์ฒ์ ๋ณด์ ๋ฌธ์ ๊ฐ ์๋ฉด ์๋ก ๋ ์ค๋ฆ ๋๋ค. ์ด๋ค ๊ฑด ํด๋ผ์ด์ธํธ ์ํ๋ก ์คํ ๋ฆฌ์ง์ ์ ์ฅํด๋ ๋๊ณ , ์ด๋ค ๊ฑด ์ ๋๋ก JS๊ฐ ์ ๊ทผํ์ง ๋ชปํ๋ ๊ณณ(HttpOnly)์ผ๋ก ์จ๊ฒจ์ผ ํ ๊น์? ์ด๋ฅผ ๋ชจ๋ฅด๋ฉด ํธ๋ฆฌํจ์ ์ป๊ณ ์ฑ์ ์ฝ์ด ๋จ์ ํต์งธ๋ก ํธ๋ฆฌ๊ฒ ๋ฉ๋๋ค.
๐๏ธ 1. ์คํ ์ด ์์์ฑ ๋ถ์ฌ: persist ๋ฏธ๋ค์จ์ด
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ persist ๋ฌธ๋ฒ๋ถํฐ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface AuthState {
isLoggedIn: boolean;
username: string;
login: (name: string) => void;
logout: () => void;
}
// ๐ฆ ์ํธ: create ๊ดํธ ์์ persist ํจ์๋ก ์คํ ์ด ์ ์๋ถ๋ฅผ ํ ๋ฒ ๋ ๊ฐ์๋๋ค.
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
isLoggedIn: false,
username: '',
login: (name) => set({ isLoggedIn: true, username: name }),
logout: () => set({ isLoggedIn: false, username: '' }),
}),
{
name: 'auth-storage', // ์คํ ๋ฆฌ์ง์ ์ ์ฅ๋ ๋ ์ฌ์ฉ๋ Key ๊ณ ์ ์๋ณ์
storage: createJSONStorage(() => sessionStorage), // ๊ธฐ๋ณธ๊ฐ์ localStorage. ์ฌ๊ธฐ์ ๋ธ๋ผ์ฐ์ ์ข
๋ฃ ์ ๋ ๋ฆฌ๋ ค๊ณ session ์ฌ์ฉ.
// ๐ก ๋ถ๋ถ ์ ์ฅ ๊ธฐ๋ฅ (์ ํ์ ์์์ฑ)
partialize: (state) => ({ isLoggedIn: state.isLoggedIn, username: state.username }),
}
)
);์ด ์ฝ๋๋ฅผ ์์ฑํ๋ ์๊ฐ, Zustand๋ ์ํ์ ๋ณ๊ฒฝ์ด ์ผ์ด๋ ๋๋ง๋ค ์๋์ผ๋ก ์ง๋ ฌํํ์ฌ ๋ธ๋ผ์ฐ์ ์คํ ๋ฆฌ์ง์ ๊ฐ์ ๋๊ธฐํ์ํต๋๋ค. ์๋ก๊ณ ์นจํด๋ ๋ค์ ๊ทธ ์คํ ๋ฆฌ์ง์์ ์ญ์ง๋ ฌํํ์ฌ ์ํ ๋ณต์์ ์์์ ์ฒ๋ฆฌํด ์ค๋๋ค.
๐ก๏ธ 2. ์์ฒ ์ด์ ๋์ฐธ์ฌ: JWT ๋ณด์์ ๋ซ
์ด์ ๋ก๊ทธ์ธ ์ ๋ฐ์์ค๋ ํ ํฐ์ ๊ด๋ฆฌํด๋ณผ๊น์?
// โ ์์ฒ ์ด์ ํญํ ์ฝ๋: ๋ชจ๋ ๊ฑธ ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๊ธฐ๋ฐ Zustand์ ์ฒ๋ฐ์
login: async (credentials) => {
const { accessToken, refreshToken, user } = await api.post('/login');
// ๐ ์์ ๊ทน๋๋
ธ: "Refresh Token์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅํ๋ค๊ณ ์?!"
set({ accessToken, refreshToken, user });
}์ ์ ๋ ๊น์? (์น ๋ณด์ ๊ธฐ์ด)
- ๋ธ๋ผ์ฐ์ ์
localStorage๋sessionStorage์ ์ ์ฅ๋ ๊ฐ์ ํ์ฌ ํ์ด์ง์์ ๋์ํ๋ ์ด๋ ํ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๋ผ๋ ์ ๊ทผ(getItem)ํ ์ ์์ต๋๋ค. - ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์ ์ ๋๋ ํดํน ๊ธฐ๋ฒ์ธ XSS(Cross-Site Scripting) ๊ฐ ๋ฐ๋ํ๋ฉด ํด์ปค๊ฐ ์ฆ์ ๊ทธ ํ ํฐ์ ์คํธํ ์ ์์ต๋๋ค.
- Refresh Token์ด ํธ๋ฆฌ๋ฉด, ํด์ปค๋ ํ ํฐ ๋ง๋ฃ ์ ๊น์ง ์ ์ ํ์ธ๋ฅผ ๋ฌดํ๋๋ก ํ ์ ์์ต๋๋ค.
๐ 3. ํ ํฐ ์คํ ๋ฆฌ์ง ๋ถ๋ฆฌ ์ ๋ต (์ค๋ฌด ์ํคํ ์ฒ)
5๋ ์ฐจ ์์ผ์์๋ ์ฑ๋ฅ๊ณผ ๋ณด์ ์ฌ์ด์ ํธ๋ ์ด๋์คํ๋ฅผ ๋ง์ถ ํ์ด๋ธ๋ฆฌ๋ ์ ๋ต์ ์ ์ฉํฉ๋๋ค.
A. ์ ์ ์ ๋ณด (User Profile) -> Zustand + Persist [์์ ]
- ๋๋ค์, ํ๋กํ ์ฌ์ง URL, ํ ๋ง ์ค์ ๋ฑ์ ํ์ทจ๋์ด๋ ์ฆ๊ฐ์ ์ธ ํ๋ฉธ์ ์ด๋ฅด์ง ์๋ ์ฝ๊ธฐ ์ ์ฉ UI ๋ฐ์ดํฐ์ ๋๋ค.
- ์ด ๋ฐ์ดํฐ๋ค์ ๋ก์ปฌ์คํ ๋ฆฌ์ง์ ์ ์ฅ(Zustand Persist)ํ์ฌ ์๋ก๊ณ ์นจ ์งํ์๋ ๋ฐ๋ก ๋ ๋๋ง๋๊ฒ ๋ก๋๋ค.
B. Access Token -> ์์ Zustand (๋ฉ๋ชจ๋ฆฌ) [๋จ๊ธฐ ์ฒด๋ฅ]
- API ํธ์ถ ๋ ํค๋์ ์ค์ด์ผ ํ๋ฏ๋ก ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ์ ๊ทผํ๊ธด ํด์ผ ํฉ๋๋ค.
- ํ์ง๋ง XSS ๋ฒ์์ ๋จ์ง ์๋๋ก ๋ก์ปฌ์คํ ๋ฆฌ์ง์ ์ ์ฅํ์ง ์์ต๋๋ค (์ฆ,
partialize์ค์ ์์ ๊ฑธ๋ฌ๋ ๋๋ค). - ์๋ก๊ณ ์นจ ์ ์ด ๋ฉ๋ชจ๋ฆฌ์์ ๋ ์๊ฐ์ง๋ง, ๊ณง๋ฐ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ API๋ฅผ ํ์ ์๋ก ๋ฐ์์ค๋ฉด ๊ทธ๋ง์ ๋๋ค(Silent Refresh).
C. Refresh Token -> HttpOnly ์ฟ ํค [JS ์ ๊ทผ ์ฐจ๋จ]
- ์ด๊ฑด ์์ ํ๋ก ํธ์๋๊ฐ ๊ด๋ฆฌํ๋ฉด ์ ๋ฉ๋๋ค.
- ๋ฐฑ์๋(์์)๊ฐ
Set-Cookie: RefreshToken=...; HttpOnly; Secure;์์ฑ์ ๋ถ์ฌ์ ๋ธ๋ผ์ฐ์ ์ฟ ํค ์ง๊ฐ์ ์ง์ ๊ฝ์์ค๋๋ค. - ์๋ฐ์คํฌ๋ฆฝํธ๋ ๊ตฌ์กฐ์ ์ผ๋ก(HttpOnly) ์ด ์ฟ ํค์ ๊ฐ์ ์์ํ ์ฝ์ ์ ์์ผ๋ฏ๋ก ๋ฌด์ ์ ๋ฐฉ์ด๋ ฅ์ด ์๊น๋๋ค. ๋ธ๋ผ์ฐ์ ๊ฐ ๋ค์ ์ธ์ฆ API ์์ฒญ์ ์ ๋ ์๋์ผ๋ก ๋๋ดํด์ ๋ณด๋ผ ๋ฟ์ ๋๋ค.
์ค๋ฌด ์ฝ๋: ๋ถ๋ถ ์์์ฑ(Partialize) ํต์
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null, // ์์ํ ๋์ (UI์ฉ)
accessToken: null, // ํ๋ฐ์ฑ ๋ฉ๋ชจ๋ฆฌ์ฉ (API ์ธ์ฆ์ฉ)
setAuth: (user, token) => set({ user, accessToken: token }),
clearAuth: () => set({ user: null, accessToken: null })
}),
{
name: 'user-storage',
// ๐ฅ ํต์ฌ: ๋ก์ปฌ์คํ ๋ฆฌ์ง์๋ ์์ ์ ์ ์ ๋ณด๋ง ๋ฐฑ์
ํ๊ณ ํ ํฐ์ ๊ฑธ๋ฌ๋ธ๋ค!
partialize: (state) => ({ user: state.user }),
}
)
);๐ 4. ๋ฒ์ธ: Next.js ์ Hydration ๋ฏธ์ค๋งค์น ์ฃผ์
Zustand์ persist๋ ๊ทผ๋ณธ์ ์ผ๋ก "์๋ฒ๊ฐ ๋ชจ๋ฅด๋ ๋ธ๋ผ์ฐ์ ๋ง์ ๋ฐ์ดํฐ"๋ฅผ ์ด๊ธฐ ์ํ์ ์ง์ด๋ฃ์ต๋๋ค.
๋ง์ฝ Next.js (SSR/SSG ํ๊ฒฝ)๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด:
- ์๋ฒ์์ ์ฒ์์ ๋น ๊ฐ(
isLoggedIn: false)์ธ ์ฑ๋ก HTML ๊ป๋ฐ๊ธฐ๋ฅผ ๋ง๋ค์ด ํด๋ผ์ด์ธํธ๋ก ๋ณด๋. - ํด๋ผ์ด์ธํธ React ๋ชจ๋์ด ๊นจ์ด๋๊ณ (Hydration) Zustand๊ฐ ๋ก์ปฌ์คํ ๋ฆฌ์ง์์
true๋ฅผ ๋์ง์ด๋. - ๋ฆฌ์กํธ ์์ง: "์ด๋ผ? ์๋ฒ๊ฐ ์ค HTML์ด๋ ๋๊ฐ ๊ทธ๋ฆด HTML์ด ๋ค๋ฅด๋ค?!" ๐ Hydration Mismatch Error ํญ๋ฐ
ํด๊ฒฐ์ฑ : Hydration์ด ์์ ํ ์ข ๋ฃ๋๊ธฐ ์ ๊น์ง๋ Persist ์คํ ์ด ๋ ๋๋ง์ ๋ฆ์ถ๋(useEffect ๋ด๋ถ์์ ๊ฐ ๊บผ๋ด์ค๊ธฐ) ํธ๋ฆญ์ด ํ์์ ์ผ๋ก ๋๋ฐ๋์ด์ผ ํฉ๋๋ค. (์ด ๋ถ๋ถ์ Next.js ์ฌํ ํธ ๊ฐ์ด๋์์ ๋ ๋ค๋ฃจ๊ฒ ๋ฉ๋๋ค.)
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. Zustand์ persist ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ ๋ ๋ธ๋ผ์ฐ์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ JWT(Access Token ๋๋ Refresh Token)๋ฅผ ์ ์ฅํ๋ฉด ๋ฐ์ํ๋ ๊ฐ์ฅ ์น๋ช
์ ์ธ ๋ณด์ ์ทจ์ฝ์ ์ ๋ฌด์์ธ๊ฐ์?
- A) CSRF (Cross-Site Request Forgery)
- B) SQL Injection
- C) XSS (Cross-Site Scripting)
- D) Clickjacking
โ
์ ๋ต: C
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๋ฐ์ดํฐ๋ ๋ธ๋ผ์ฐ์ ๋ด์์ ์คํ๋๋ ๋ชจ๋ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋(
window.localStorage.getItem)์ ๋ ธ์ถ๋ฉ๋๋ค. ๊ฒ์ํ ๊ธ์ด๋ ๊ด๊ณ ์คํฌ๋ฆฝํธ ๋ฑ์ ํตํด ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์ฃผ์ ๋๋ XSS ๊ณต๊ฒฉ ์, ํด์ปค๊ฐ ์ฆ๊ฐ์ ์ผ๋ก ํ ํฐ์ ํ์ทจํ ์ ์์ต๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋, CSRF ๋ฐฉ์ด๋ฅผ ์ํด์๋ ์ฟ ํค(
HttpOnly,SameSite)๋ฅผ ์ฐ๊ฑฐ๋ ํค๋ ์ค์ ์ ์ถ๊ฐํด์ผ ํ๊ณ , ์คํ ๋ฆฌ์ง ํ ํฐ ํ์ทจ๋ ์ ํ์ ์ธ XSS ๋ฐฅ์ ๋๋ค." - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ๐ ์์: "๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ ์ ๋ฆฌ๋ฒฝ ์์ ๊ธ๊ณ ์ ๋๋ค. ์๋ฐ์คํฌ๋ฆฝํธ๋ผ๋ ์ด์ ๋ง ์์ผ๋ฉด ๋๊ตฌ๋ ๋ค์ฌ๋ค๋ณผ ์ ์์ต๋๋ค."
Q2. Zustand ์คํ ์ด์ persist ๋ฏธ๋ค์จ์ด ์ฌ์ฉ ์, ๋ณด์์ ๋ฏผ๊ฐํ ํ ํฐ ๋ฐ์ดํฐ๋ง ์คํ ๋ฆฌ์ง ๋ฐฑ์
์์ ์ ์ธํ๊ณ ์ ์ ํ๋กํ ๋ฑ ์์ ํ ์ ๋ณด๋ง ์ ์ฅํ๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ ์์ฑ์ ๋ฌด์์ธ๊ฐ์?
- A)
exclude - B)
partialize - C)
serialize - D)
getStorage
โ
์ ๋ต: B
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
:
persist์ค์ ๊ฐ์ฒด ๋ด์partialize: (state) => ({ user: state.user })์ ๊ฐ์ด ์ ์ธํ๋ฉด, ์ ์ฒด Zustand ์ํ ์ค์์ ์ง์ ๋ ์์ฑ๋ง ๊ณจ๋ผ์ ์คํ ๋ฆฌ์ง์ ์ง๋ ฌํํฉ๋๋. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋, ํ์ํ ๊ฒ๋ง ๊ฑธ๋ฌ๋ด๋ ๊ฑฐ๋ฆ๋ง ์ด๋ฆ์ด
partialize(๋ถ๋ถํ)์ ๋๋ค. ๊ผญ ์ธ์๋์ธ์!" - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ๐ฆ ์ํธ: "ํ ํฐ์ ํ๋ฐ์ํค๊ณ , ํ๋กํ์ Partialize๋ก ๋ฐฑ์ ํ์ธ์."
Q3. ๐๏ธโโ๏ธ ์์ฒ ์ ํ ์คํธ ํ์ (Q&A)
์์ฒ ์ด๊ฐ ์ผํ ์ฑ์ '์ต๊ทผ ๋ณธ ์ํ ID ๋ชฉ๋ก'์ ์ฌ์ฉ์์ ๊ธฐ๊ธฐ์ ์๊ตฌํ ๊ธฐ์ต์ํฌ ๋ชฉ์ ์ผ๋ก Zustand
persist(localStorage) ์ ์ ์ฅํ์ต๋๋ค. ํ๋ฐ ์์๋ฆฌ ์์ ๋์ด "์ฅ๋ฐ๊ตฌ๋ ๊ฒฐ์ ํ ํฐ๋ ๊ฑฐ๊ธฐ์ ๊ฐ์ด ์ ์ฅํ๋ฉด ์ ๋๋"๊ณ ์ ์์ ํ๋ค์. ์ด๋ป๊ฒ ๋ฐฉ์ดํด์ผ ํ ๊น์?
โ
์ ๋ต: ๊ฒฐ์ ํ ํฐ์ฒ๋ผ ํ์ทจ ์ ์น๋ช
์ ์ธ ๊ถํ ์ ์ด ํค๋ partialize ํจ์๋ฅผ ์ด์ฉํด ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๋ฐฑ์
๋์์์ ๋ฐ๋์ ์ ์ธ์ํจ๋ค. ํน์ ๋ฐฑ์๋์์ HttpOnly ์ฟ ํค๋ก ์ค๊ณํ๋๋ก ์ญ์ ์ํ๋ค.
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: LocalStorage์ ๊ธฐ๋ก๋ ๊ฐ์ ํ์ฌ ํญ์ ์ด๋ ค์๋ ์ด๋ ํ ์๋ํํฐ ์คํฌ๋ฆฝํธ(๊ตฌ๊ธ ์ ๋๋ฆฌํฑ์ค, ์ธ๋ถ ๊ด๊ณ ๋ฐฐ๋ ๋ฑ)๋ ์
์ฑ XSS ์ฃผ์
์ฝ๋๋ผ๋
window.localStorage.getItem๋ช ๋ น ํ๋๋ก ํ์ณ๊ฐ ์ ์์ต๋๋ค. UI ๋ ๋๋ง ์ต์ ํ๋ฅผ ์ํ ๋ถ๊ฐ ์ ๋ณด๋ง Persist์ ์์กดํด์ผ ํฉ๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋, PM์ด ํด๋ฌ๋ผ๊ณ ๋ค ํด์ฃผ๋ฉด ์ ๋ผ์. ๊ฒฐ์ ํ ํฐ์ ๋ฉ๋ชจ๋ฆฌ(
accessToken์์ฑ)์ ๋๊ณ ์๋ก๊ณ ์นจ ์ ๋ ๋ฆฐ ๋ค์ Silent Refresh ๊ธฐ๋ฒ์ผ๋ก ๋ฐฑ์๋์์ ๋ค์ ๋ฐ์์ค๊ฒ ์ค๊ณํด์ผ ์ฌ๊ณ ๋ฅผ ๋ง์ต๋๋ค." - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ๐ ์์: "์๋ฒ๊ฐ ์๋ฉด ํฐ์ผ ๋๋ ๋ฐ์ดํฐ๋ ์์ง๋ง, ํด๋ผ์ด์ธํธ(JS) ์์ญ์ ๋จ์ผ๋ฉด ํฐ์ผ ๋๋ ๋ฐ์ดํฐ๋ ์๋ ์์ด ๋ง๋ค."
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ ์์ PM๋ํํ
๋ผ๋ฅผ ์์ฒญ ์ธ๊ฒ ๋ง์๋ค. "๋ด ์ ๋ณด ๋ด๊ฐ ์ฐ๊ฒ ์คํ ๋ฆฌ์ง์ ์ ์ฅํ๊ฒ ๋ค๋๋ฐ ๋ญ๊ฐ ๋ฌธ์ ์ผ!" ๋ผ๊ณ ์์ผ๋ก ํฌ๋๋์๋๋ฐ, XSS ๊ณต๊ฒฉ์ด๋ ๊ฑธ ๋ฃ๊ณ ๋๋๊น ๊ทธ๋์ ๋ด๊ฐ ๋ง๋ ํ์๊ฐ์
๋ก์ง์ด ์ํํญํ์ด์๋ค๋ ๊ฑธ ๊นจ๋ฌ์๋ค. Zustand persist ๋ฏธ๋ค์จ์ด์์ partialize ์ต์
ํ๋ ๋ฑ ๋ฃ์ด์ ํ ํฐ ๋นผ๊ณ ์ ์ฅํ๋๊น ๋ณด์๊ณผ ์ฌ์ฉ์ ํธ์๋ฅผ ๋ ๋ค ์ก์ ๊ธฐ๋ถ์ด๋ค! ํํ, ์ด๋ฐ ๋ณด์ ๊ฐ๋
๊น์ง ์๋ ์ฃผ๋์ด๊ฐ ํํ ๊น? ๋ด์ผ ์ํธ ๋ฆฌ๋ ๋ํํ
์๊ทผ์ฌ์ฉ ์๋ํด๋ด์ผ์ง. ๋ฌ๋ฌํ ์์ด์คํฌ๋ฆผ ํ๋ ๋จน๊ณ ํน ์์ผ๊ฒ ๋ค ๐ฆ!
๐ก "๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ ๋ชจ๋๊ฐ ๋ณด๋ ํฌ๋ช ํ ๊ฒ์ํ์ด๋ค. ์ธ์ฆ ํค๋ ์จ๊ธฐ๊ณ , ๊ป๋ฐ๊ธฐ ์ ๋ณด๋ง ๊ธฐ๋กํ์."