로그인 되어있을 시 /main 으로 이동

This commit is contained in:
dohyeons 2025-10-24 10:09:19 +09:00
parent bc8587f688
commit 03039ab743
3 changed files with 76 additions and 8 deletions

View File

@ -63,13 +63,19 @@ const TokenManager = {
setToken: (token: string): void => { setToken: (token: string): void => {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
// localStorage에 저장
localStorage.setItem("authToken", token); localStorage.setItem("authToken", token);
// 쿠키에도 저장 (미들웨어에서 사용)
document.cookie = `authToken=${token}; path=/; max-age=86400; SameSite=Lax`;
} }
}, },
removeToken: (): void => { removeToken: (): void => {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
// localStorage에서 제거
localStorage.removeItem("authToken"); localStorage.removeItem("authToken");
// 쿠키에서도 제거
document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax";
} }
}, },

View File

@ -61,11 +61,15 @@ export const useLogin = () => {
* API * API
*/ */
const apiCall = useCallback(async (endpoint: string, options: RequestInit = {}): Promise<LoginResponse> => { const apiCall = useCallback(async (endpoint: string, options: RequestInit = {}): Promise<LoginResponse> => {
// 로컬 스토리지에서 토큰 가져오기
const token = localStorage.getItem("authToken");
const response = await fetch(`${API_BASE_URL}${endpoint}`, { const response = await fetch(`${API_BASE_URL}${endpoint}`, {
credentials: "include", credentials: "include",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
...(token && { Authorization: `Bearer ${token}` }),
...options.headers, ...options.headers,
}, },
...options, ...options,
@ -90,16 +94,19 @@ export const useLogin = () => {
// 토큰이 있으면 API 호출로 유효성 확인 // 토큰이 있으면 API 호출로 유효성 확인
const result = await apiCall(AUTH_CONFIG.ENDPOINTS.STATUS); const result = await apiCall(AUTH_CONFIG.ENDPOINTS.STATUS);
if (result.success && result.data?.isLoggedIn) { // 백엔드가 isAuthenticated 필드를 반환함
if (result.success && result.data?.isAuthenticated) {
// 이미 로그인된 경우 메인으로 리다이렉트 // 이미 로그인된 경우 메인으로 리다이렉트
router.push(AUTH_CONFIG.ROUTES.MAIN); router.push(AUTH_CONFIG.ROUTES.MAIN);
} else { } else {
// 토큰이 유효하지 않으면 제거 // 토큰이 유효하지 않으면 제거
localStorage.removeItem("authToken"); localStorage.removeItem("authToken");
document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax";
} }
} catch (error) { } catch (error) {
// 에러가 발생하면 토큰 제거 // 에러가 발생하면 토큰 제거
localStorage.removeItem("authToken"); localStorage.removeItem("authToken");
document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax";
console.debug("기존 인증 체크 중 오류 (정상):", error); console.debug("기존 인증 체크 중 오류 (정상):", error);
} }
}, [apiCall, router]); }, [apiCall, router]);
@ -128,9 +135,12 @@ export const useLogin = () => {
}); });
if (result.success && result.data?.token) { if (result.success && result.data?.token) {
// JWT 토큰 저장 // JWT 토큰 저장 (localStorage와 쿠키 모두에 저장)
localStorage.setItem("authToken", result.data.token); localStorage.setItem("authToken", result.data.token);
// 쿠키에도 저장 (미들웨어에서 사용)
document.cookie = `authToken=${result.data.token}; path=/; max-age=86400; SameSite=Lax`;
// 로그인 성공 // 로그인 성공
router.push(AUTH_CONFIG.ROUTES.MAIN); router.push(AUTH_CONFIG.ROUTES.MAIN);
} else { } else {
@ -148,13 +158,10 @@ export const useLogin = () => {
[formData, validateForm, apiCall, router], [formData, validateForm, apiCall, router],
); );
// 컴포넌트 마운트 시 기존 인증 상태 확인 (한 번만 실행) // 컴포넌트 마운트 시 기존 인증 상태 확인
useEffect(() => { useEffect(() => {
// 로그인 페이지에서만 실행
if (window.location.pathname === "/login") {
checkExistingAuth(); checkExistingAuth();
} }, [checkExistingAuth]);
}, []); // 의존성 배열을 비워서 한 번만 실행
return { return {
// 상태 // 상태

55
frontend/middleware.ts Normal file
View File

@ -0,0 +1,55 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
/**
* Next.js
*
*/
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 인증 토큰 확인
const token = request.cookies.get("authToken")?.value || request.headers.get("authorization")?.replace("Bearer ", "");
// /login 페이지 접근 시
if (pathname === "/login") {
// 토큰이 있으면 메인 페이지로 리다이렉트
if (token) {
const url = request.nextUrl.clone();
url.pathname = "/main";
return NextResponse.redirect(url);
}
// 토큰이 없으면 로그인 페이지 표시
return NextResponse.next();
}
// 인증이 필요한 페이지들
const protectedPaths = ["/main", "/admin", "/dashboard", "/settings"];
const isProtectedPath = protectedPaths.some((path) => pathname.startsWith(path));
if (isProtectedPath && !token) {
// 인증되지 않은 사용자는 로그인 페이지로 리다이렉트
const url = request.nextUrl.clone();
url.pathname = "/login";
return NextResponse.redirect(url);
}
return NextResponse.next();
}
/**
*
*/
export const config = {
matcher: [
/*
* :
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public
*/
"/((?!api|_next/static|_next/image|favicon.ico|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.svg$).*)",
],
};