최초커밋
This commit is contained in:
254
backend-node/src/middleware/authMiddleware.ts
Normal file
254
backend-node/src/middleware/authMiddleware.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
// 인증 미들웨어
|
||||
// JWT 토큰 검증 및 사용자 정보 설정
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { JwtUtils } from "../utils/jwtUtils";
|
||||
import { AuthenticatedRequest, PersonBean } from "../types/auth";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
// Express Request 타입 확장
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
ip: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰 검증 미들웨어
|
||||
* 기존 세션 방식과 동일한 효과를 제공
|
||||
*/
|
||||
export const authenticateToken = (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void => {
|
||||
try {
|
||||
// Authorization 헤더에서 토큰 추출
|
||||
const authHeader = req.get("Authorization");
|
||||
const token = authHeader && authHeader.split(" ")[1]; // Bearer TOKEN
|
||||
|
||||
if (!token) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: "TOKEN_MISSING",
|
||||
details: "인증 토큰이 필요합니다.",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// JWT 토큰 검증 및 사용자 정보 추출
|
||||
const userInfo: PersonBean = JwtUtils.verifyToken(token);
|
||||
|
||||
// 요청 객체에 사용자 정보 설정 (기존 PersonBean과 동일)
|
||||
req.user = userInfo;
|
||||
|
||||
// 로그 기록
|
||||
logger.info(`인증 성공: ${userInfo.userId} (${req.ip})`);
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`인증 실패: ${error instanceof Error ? error.message : "Unknown error"} (${req.ip})`
|
||||
);
|
||||
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: "INVALID_TOKEN",
|
||||
details:
|
||||
error instanceof Error ? error.message : "토큰 검증에 실패했습니다.",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 선택적 인증 미들웨어 (토큰이 없어도 통과)
|
||||
* 일부 API에서 사용 (예: 공개 정보 조회)
|
||||
*/
|
||||
export const optionalAuth = (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void => {
|
||||
try {
|
||||
const authHeader = req.get("Authorization");
|
||||
const token = authHeader && authHeader.split(" ")[1];
|
||||
|
||||
if (token) {
|
||||
const userInfo: PersonBean = JwtUtils.verifyToken(token);
|
||||
req.user = userInfo;
|
||||
logger.info(`선택적 인증 성공: ${userInfo.userId} (${req.ip})`);
|
||||
} else {
|
||||
logger.info(`선택적 인증: 토큰 없음 (${req.ip})`);
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
// 토큰이 있지만 유효하지 않은 경우에도 통과 (선택적 인증)
|
||||
logger.warn(
|
||||
`선택적 인증 실패: ${error instanceof Error ? error.message : "Unknown error"} (${req.ip})`
|
||||
);
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 관리자 권한 확인 미들웨어
|
||||
*/
|
||||
export const requireAdmin = (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void => {
|
||||
if (!req.user) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: "AUTHENTICATION_REQUIRED",
|
||||
details: "인증이 필요합니다.",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 기존 Java 로직과 동일: plm_admin 사용자만 관리자로 인식
|
||||
if (req.user.userId === "plm_admin") {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: "ADMIN_REQUIRED",
|
||||
details: "관리자 권한이 필요합니다.",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 특정 사용자 또는 관리자 권한 확인 미들웨어
|
||||
*/
|
||||
export const requireUserOrAdmin = (targetUserId: string) => {
|
||||
return (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void => {
|
||||
if (!req.user) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: "AUTHENTICATION_REQUIRED",
|
||||
details: "인증이 필요합니다.",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 본인 또는 관리자인 경우 통과
|
||||
if (req.user.userId === targetUserId || req.user.userId === "plm_admin") {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: "PERMISSION_DENIED",
|
||||
details: "권한이 없습니다.",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 토큰 갱신 미들웨어
|
||||
* 토큰이 곧 만료될 경우 자동으로 갱신
|
||||
*/
|
||||
export const refreshTokenIfNeeded = (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void => {
|
||||
try {
|
||||
const authHeader = req.get("Authorization");
|
||||
const token = authHeader && authHeader.split(" ")[1];
|
||||
|
||||
if (token) {
|
||||
// 토큰이 1시간 이내에 만료되는지 확인
|
||||
const decoded = JwtUtils.decodeToken(token);
|
||||
if (decoded && decoded.exp) {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
const timeUntilExpiry = decoded.exp - currentTime;
|
||||
|
||||
// 1시간(3600초) 이내에 만료되는 경우 갱신
|
||||
if (timeUntilExpiry > 0 && timeUntilExpiry < 3600) {
|
||||
const newToken = JwtUtils.refreshToken(token);
|
||||
|
||||
// 새로운 토큰을 응답 헤더에 포함
|
||||
res.setHeader("X-New-Token", newToken);
|
||||
logger.info(`토큰 갱신: ${decoded.userId} (${req.ip})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
// 토큰 갱신 실패해도 요청은 계속 진행
|
||||
logger.warn(
|
||||
`토큰 갱신 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||
);
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 인증 상태 확인 미들웨어
|
||||
* 토큰 유효성만 확인하고 사용자 정보는 설정하지 않음
|
||||
*/
|
||||
export const checkAuthStatus = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void => {
|
||||
try {
|
||||
const authHeader = req.get("Authorization");
|
||||
const token = authHeader && authHeader.split(" ")[1];
|
||||
|
||||
if (!token) {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
isAuthenticated: false,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const validation = JwtUtils.validateToken(token);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
isAuthenticated: validation.isValid,
|
||||
error: validation.error,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`인증 상태 확인 실패: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
isAuthenticated: false,
|
||||
error: "인증 상태 확인 중 오류가 발생했습니다.",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user