175 lines
5.1 KiB
TypeScript
175 lines
5.1 KiB
TypeScript
|
|
// JWT 토큰 관리 유틸리티
|
||
|
|
// 기존 PersonBean 정보를 JWT 페이로드로 변환
|
||
|
|
|
||
|
|
import jwt from "jsonwebtoken";
|
||
|
|
import { PersonBean, JwtPayload } from "../types/auth";
|
||
|
|
import config from "../config/environment";
|
||
|
|
|
||
|
|
export class JwtUtils {
|
||
|
|
/**
|
||
|
|
* 사용자 정보로 JWT 토큰 생성
|
||
|
|
* 기존 PersonBean 정보를 JWT 페이로드로 변환
|
||
|
|
*/
|
||
|
|
static generateToken(userInfo: PersonBean): string {
|
||
|
|
try {
|
||
|
|
const payload: JwtPayload = {
|
||
|
|
userId: userInfo.userId,
|
||
|
|
userName: userInfo.userName,
|
||
|
|
deptName: userInfo.deptName,
|
||
|
|
companyCode: userInfo.companyCode,
|
||
|
|
userType: userInfo.userType,
|
||
|
|
userTypeName: userInfo.userTypeName,
|
||
|
|
};
|
||
|
|
|
||
|
|
return jwt.sign(payload, config.jwt.secret, {
|
||
|
|
expiresIn: config.jwt.expiresIn,
|
||
|
|
issuer: "PMS-System",
|
||
|
|
audience: "PMS-Users",
|
||
|
|
} as any);
|
||
|
|
} catch (error) {
|
||
|
|
console.error("JWT token generation error:", error);
|
||
|
|
throw new Error("토큰 생성 중 오류가 발생했습니다.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* JWT 토큰 검증 및 사용자 정보 추출
|
||
|
|
*/
|
||
|
|
static verifyToken(token: string): PersonBean {
|
||
|
|
try {
|
||
|
|
const decoded = jwt.verify(token, config.jwt.secret) as JwtPayload;
|
||
|
|
|
||
|
|
// PersonBean 형태로 변환
|
||
|
|
const personBean: PersonBean = {
|
||
|
|
userId: decoded.userId,
|
||
|
|
userName: decoded.userName,
|
||
|
|
deptName: decoded.deptName,
|
||
|
|
companyCode: decoded.companyCode,
|
||
|
|
userType: decoded.userType,
|
||
|
|
userTypeName: decoded.userTypeName,
|
||
|
|
};
|
||
|
|
|
||
|
|
return personBean;
|
||
|
|
} catch (error) {
|
||
|
|
if (error instanceof jwt.TokenExpiredError) {
|
||
|
|
throw new Error("토큰이 만료되었습니다.");
|
||
|
|
} else if (error instanceof jwt.JsonWebTokenError) {
|
||
|
|
throw new Error("유효하지 않은 토큰입니다.");
|
||
|
|
} else {
|
||
|
|
console.error("JWT token verification error:", error);
|
||
|
|
throw new Error("토큰 검증 중 오류가 발생했습니다.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* JWT 토큰에서 페이로드만 추출 (검증 없이)
|
||
|
|
*/
|
||
|
|
static decodeToken(token: string): JwtPayload | null {
|
||
|
|
try {
|
||
|
|
return jwt.decode(token) as JwtPayload;
|
||
|
|
} catch (error) {
|
||
|
|
console.error("JWT token decode error:", error);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 토큰 만료 시간 확인
|
||
|
|
*/
|
||
|
|
static isTokenExpired(token: string): boolean {
|
||
|
|
try {
|
||
|
|
const decoded = jwt.decode(token) as JwtPayload;
|
||
|
|
if (!decoded || !decoded.exp) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
const currentTime = Math.floor(Date.now() / 1000);
|
||
|
|
return decoded.exp < currentTime;
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Token expiration check error:", error);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 토큰 갱신 (만료 시간만 연장)
|
||
|
|
*/
|
||
|
|
static refreshToken(token: string): string {
|
||
|
|
try {
|
||
|
|
const decoded = jwt.decode(token) as JwtPayload;
|
||
|
|
if (!decoded) {
|
||
|
|
throw new Error("토큰을 디코드할 수 없습니다.");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 페이로드에서 만료 시간 관련 필드 제거
|
||
|
|
const { iat, exp, aud, iss, ...payload } = decoded;
|
||
|
|
|
||
|
|
return jwt.sign(payload, config.jwt.secret, {
|
||
|
|
expiresIn: config.jwt.expiresIn,
|
||
|
|
issuer: "PMS-System",
|
||
|
|
} as any);
|
||
|
|
} catch (error) {
|
||
|
|
console.error("JWT token refresh error:", error);
|
||
|
|
throw new Error("토큰 갱신 중 오류가 발생했습니다.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 토큰에서 사용자 ID 추출
|
||
|
|
*/
|
||
|
|
static getUserIdFromToken(token: string): string | null {
|
||
|
|
try {
|
||
|
|
const decoded = jwt.decode(token) as JwtPayload;
|
||
|
|
return decoded?.userId || null;
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Get user ID from token error:", error);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 토큰 유효성 검사 (만료 여부 포함)
|
||
|
|
*/
|
||
|
|
static validateToken(token: string): { isValid: boolean; error?: string } {
|
||
|
|
try {
|
||
|
|
jwt.verify(token, config.jwt.secret);
|
||
|
|
return { isValid: true };
|
||
|
|
} catch (error) {
|
||
|
|
if (error instanceof jwt.TokenExpiredError) {
|
||
|
|
return { isValid: false, error: "토큰이 만료되었습니다." };
|
||
|
|
} else if (error instanceof jwt.JsonWebTokenError) {
|
||
|
|
return { isValid: false, error: "유효하지 않은 토큰입니다." };
|
||
|
|
} else {
|
||
|
|
return { isValid: false, error: "토큰 검증 중 오류가 발생했습니다." };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 테스트용 메서드 (개발 환경에서만 사용)
|
||
|
|
*/
|
||
|
|
static testJwtUtils(userInfo: PersonBean): void {
|
||
|
|
console.log("=== JWT 토큰 테스트 ===");
|
||
|
|
console.log("사용자 정보:", userInfo);
|
||
|
|
|
||
|
|
const token = this.generateToken(userInfo);
|
||
|
|
console.log("생성된 토큰:", token);
|
||
|
|
|
||
|
|
const decoded = this.decodeToken(token);
|
||
|
|
console.log("디코드된 페이로드:", decoded);
|
||
|
|
|
||
|
|
const verified = this.verifyToken(token);
|
||
|
|
console.log("검증된 사용자 정보:", verified);
|
||
|
|
|
||
|
|
const isExpired = this.isTokenExpired(token);
|
||
|
|
console.log("토큰 만료 여부:", isExpired);
|
||
|
|
|
||
|
|
const userId = this.getUserIdFromToken(token);
|
||
|
|
console.log("토큰에서 추출한 사용자 ID:", userId);
|
||
|
|
|
||
|
|
const validation = this.validateToken(token);
|
||
|
|
console.log("토큰 유효성 검사:", validation);
|
||
|
|
}
|
||
|
|
}
|