jskim-node #402
|
|
@ -2,7 +2,6 @@
|
||||||
// Phase 2-1B: 핵심 인증 API 구현
|
// Phase 2-1B: 핵심 인증 API 구현
|
||||||
|
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { checkAuthStatus } from "../middleware/authMiddleware";
|
|
||||||
import { AuthController } from "../controllers/authController";
|
import { AuthController } from "../controllers/authController";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
@ -12,7 +11,7 @@ const router = Router();
|
||||||
* 인증 상태 확인 API
|
* 인증 상태 확인 API
|
||||||
* 기존 Java ApiLoginController.checkAuthStatus() 포팅
|
* 기존 Java ApiLoginController.checkAuthStatus() 포팅
|
||||||
*/
|
*/
|
||||||
router.get("/status", checkAuthStatus);
|
router.get("/status", AuthController.checkAuthStatus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/auth/login
|
* POST /api/auth/login
|
||||||
|
|
|
||||||
|
|
@ -161,13 +161,14 @@ export const useAuth = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const token = TokenManager.getToken();
|
const token = TokenManager.getToken();
|
||||||
if (!token || TokenManager.isTokenExpired(token)) {
|
if (!token) {
|
||||||
AuthLogger.log("AUTH_CHECK_FAIL", `refreshUserData: 토큰 ${!token ? "없음" : "만료됨"}`);
|
AuthLogger.log("AUTH_CHECK_FAIL", "refreshUserData: 토큰 없음");
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setAuthStatus({ isLoggedIn: false, isAdmin: false });
|
setAuthStatus({ isLoggedIn: false, isAdmin: false });
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 만료된 토큰이라도 apiClient 요청 인터셉터가 자동 갱신하므로 여기서 차단하지 않음
|
||||||
|
|
||||||
AuthLogger.log("AUTH_CHECK_START", "refreshUserData: API로 인증 상태 확인 시작");
|
AuthLogger.log("AUTH_CHECK_START", "refreshUserData: API로 인증 상태 확인 시작");
|
||||||
|
|
||||||
|
|
@ -177,6 +178,10 @@ export const useAuth = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// /auth/me 성공 = 인증 확인 완료. /auth/status는 보조 정보(isAdmin)만 참조
|
||||||
|
// 두 API를 Promise.all로 호출 시, 토큰 만료 타이밍에 따라
|
||||||
|
// /auth/me는 401→갱신→성공, /auth/status는 200 isAuthenticated:false를 반환하는
|
||||||
|
// 레이스 컨디션이 발생할 수 있으므로, isLoggedIn 판단은 /auth/me 성공 여부로 결정
|
||||||
const [userInfo, authStatusData] = await Promise.all([fetchCurrentUser(), checkAuthStatus()]);
|
const [userInfo, authStatusData] = await Promise.all([fetchCurrentUser(), checkAuthStatus()]);
|
||||||
|
|
||||||
if (userInfo) {
|
if (userInfo) {
|
||||||
|
|
@ -184,19 +189,12 @@ export const useAuth = () => {
|
||||||
|
|
||||||
const isAdminFromUser = userInfo.userId === "plm_admin" || userInfo.userType === "ADMIN";
|
const isAdminFromUser = userInfo.userId === "plm_admin" || userInfo.userType === "ADMIN";
|
||||||
const finalAuthStatus = {
|
const finalAuthStatus = {
|
||||||
isLoggedIn: authStatusData.isLoggedIn,
|
isLoggedIn: true,
|
||||||
isAdmin: authStatusData.isAdmin || isAdminFromUser,
|
isAdmin: authStatusData.isAdmin || isAdminFromUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
setAuthStatus(finalAuthStatus);
|
setAuthStatus(finalAuthStatus);
|
||||||
AuthLogger.log("AUTH_CHECK_SUCCESS", `사용자: ${userInfo.userId}, 인증: ${finalAuthStatus.isLoggedIn}`);
|
AuthLogger.log("AUTH_CHECK_SUCCESS", `사용자: ${userInfo.userId}, 인증: ${finalAuthStatus.isLoggedIn}`);
|
||||||
|
|
||||||
if (!finalAuthStatus.isLoggedIn) {
|
|
||||||
AuthLogger.log("AUTH_CHECK_FAIL", "API 응답에서 비인증 상태 반환 → 토큰 제거");
|
|
||||||
TokenManager.removeToken();
|
|
||||||
setUser(null);
|
|
||||||
setAuthStatus({ isLoggedIn: false, isAdmin: false });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
AuthLogger.log("AUTH_CHECK_FAIL", "userInfo 조회 실패 → 토큰 기반 임시 인증 유지 시도");
|
AuthLogger.log("AUTH_CHECK_FAIL", "userInfo 조회 실패 → 토큰 기반 임시 인증 유지 시도");
|
||||||
try {
|
try {
|
||||||
|
|
@ -412,18 +410,19 @@ export const useAuth = () => {
|
||||||
|
|
||||||
const token = TokenManager.getToken();
|
const token = TokenManager.getToken();
|
||||||
|
|
||||||
if (token && !TokenManager.isTokenExpired(token)) {
|
if (token) {
|
||||||
AuthLogger.log("AUTH_CHECK_START", `초기 인증 확인: 유효한 토큰 존재 (경로: ${window.location.pathname})`);
|
// 유효/만료 모두 refreshUserData로 처리
|
||||||
|
// apiClient 요청 인터셉터가 만료 토큰을 자동 갱신하므로 여기서 삭제하지 않음
|
||||||
|
const isExpired = TokenManager.isTokenExpired(token);
|
||||||
|
AuthLogger.log(
|
||||||
|
"AUTH_CHECK_START",
|
||||||
|
`초기 인증 확인: 토큰 ${isExpired ? "만료됨 → 갱신 시도" : "유효"} (경로: ${window.location.pathname})`,
|
||||||
|
);
|
||||||
setAuthStatus({
|
setAuthStatus({
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
});
|
});
|
||||||
refreshUserData();
|
refreshUserData();
|
||||||
} else if (token && TokenManager.isTokenExpired(token)) {
|
|
||||||
AuthLogger.log("TOKEN_EXPIRED_DETECTED", `초기 확인 시 만료된 토큰 발견 → 정리 (경로: ${window.location.pathname})`);
|
|
||||||
TokenManager.removeToken();
|
|
||||||
setAuthStatus({ isLoggedIn: false, isAdmin: false });
|
|
||||||
setLoading(false);
|
|
||||||
} else {
|
} else {
|
||||||
AuthLogger.log("AUTH_CHECK_FAIL", `초기 확인: 토큰 없음 (경로: ${window.location.pathname})`);
|
AuthLogger.log("AUTH_CHECK_FAIL", `초기 확인: 토큰 없음 (경로: ${window.location.pathname})`);
|
||||||
setAuthStatus({ isLoggedIn: false, isAdmin: false });
|
setAuthStatus({ isLoggedIn: false, isAdmin: false });
|
||||||
|
|
|
||||||
|
|
@ -329,6 +329,11 @@ apiClient.interceptors.request.use(
|
||||||
const newToken = await refreshToken();
|
const newToken = await refreshToken();
|
||||||
if (newToken) {
|
if (newToken) {
|
||||||
config.headers.Authorization = `Bearer ${newToken}`;
|
config.headers.Authorization = `Bearer ${newToken}`;
|
||||||
|
} else {
|
||||||
|
// 갱신 실패 시 인증 없는 요청을 보내면 TOKEN_MISSING 401 → 즉시 redirectToLogin 연쇄 장애
|
||||||
|
// 요청 자체를 차단하여 호출부의 try/catch에서 처리하도록 함
|
||||||
|
authLog("TOKEN_REFRESH_FAIL", `요청 인터셉터에서 갱신 실패 → 요청 차단 (${config.url})`);
|
||||||
|
return Promise.reject(new Error("TOKEN_REFRESH_FAILED"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue