ads367 / AJIN-ERP star
류윤주 류윤주 2024-03-12
240312 이세현 로그인
@e699584895b49bea854089b6dbf72dd1185220e6
build.gradle
--- build.gradle
+++ build.gradle
@@ -40,7 +40,9 @@
 	implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1'
 	// https://mvnrepository.com/artifact/org.json/json
 	implementation group: 'org.json', name: 'json', version: '20231013'
-
+	// https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto
+	implementation group: 'org.springframework.security', name: 'spring-security-crypto', version: '6.2.1'
+	// https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
 	//compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
 	implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
 	// https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310
 
build/resources/main/spring/mapper/mybatis-config.xml (added)
+++ build/resources/main/spring/mapper/mybatis-config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0/EN"
+        "http://mybatis.org/dtd/mybatis-3-config.dtd">
+<configuration>
+    <settings>
+        <setting name="cacheEnabled" value="true"/> <!-- mapper 캐시 전역 사용여부 -->
+        <setting name="lazyLoadingEnabled" value="false"/> <!-- mybatis 지연 로딩 사용여부 -->
+        <setting name="multipleResultSetsEnabled" value="true"/> <!-- 한개의 구문에서 여러개의  ResultSet 허용 여부 -->
+        <setting name="useColumnLabel" value="true"/> <!-- 컬럼명 대신 컬럼라벨 사용 여부 -->
+        <setting name="useGeneratedKeys" value="false"/> <!-- 키자동생성 -->
+        <setting name="defaultExecutorType" value="SIMPLE"/>
+        <setting name="defaultStatementTimeout" value="25000"/>
+        <setting name="callSettersOnNulls" value="true"/>
+    </settings>
+
+    <typeAliases>
+        <!-- 회원정보 객체 -->
+        <typeAlias type="com.ajin.ajinerp.user.member.vo.Member" alias="Member"/>
+        <!-- 로그인로그 객체 -->
+        <typeAlias type="com.ajin.ajinerp.user.member.vo.LoginLogVO" alias="LoginLogVO"/>
+    </typeAliases>
+
+</configuration>(파일 끝에 줄바꿈 문자 없음)
 
build/resources/main/spring/mapper/user/login-SQL.xml (added)
+++ build/resources/main/spring/mapper/user/login-SQL.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+
+<mapper namespace="com.ajin.ajinerp.user.member.dao.LoginDAO">
+	<!-- 로그인 매퍼 -->
+	<resultMap id="loginLog" type="LoginLogVO">
+		<result property="userid" column="USERID" />
+		<result property="usernm" column="USERNM" />
+		<result property="contgu" column="CONTGU" />
+		<result property="errxxx" column="ERRXXX" />
+		<result property="ipaddr" column="IPADDR" />
+		<result property="tmlreg" column="TMLREG" />
+		<result property="macadd" column="MACADD" />
+		<result property="sysdat" column="SYSDAT" />
+		<result property="systim" column="SYSTIM" />
+	</resultMap>
+
+	<!-- 2.로그인 로그 -->
+	<insert id="insertLoginLog" parameterType="LoginLogVO">
+		INSERT INTO USLOGXXT
+			(
+			 USERID,
+			 USERNM,
+			 CONTGU,
+			 ERRXXX,
+			 IPADDR,
+			 TMLREG,
+			 SYSDAT,
+			 SYSTIM)
+		VALUES
+			(#{userid},
+			 #{usernm},
+			 #{contgu},
+			 #{errxxx},
+			 #{ipaddr},
+			 #{tmlreg},
+    		TO_CHAR(SYSDATE, 'YYYYMMDD'), -- 현재 날짜를 YYYYMMDD 포맷으로 변환
+     		TO_CHAR(SYSDATE, 'HH24MISS')) -- 현재 시간을 HH24MISS 포맷으로 변환
+	</insert>
+</mapper>(파일 끝에 줄바꿈 문자 없음)
 
build/resources/main/spring/mapper/user/member-SQL.xml (added)
+++ build/resources/main/spring/mapper/user/member-SQL.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+
+<mapper namespace="com.ajin.ajinerp.user.member.dao.MemberDAO">
+
+	<!-- 사용자 매퍼 -->
+	<resultMap id="memberResult" type="Member">
+		<result property="userid" column="USERID" />
+		<result property="usernm" column="USERNM" />
+		<result property="oldwrd" column="OLDWRD" />
+		<result property="newwrd" column="NEWWRD" />
+		<result property="pw_ymd" column="PW_YMD" />
+		<result property="prjcod" column="PRJCOD" />
+		<result property="compcd" column="COMPCD" />
+		<result property="buscod" column="BUSCOD" />
+		<result property="kangub" column="KANGUB" />
+		<result property="saygub" column="SAYGUB" />
+		<result property="iduptx" column="IDUPTX" />
+		<result property="dtmupt" column="DTMUPT" />
+		<result property="tmlupt" column="TMLUPT" />
+		<result property="imagex" column="IMAGEX" />
+	</resultMap>
+
+	<!-- 1.아이디로 이름 조회-->
+	<select id="getNameById" parameterType="String" resultMap="memberResult">
+			SELECT NVL(USERID, '') AS USERID
+			, NVL(USERNM, '') AS USERNM
+			FROM US01001T
+			WHERE USERID = #{userid}
+			AND SAYGUB = 'Y'
+	</select>
+
+	<!-- 2. 아이디로 회원 조회 COUNT-->
+	<select id="getCountById" parameterType="String" resultType="int">
+		SELECT COUNT(*)
+		FROM US01001T
+		WHERE USERID = #{userid}
+		AND SAYGUB = 'Y'
+	</select>
+
+	<!-- 2.아이디로 이미지 조회	-->
+	<select id="getImageById" parameterType="String" resultMap="memberResult">
+		SELECT IMAGEX
+		FROM US01003T
+		WHERE USERID = #{userid}
+	</select>
+
+	<!-- 3.사용자 이미지 변경-->
+	<update id="updateUserImage" parameterType="String">
+		UPDATE US01003T
+		SET IMAGEX = #{imagex}
+		WHERE USERID = #{userid}
+	</update>
+
+	<!-- 4.사용자 마지막 비밀번호 변경 날짜 조회 -->
+	<select id="checkPasswordExpiry" parameterType="String" resultType="int">
+		SELECT COUNT(*) FROM US01001T WHERE USERID = #{userid} AND MONTHS_BETWEEN(SYSDATE, TO_DATE(PW_YMD, 'YYYYMMDD')) >= 2
+	</select>
+
+	<!-- 5.사용자 비밀번호 변경	-->
+	<update id="updatePassword" parameterType="Member">
+		UPDATE US01001T
+		SET OLDWRD = #{oldwrd}
+		, 	NEWWRD = #{newwrd}
+		,	IDUPTX = #{userid}
+		,	DTMUPT = SYSDATE
+		,	TMLUPT = #{tmlupt}
+		WHERE USERID = #{userid}
+	</update>
+
+	<!-- 6.아이디 비밀번호로 회원 조회 -->
+	<select id="getMember" parameterType="Member" resultMap="memberResult">
+		SELECT NVL(USERID, '') AS USERID
+			 , NVL(USERNM, '') AS USERNM
+			 , NVL(OLDWRD, '') AS OLDWRD
+	 		 , NVL(NEWWRD, '') AS NEWWRD
+			 , NVL(PW_YMD, '') AS PW_YMD
+			 , NVL(PRJCOD, '') AS PRJCOD
+			 , NVL(COMPCD, '') AS COMPCD
+			 , NVL(KANGUB, '') AS KANGUB
+			 , NVL(SAYGUB, '') AS SAYGUB
+		FROM US01001T
+		WHERE USERID = #{userid}
+		AND NEWWRD = #{newwrd}
+		AND SAYGUB = 'Y'
+	</select>
+
+	<!-- 7.아이디 비밀번호로 회원 조회 COUNT	-->
+	<select id="getMemberCount" parameterType="Member" resultType="int">
+		SELECT COUNT(*)
+		FROM US01001T
+		WHERE USERID = #{userid}
+		AND NEWWRD = #{newwrd}
+		AND SAYGUB = 'Y'
+	</select>
+</mapper>(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/ajin/ajinerp/common/util/AsyncUtil.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/AsyncUtil.java
@@ -0,0 +1,35 @@
+package com.ajin.ajinerp.common.util;
+import com.ajin.ajinerp.common.util.reflection.ReflectionUtil;
+
+public class AsyncUtil implements Runnable {
+    
+	String classFilePath, packageName, className, methodName;
+	Object[] paramValues;
+	Class<?>[] paramTypes;
+	
+	public AsyncUtil(String classFilePath, String packageName, String className, String methodName, Object[] paramValues, Class<?>[] paramTypes) {
+		this.classFilePath = classFilePath;
+		this.packageName = packageName;
+		this.className = className;
+		this.methodName = methodName;
+		this.paramValues = paramValues;
+		this.paramTypes = paramTypes;
+	}
+	
+	public AsyncUtil(String packageName, String className, String methodName, Object[] paramValues, Class<?>[] paramTypes) {
+		this.packageName = packageName;
+		this.className = className;
+		this.methodName = methodName;
+		this.paramValues = paramValues;
+		this.paramTypes = paramTypes;
+	}
+	
+	@Override
+    public void run () {
+		//객체 생성
+		Object clazz = ReflectionUtil.classAndBeanLoad(classFilePath, (packageName + "." + className));
+		//메서드 실행
+		ReflectionUtil.invokeByMethodName(clazz, methodName, paramValues, paramTypes);
+    }
+
+}
 
src/main/java/com/ajin/ajinerp/common/util/AuthUtil.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/AuthUtil.java
@@ -0,0 +1,99 @@
+package com.ajin.ajinerp.common.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.servlet.http.HttpSession;
+
+import java.util.HashMap;
+
+public class AuthUtil {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AuthUtil.class);
+
+    //Session에 등록된 Login User ID Key
+    public static final String LOGIN_USER_SESSION = "loginUsers";
+
+    //중복 로그인가능 여부
+    public static final boolean IS_POSSIBLE_DUPLICATION_LOGIN = false;
+
+    //Session Max 시간(초)
+    public static final int SESSION_MAX_TIME = 60*60*6;//6시간
+
+    public static HashMap<String, Object> getLoginUser () {
+        try {
+            //현재 client의 HttpSession 조회
+            HttpSession session = CommonUtil.getHttpSession(false);
+            if(session == null || session.getAttribute(LOGIN_USER_SESSION) == null || ((HashMap<String, Object>) session.getAttribute(LOGIN_USER_SESSION)).get("user_id") == null) {
+                return null;
+            }else {
+                return (HashMap<String, Object>) session.getAttribute(LOGIN_USER_SESSION);
+            }
+        } catch(NullPointerException e) {
+            LOGGER.error(e.toString());
+         //   System.out.println("AuthUtil getLoginUser Error : ");
+         //   e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static String getLoginUserId () {
+        HashMap<String, Object> user = getLoginUser();
+        if (user != null) {
+            return (String) user.get("user_id");
+        } else {
+            return null;
+        }
+    }
+    public static HashMap<String, Object> getKey () {
+        try {
+            //현재 client의 HttpSession 조회
+            HttpSession session = CommonUtil.getHttpSession(true);
+            if(session == null || session.getAttribute("key") == null || ((HashMap<String, Object>) session.getAttribute("key")).get("salt") == null) {
+                return null;
+            }else {
+                return ((HashMap<String, Object>) session.getAttribute("key"));
+            }
+        } catch(NullPointerException e) {
+            LOGGER.error(e.toString());
+            return null;
+        }
+    }
+
+    public static String getKeySaltKey () {
+        HashMap<String, Object> key = getKey();
+        if (key != null ) {
+            if(key.get("salt") != null) {
+                return key.get("salt").toString();
+            }else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+    public static String getKeyIvtKey () {
+        HashMap<String, Object> key = getKey();
+        if (key != null ) {
+            if(key != null) {
+                return key.get("iv").toString();
+            }else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+    public static String getKeyENC_KEY () {
+        HashMap<String, Object> key = getKey();
+        if (key != null ) {
+            if(key.get("ENC_KEY") != null) {
+                return key.get("ENC_KEY").toString();
+            }else{
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+}
 
src/main/java/com/ajin/ajinerp/common/util/CommonUtil.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/CommonUtil.java
@@ -0,0 +1,801 @@
+package com.ajin.ajinerp.common.util;
+
+
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpSession;
+
+import org.json.XML;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+public class CommonUtil {
+
+	/**
+     * @author 최정우
+     * @since 2019.12.11
+     * 
+     * 데이터의 표준화 사용 유무 
+     */
+	private static boolean IS_USE_STANDARD = true;
+	
+	public static void setIsUseStandard (boolean isUseStandard) {
+		IS_USE_STANDARD = isUseStandard;
+	}
+	
+	public static boolean getIsUseStandard () {
+		return IS_USE_STANDARD;
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 빈 문자열 검사
+     */
+    public static boolean isNull(Object obj) {
+        return obj == null;
+    }
+    
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * string to int check
+     */
+    public static boolean isInt (String text) {
+		try {
+			Integer.parseInt(text);
+			return true;
+		} catch(NumberFormatException e) {
+			return false;
+		}
+	}
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * string to int check
+     */
+    public static boolean isLong (String text) {
+		try {
+			Long.parseLong(text);
+			return true;
+		} catch(NumberFormatException e) {
+			return false;
+		}
+	}
+	
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * string to double check
+     */
+	public static boolean isDouble (String text) {
+		try {
+			Double.parseDouble(text);
+			return true;
+		} catch (NumberFormatException e) {
+			return false;
+		} catch (Exception e) {
+			return false;
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2020.01.08
+     * 
+     * object to int
+     */
+	public static int parseInt (Object obj) {
+		try {
+			return Integer.parseInt(obj.toString());
+		} catch(Exception e) {
+			return 0;
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2020.01.08
+     * 
+     * string to int
+     */
+	public static int parseInt (String text) {
+		try {
+			return Integer.parseInt(text);
+		} catch(NumberFormatException e) {
+			return 0;
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2020.01.08
+     * 
+     * int to double 
+     */
+	public static long parseLong (int number) {
+		try {
+			return (long) number;
+		} catch(Exception e) {
+			return 0;
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2020.01.08
+     * 
+     * object to double 
+     */
+	public static long parseLong (String text) {
+		try {
+			return Long.parseLong(text);
+		} catch(Exception e) {
+			return 0;
+		}
+	}
+
+	/**
+     * @author 최정우
+     * @since 2020.01.08
+     * 
+     * object to double 
+     */
+	public static long parseLong (Object obj) {
+		try {
+			if (obj instanceof Integer) {
+				return (long) obj;
+			} else {
+				return Long.parseLong(obj.toString());
+			}
+		} catch(Exception e) {
+			return 0;
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2020.01.08
+     * 
+     * int to double 
+     */
+	public static double parseDouble (int number) {
+		try {
+			return (double) number;
+		} catch(Exception e) {
+			return 0.0;
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2020.01.08
+     * 
+     * object to double 
+     */
+	public static double parseDouble (String text) {
+		try {
+			return Double.parseDouble(text);
+		} catch(Exception e) {
+			return 0.0;
+		}
+	}
+
+	/**
+     * @author 최정우
+     * @since 2020.01.08
+     * 
+     * object to double 
+     */
+	public static double parseDouble (Object obj) {
+		try {
+			if (obj instanceof Integer) {
+				return (double) obj;
+			} else {
+				return Double.parseDouble(obj.toString());
+			}
+		} catch(Exception e) {
+			return 0.0;
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열의 모든 공백 제거
+     */
+	public static String allTrim(String text) {
+		return text.replaceAll("\\p{Z}", "");
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열 앞뒤 공백 제거후, 문자열 사이에 존재하는 공백을 한개의 공백으로 치환
+     * ex) " abcd     efg    hijk   " => "abcd efg hijk" 
+     */
+	public static String normalizeSpace(String text) {
+		return text.trim().replaceAll("\\s+", " ");
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 숫자 빼고 모든 문자 제거
+     */
+	public static String getOnlyNumber (String text) {
+		return text.replaceAll("[^0-9]", "");
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자 빼고 모든 숫자 제거
+     */
+	public static String getOnlyText (String text) {
+		return text.replaceAll("[0-9]", "");
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 특정 문자열 개수 check
+     */
+	public static int getWordCount (String text, String word) {
+		int size = 0;
+		int fromIndex = -1;
+		while ((fromIndex = text.indexOf(word, fromIndex + 1)) >= 0) {
+			size++;
+		}
+		return size;
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열 to Date문자열
+     */
+	public static boolean isDate (String text) {
+		if (StringUtil.isEmpty(text) == true || StringUtil.isEmpty(getOnlyNumber(text)) == true || getOnlyNumber(text).length() < 6) {
+			return false;
+		}
+		
+		//공백을 제외한 문자얻기, 대문자로 치환
+		String newText = allTrim(text).toUpperCase();
+		
+		try {
+			//문자열의 날짜 패턴 생성
+			String pattern = createDatePattern(newText);
+			if (pattern == null) {
+				return false;
+			}
+		
+			SimpleDateFormat newPattern = new SimpleDateFormat(pattern);
+			//문자열 날짜 형태로 변환
+			newPattern.parse(newText);
+			return true;
+		} catch (Exception e) {
+			//e.printStackTrace();
+			return false;
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열 to Date문자열
+     */
+	public static String parseDateText (String text) {
+		if (StringUtil.isEmpty(text) == true || StringUtil.isEmpty(getOnlyNumber(text)) == true || getOnlyNumber(text).length() < 6) {
+			return null;
+		}
+		
+		//공백을 제외한 문자얻기, 대문자로 치환
+		String newText = allTrim(text).toUpperCase();
+		
+		//문자열의 날짜 패턴 생성 
+		String pattern = createDatePattern(newText);
+		if (pattern == null) {
+			return null;
+		}
+
+		Date date = null;
+		String dateText = null;
+		try {
+			SimpleDateFormat newPattern = new SimpleDateFormat(pattern);
+			
+			//문자열 날짜 형태로 변환
+			date = newPattern.parse(newText);
+			
+			//DB에 저장할 날짜 패턴
+			SimpleDateFormat defalutPattern = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+			dateText = defalutPattern.format(date);
+		} catch (Exception e) {
+			//e.printStackTrace();
+		}
+		return dateText;
+	}
+	
+	public static <T> List<T> mapToList (Map<?, T> map) {
+		List<T> items = new ArrayList<T>();
+		
+		if (map != null) {
+			for(Map.Entry<?, T> item : map.entrySet()) {
+				items.add(item.getValue());
+	        }
+		}
+		
+		return items;
+	}
+	
+	public static Map objectToMap(Object obj) {
+		if (obj != null) {
+			try {
+				return (Map) obj;
+			} catch (Exception e) {
+				return new HashMap();
+			}
+		} else {
+			return new HashMap();
+		}
+	}
+	
+	
+	/*
+	 * 시간 타입
+	 * PM, AM
+	 */
+	public final static List<String> TIME_TYPES = Arrays.asList(new String[] {"AM", "PM", "오전", "오후"});
+	
+	/*
+	 * 날짜 포맷 패턴's
+	 */
+	public final static List<Character> DATE_PATTERNS = Arrays.asList(new Character[] {'y', 'M', 'd', 'H', 'm', 's'});
+	
+	/*
+	 * 날짜 포맷 패턴's의 최대 문자열 수
+	 */
+	public final static List<Integer> DATE_PATTERNS_MAX_LENGTH = Arrays.asList(new Integer[] {4, 2, 2, 2, 2, 2});
+	
+	/**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열의 날짜 패턴 생성
+     */
+	public static String createDatePattern (String date) {
+		
+		List<Character> DATE_PATTERNS = Arrays.asList(new Character[] {'y', 'M', 'd', 'H', 'm', 's'});
+		
+		//시간 표기가 (0~12 -> AM, PM사용)인지 (0~23)인지 확인 후, 날짜 포맷 패턴's에 있는 시간 패턴 변경
+		int timeTypeFindIndex = -1;
+		for (int i = 0; i < TIME_TYPES.size(); i++) {
+			//("AM", "PM", "오전", "오후" 중 1개)가 포함된 단어가 있는지 확인, Index 위치를 담기(없으면 -1)
+			if ((timeTypeFindIndex = date.indexOf(TIME_TYPES.get(i))) > -1) {
+				//문자열에 포함된 ("AM", "PM", "오전", "오후" 중 1개) 삭제
+				date = date.replaceAll(TIME_TYPES.get(i), "");
+				//시간 패턴 변경 [H -> h]
+				DATE_PATTERNS.set(3, 'h');
+				break;
+			}
+		}
+		
+		//숫자를 뺀 나머지 문자열 가지고오기 ex) "2020.08.03" -> ".."
+		//숫자를 뺀 나머지 문자열 가지고오기 ex) "2020.08.03 19:20:21" -> "..::"
+		final char[] separators = getOnlyText(date).toCharArray();
+		
+	
+		
+		//사용할 최대 패턴 수
+		int maxPatterSize = 0;
+		if (DATE_PATTERNS.size() <= separators.length) {
+			maxPatterSize = DATE_PATTERNS.size();
+		} else {
+			maxPatterSize = separators.length;
+		}
+		
+		//구분자별 Index 위치's (사용할 최대 패턴 수 + 시작점:-1, 종료점:date문자열의 최종 길이)
+		List<Integer> sizeByPatterns = new ArrayList<Integer>();
+		
+		
+		//구분자 별 Index 위치 파악 후, 앞에 있는 문자열의 수 찾은 후, 추가 (마지막 패턴 뒤에 있는 문자열을 따로 처리해줘야함)
+		int fromIndex = -1;
+		for (int i = 0; i < maxPatterSize; i++) {
+			//구분자
+			char separator = separators[i];
+			
+			//'현재 찾은 위치' : 이전에 찾은 위치(찾기 시작할 위치 => fromIndex) + 1 부터 찾기 시작함
+			int currentFromIndex = date.indexOf(separator, fromIndex + 1);
+			
+			//현재 패턴의 문자열 수 = '현재 찾은 위치' - '이전에 찾은 위치' - 1 [추가]
+			sizeByPatterns.add(currentFromIndex - fromIndex - 1);			
+			
+			//'현재 찾은 위치'는 '이전에 찾은 위치'가 됨
+			fromIndex = currentFromIndex;
+		}
+		//마지막 패턴 뒤에 있는 문자열 = '문자열의 길이' - '마지막에 찾은 위치(이전에 찾은 위치)' - 1 [추가]
+		sizeByPatterns.add(date.length() - fromIndex - 1);
+		
+		
+		//패턴을 담을 변수
+		StringBuilder pattern = new StringBuilder();
+		
+		//DATE_PATTERS 순서 대로, 각 구분자 별 Index 위치 크기만큼 문자열에 패턴 삽입 + 구분자 삽입
+		//마지막 전까지만 for문 돌림
+		for (int i = 0, patternIndex = 0; i < sizeByPatterns.size() && patternIndex < DATE_PATTERNS.size(); i++, patternIndex++) {
+			
+			//패턴 추가
+			int usingSize = 0;
+			for (int j = 0; j < sizeByPatterns.get(i); j++) {
+				if (j >= usingSize + DATE_PATTERNS_MAX_LENGTH.get(patternIndex)) {
+					usingSize += DATE_PATTERNS_MAX_LENGTH.get(patternIndex++);
+					
+					/*단 한개의 패턴이라도 '최대 문자열 수'를 넘어서면 -> '날짜 아님'*/
+					if (i >= sizeByPatterns.size() || patternIndex >= DATE_PATTERNS.size()) {
+						return null;
+					}
+				}
+				
+				pattern.append(DATE_PATTERNS.get(patternIndex));
+			}
+			
+			//날짜 구분자 추가 (마지막 구분자까지만) 
+			if (i < separators.length) {
+				pattern.append(separators[i]);
+			}
+			
+			
+		}
+		
+		if (timeTypeFindIndex > -1) {
+			pattern.insert(timeTypeFindIndex, 'a');
+		}
+		
+		if(!(pattern.toString().equals("-") || pattern.toString().equals("/") || pattern.toString().equals("."))){
+			pattern = null;
+		}
+		
+		return pattern.toString();
+	}
+	
+	
+	/**
+     * @author 최정우
+     * @since 2020.01.26
+     * 
+     * ping 체크
+     */
+	public static boolean pingCheck (String ip) {
+		InetAddress inetAddress;
+		try {
+			inetAddress = InetAddress.getByName(ip);
+			return inetAddress.isReachable(1000);
+		} catch (UnknownHostException e) {
+			return false;
+		} catch (IOException e) {
+			return false;
+		} catch (Exception e) {
+			return false;
+		}
+	}
+	
+	/**
+     * @author 김성원
+     * @since 2024.01.04
+     * 
+     * 접속 체크 (ip + port)
+     */
+	public static boolean linkCheck(String ip, int port) {
+		Socket socket = new Socket();
+		try {
+			socket.connect(new InetSocketAddress(ip, port), 1000);
+			boolean isConnect = socket.isConnected();
+			socket.close();
+			return isConnect;
+		} catch (UnknownHostException e) {
+			return false;
+		} catch (IOException e) {
+			return false;
+		} catch (Exception e) {
+			return false;
+		}
+	}	
+	
+	
+	/**
+	 * @author 최정우
+	 * @since 2019.12.09
+	 * 
+	 * 데이터 셋 목록 Convert LinkedHashMap<String, Object> to List<String>
+	 */
+	public static List<List<Object>> rowDataMapToList (List<LinkedHashMap<String, Object>> rowMapData) throws Exception {
+		List<List<Object>> rowData = new ArrayList<List<Object>>();
+		for (int i = 0; i < rowMapData.size(); i++) {
+			List<Object> row = new ArrayList<Object>();
+			LinkedHashMap<String, Object> mapdata = rowMapData.get(i);
+
+	        for( String key : mapdata.keySet() ){
+	        	if (mapdata.get(key) == null) {
+	        		row.add("");//null값 대체
+	        	} else {
+	        		row.add(mapdata.get(key).toString());
+	        	}
+	        }
+	        rowData.add(row);
+		}
+		return rowData;
+	}
+
+	/**
+	 * @author 최정우
+	 * @since 2019.12.09
+	 *
+	 * 데이터 셋 목록 Convert LinkedHashMap<String, Object> to List<String>
+	 */
+	public static List<List<Object>> rowDataMapToListOnject (List<LinkedHashMap<String, Object>> rowMapData) throws Exception {
+		List<List<Object>> rowData = new ArrayList<List<Object>>();
+		for (int i = 0; i < rowMapData.size(); i++) {
+			List<Object> row = new ArrayList<Object>();
+			LinkedHashMap<String, Object> mapdata = rowMapData.get(i);
+			for( String key : mapdata.keySet() ){
+				if (mapdata.get(key) == null) {
+					row.add(null);//null값 대체
+				} else {
+					row.add(mapdata.get(key));
+				}
+			}
+			rowData.add(row);
+		}
+		return rowData;
+	}
+	
+	/**
+	 * @author 최정우
+	 * @since 2020.01.26
+	 *
+	 * 현재 client의 HttpServletRequest 조회
+	 */
+	public static HttpServletRequest getHttpServletRequest () {
+		try {
+			ServletRequestAttributes servletRequestAttribute = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
+			return servletRequestAttribute.getRequest();
+		} catch (NullPointerException e) {
+			return null;
+		}
+	}
+	
+	
+	/**
+	 * @author 최정우
+	 * @since 2020.01.26
+	 *
+	 * 현재 client의 HttpSession 조회
+	 */
+	public static HttpSession getHttpSession (boolean create) {
+		try {
+			HttpServletRequest request = getHttpServletRequest();
+			if (request != null) {
+				return request.getSession(create);
+			} else {
+				return null;
+			}
+		} catch (NullPointerException e) {
+			return null;
+		}
+	}
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.10
+	 *
+	 * 사용자 세션 정보 조회
+	 */
+	public static HashMap<String, Object> getHttpSessionMember () {
+		try {
+			HttpServletRequest request = getHttpServletRequest();
+			if(request.getSession().getAttribute(AuthUtil.LOGIN_USER_SESSION) != null) {				
+				return (HashMap<String, Object>)request.getSession().getAttribute(AuthUtil.LOGIN_USER_SESSION);	
+			}else {
+				return null;
+			}	
+		} catch (NullPointerException e) {
+			return null;
+		}
+	}
+	
+	/**
+	 * @author 최정우
+	 * @since 2020.01.26
+	 *
+	 * HttpServletRequest를 활용한 Client IP 가지고오기
+	 * Header의 X-FORWARDED-FOR 값을 통해 IP 조회, 만약 Header에 X-FORWARDED-FOR가 없으면 getRemoteAddr() 이걸로 조회
+	 */
+	public static String getClientIp () {
+		try {
+			HttpServletRequest request = getHttpServletRequest();
+			if (null != request.getHeader("X-FORWARDED-FOR")) {
+				return request.getHeader("X-FORWARDED-FOR");
+			} else {
+				return request.getRemoteAddr();
+			}
+		} catch (NullPointerException e) {
+			return null;
+		}
+	}
+	
+
+	
+	/**
+	 * @author 최정우
+	 * @since 2019.12.09
+	 * 
+	 * JSONObject to Map<String, Object>
+	 */
+	public static Map<String, Object> jsonObjectToMap( JSONObject jsonObj ) {
+        Map<String, Object> map = null;
+        try {
+            map = new ObjectMapper().readValue(jsonObj.toJSONString(), Map.class) ;
+            	
+        } catch (JsonParseException e) {
+            e.printStackTrace();
+        } catch (JsonMappingException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return map;
+    }
+	
+	/**
+	 * @author 최정우
+	 * @since 2019.12.09
+	 * 
+	 * JSONObject to List<Map<String, Object>>
+	 */
+	public static List<Map<String, Object>> jsonArrayToMap( JSONArray jsonObj ) {
+		List<Map<String, Object>> map = null;
+        try {
+            map = new ObjectMapper().readValue(jsonObj.toJSONString(), new TypeReference<List<Map<String, Object>>>(){}) ;
+            	
+        } catch (JsonParseException e) {
+            e.printStackTrace();
+        } catch (JsonMappingException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return map;
+    }
+	
+	/**
+	 * @author 최정우
+	 * @since 2019.12.09
+	 * 
+	 * xmlStr to JsonStr
+	 */
+	public static String xmlStrToJsonStr(String xmlStr) throws Exception {
+ 	    org.json.JSONObject jObject = XML.toJSONObject(xmlStr);
+ 	    ObjectMapper mapper = new ObjectMapper();
+ 	    mapper.enable(SerializationFeature.INDENT_OUTPUT);
+ 	    Object json = mapper.readValue(jObject.toString(), Object.class);
+ 	    String output = mapper.writeValueAsString(json);
+ 	    return output;
+	}
+	
+	
+	
+	/**
+     * @author 김성원 
+	 * @since 2024.01.10
+     * 
+     * 데이터베이스 난수생성
+     */
+	public static String getRandKey(String prifix) {
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
+		long timeInMillis =System.currentTimeMillis();
+		Date timeInDate = new Date(timeInMillis);		
+		return prifix+"_"+sdf.format(timeInDate)+UUID.randomUUID().toString().substring(0,5);
+	}
+	
+	/**
+     * @author 김성원 
+	 * @since 2024.01.10
+     * 
+     * 로그인된 사용자 ID 조회
+     */
+	public static String getLoginUserId() {
+		 String resultId = "";
+		 HashMap<String, Object> LoginUserInfo = new HashMap<>();
+		 HttpServletRequest request = CommonUtil.getHttpServletRequest();
+		 HttpSession session = request.getSession(false);
+		 
+		 if(session != null &&  session.getAttribute(AuthUtil.LOGIN_USER_SESSION) != null) {
+			 LoginUserInfo = (HashMap<String, Object>)session.getAttribute(AuthUtil.LOGIN_USER_SESSION);	
+			 resultId = LoginUserInfo.get("user_id").toString();
+		 }
+		 
+		return resultId;
+	}
+
+	/**
+	 * @author 김성원
+	 * @since 2024.01.10
+	 *
+	 * 로그인된 사용자 ID 조회
+	 */
+	public static String getLoginUserDeptCode() {
+		String resultId = "";
+		HashMap<String, Object> LoginUserInfo = new HashMap<>();
+		HttpServletRequest request = CommonUtil.getHttpServletRequest();
+		HttpSession session = request.getSession(false);
+
+		if(session != null &&  session.getAttribute(AuthUtil.LOGIN_USER_SESSION) != null) {
+			LoginUserInfo = (HashMap<String, Object>)session.getAttribute(AuthUtil.LOGIN_USER_SESSION);
+			resultId = LoginUserInfo.get("dept_code").toString();
+		}
+
+		return resultId;
+	}
+	
+	/**
+     * @author 김성원 
+	 * @since 2024.01.10
+     * 
+     * 로그인된 사용자 권한 조회
+     */
+	public static List<String> getLoginUserAuth() {
+		 List<String> authList = new  ArrayList<>();
+		 HashMap<String, Object> LoginUserInfo = new HashMap<>();
+		 HttpServletRequest request = CommonUtil.getHttpServletRequest();
+		 HttpSession session = request.getSession(false);		 
+		 if(session != null &&  session.getAttribute(AuthUtil.LOGIN_USER_SESSION) != null) {
+			 LoginUserInfo = (HashMap<String, Object>)session.getAttribute(AuthUtil.LOGIN_USER_SESSION);	
+			 authList = (List<String>)LoginUserInfo.get("user_auth");			
+		 }		 
+		return authList;
+	}
+
+	
+	
+}
 
src/main/java/com/ajin/ajinerp/common/util/CryptoUtil.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/CryptoUtil.java
@@ -0,0 +1,221 @@
+package com.ajin.ajinerp.common.util;
+
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Base64.Decoder;
+import java.util.Base64.Encoder;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.encrypt.AesBytesEncryptor;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * @author 김성원
+ * @since 2024.01.02
+ * 
+ * 암호화 관련 클래스 
+ */
+public class CryptoUtil {
+	
+
+	private final static String encoderKey = "bcrypt";
+	private final static String secret = "takenbmsc!@#";
+	private final static String salt = "70726574657374";
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 기본 단방향 엔코드 생성
+	 */
+	public static PasswordEncoder createDelegatingPasswordEncoder() {	
+		Map<String, PasswordEncoder> encoders = new HashMap<>();
+		encoders.put(encoderKey, new BCryptPasswordEncoder());	
+		return new DelegatingPasswordEncoder(encoderKey, encoders);
+	}
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 커스텀 key기반의  단방향 엔코드 생성
+	 */
+	public static PasswordEncoder createDelegatingPasswordEncoder(String key) {	
+		Map<String, PasswordEncoder> encoders = new HashMap<>();
+		encoders.put(key, new BCryptPasswordEncoder());	
+		return new DelegatingPasswordEncoder(key, encoders);
+	}
+	
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 기본 단방향 암호화 
+	 */
+	public static String PasswordEncoder(String data) {	
+		
+		PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+		Encoder encoder = Base64.getEncoder();       
+		return  encoder.encodeToString(passwordEncoder.encode(data).getBytes(StandardCharsets.UTF_8)) ;
+	}
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 커스텀 key기반의  단방향 암호화 
+	 */
+	public static String PasswordEncoder(String key , String data) {	
+		
+		if(StringUtil.isEmpty(data)) {
+			return data;
+		}
+		
+		PasswordEncoder passwordEncoder = createDelegatingPasswordEncoder(key);
+		
+		return passwordEncoder.encode(data);
+	}
+		
+	
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 단방향 암호화 비교구문 
+	 */
+	public static boolean passwordMatch(String data, String checkData) {
+		
+		PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+		Decoder decoder = Base64.getDecoder();		
+		return passwordEncoder.matches(data,new String(decoder.decode(checkData), StandardCharsets.UTF_8) );
+		
+	}
+	
+
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 기본 key, salt 기반의 양방향 암호화 객체 생성  
+	 */
+	public static AesBytesEncryptor aesBytesEncryptor() {
+	    return new AesBytesEncryptor("232323", salt);
+	}
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 커스텀 key, salt 기반의 양방향 암호화 객체 생성  
+	 */
+	public static AesBytesEncryptor aesBytesEncryptor(String key, String salt) {
+	    return new AesBytesEncryptor(key, salt);
+	    
+	}	
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 기본 key, salt 기반의 양방향 암호화 
+	 */
+	public static String encryptData(String data) {
+		if(StringUtil.isEmpty(data)) {
+			return data;
+		}
+		AesBytesEncryptor  bytesEncryptor =  aesBytesEncryptor();
+        byte[] encrypt = bytesEncryptor.encrypt(data.getBytes(StandardCharsets.UTF_8));
+        Encoder encoder = Base64.getEncoder();       
+        return encoder.encodeToString(encrypt); 
+    }
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 커스텀  key, salt 기반의 양방향 암호화 
+	 */
+	public static String encryptData(String key, String salt, String data) {
+		if(StringUtil.isEmpty(data)) {
+			return data;
+		}
+		AesBytesEncryptor  bytesEncryptor =  aesBytesEncryptor(key, salt);
+        byte[] encrypt = bytesEncryptor.encrypt(data.getBytes(StandardCharsets.UTF_8));
+        Encoder encoder = Base64.getEncoder();       
+        return encoder.encodeToString(encrypt); 
+    }
+	
+	
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 기본 key, salt 기반의 양방향 복호화
+	 */
+	public static String decryptData(String data) {
+		if(StringUtil.isEmpty(data)) {
+			return data;
+		}
+		AesBytesEncryptor  bytesEncryptor =  aesBytesEncryptor();
+		Decoder decoder = Base64.getDecoder();		
+        byte[] decrypt = bytesEncryptor.decrypt(decoder.decode(data));
+        return new String(decrypt, StandardCharsets.UTF_8);
+    }	
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 커스텀 key, salt 기반의 양방향 복호화
+	 */
+	public static String decryptData(String key, String salt, String data) {
+		if(StringUtil.isEmpty(data)) {
+			return data;
+		}
+		AesBytesEncryptor  bytesEncryptor =  aesBytesEncryptor(key, salt);
+		Decoder decoder = Base64.getDecoder();		
+        byte[] decrypt = bytesEncryptor.decrypt(decoder.decode(data));
+        return new String(decrypt, StandardCharsets.UTF_8);
+    }	
+	
+	 
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 	* 바이트 스트링 변환
+	*/
+	public static String byteArrayToString(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte abyte :bytes){
+            sb.append(abyte);
+            sb.append(" ");
+        }
+        return sb.toString();
+    }
+	
+	/**
+	 * @author 김성원
+	 * @since 2024.01.09
+	 * 
+	 * 스트링 바이트 변환
+	*/
+	public static byte[] stringToByteArray(String byteString) {
+        String[] split = byteString.split("\\s");
+        ByteBuffer buffer = ByteBuffer.allocate(split.length);
+        for (String s : split) {
+            buffer.put((byte) Integer.parseInt(s));
+        }
+        return buffer.array();
+    }
+	
+}
 
src/main/java/com/ajin/ajinerp/common/util/PaginationSupport.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/PaginationSupport.java
@@ -0,0 +1,32 @@
+package com.ajin.ajinerp.common.util;
+
+/**
+ * 페이징 지원 객체 입니다.
+ *
+ * @author 서영석
+ * @since 2023.10.24
+ */
+public class PaginationSupport {
+
+    //한 페이지당 보여질 데이터 개수 (Default)
+    private final static int PER_PAGE = 10;
+
+    /**
+     * @author 서영석
+     * @since 2023.10.24
+     * 내용 : PostgreSQL 데이터
+     */
+    public static int pagingRowIndexForPostgreSql (int currentPage) {
+        return pagingRowIndexForPostgreSql(currentPage, PER_PAGE);
+    }
+    /**
+     * @author 서영석
+     * @since 2023.10.24
+     * 내용 : PostgreSQL 데이터
+     */
+    public static int pagingRowIndexForPostgreSql (int currentPage, int perPage) {
+        int startIndex = 0;
+        startIndex = (currentPage - 1) * perPage;
+        return startIndex;
+    }
+}
 
src/main/java/com/ajin/ajinerp/common/util/SesssionEventListener.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/SesssionEventListener.java
@@ -0,0 +1,292 @@
+package com.ajin.ajinerp.common.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.servlet.annotation.WebListener;
+import jakarta.servlet.http.HttpSession;
+import jakarta.servlet.http.HttpSessionEvent;
+import jakarta.servlet.http.HttpSessionListener;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author 서영석
+ * @since 2023.05.31
+ *
+ * Custom session 저장소를 만들어 놓고
+ * Tomcat이 관리하는 session 생성 또는 소멸시, 발생하는 이벤트를 활용하여 (HttpSessionListener를 통해 이벤트를 받을 수 있음)
+ * Custom session 저장소에 추가, 삭제를 통해
+ * session을 적절히 controll 하기위한 목적을 가진 Class 입니다.
+ */
+@WebListener
+public class SesssionEventListener implements HttpSessionListener {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SesssionEventListener.class);
+
+    //싱글턴패턴 사용을 위한 클래스변수
+    public static SesssionEventListener sesssionEventListener = null;
+
+    //싱글턴패턴으로 객체 생성 후 리턴.
+    public static synchronized SesssionEventListener getInstance() {
+        if(sesssionEventListener == null) {
+            sesssionEventListener = new SesssionEventListener();
+        }
+        return sesssionEventListener;
+    }
+
+    /**
+     * Custom session 저장소 (tomcat이 관리하고있는 실제 session저장소는 아님) 목록
+     * ConcurrentHashMap을 사용하는 이유 :
+     * HashMap은 Thread Safe 하지 않기 때문에 싱글턴 패턴에서 해당 session 저장소를 사용하기 위해서는 Thread Safe 한 ConcurrentHashMap를 사용
+     * HashTable도 Thread Safe 하지만, 읽기 쓰기 둘다 한개의 Thread만 접근 가능함
+     * ConcurrentHashMap은 쓰기에서한 한개의 Tread만 접근 가능하도록 해놓음 (읽기는 여러개의 쓰레드에서 동시 접근 가능)
+     */
+    private static final Map<String, HttpSession> sessions = new ConcurrentHashMap<>();
+
+    /**
+     * @author 서영석
+     * @since 2023.05.31
+     *
+     * @param userId : 사용자(회원) ID
+     *
+     * userId와 동일한 로그인 session을 가진 session ID 목록 조회
+     */
+    public synchronized static List<String> duplicationLoginSessionIdSelectListByUserId (String userId) {
+        List<String> result = new ArrayList<>();
+        try {
+            //Custom session 저장소에서 session ID는 다르지만 userId가 같은게 존재하는지 확인
+            for (String key : sessions.keySet()) {
+                HttpSession session = sessions.get(key);
+                if (session == null || session.getAttribute(AuthUtil.LOGIN_USER_SESSION) == null) {
+                    //세션이 존재하고, 로그인 정보가 없으면 continue;
+                    continue;
+                } else {
+                    //로그인 정보가 있으면 -> 중복 확인
+                    HashMap<String, Object> loginUser = (HashMap<String, Object>) session.getAttribute(AuthUtil.LOGIN_USER_SESSION);
+                    String loginUserId = (String) loginUser.get("user_id");
+
+                    /** 사용자(회원) ID와 동일한 로그인 정보 일 시 -> add **/
+                    if (userId.equals(loginUserId) == true) {
+                        result.add(session.getId());
+                    } else {
+                        //동일한 로그인 정보가 아니면 -> continue;
+                        continue;
+                    }
+                }
+            }
+        } catch (NullPointerException e) {
+            LOGGER.error(e.toString());
+        }
+
+        return result;
+    }
+
+
+    /**
+     * @author 서영석
+     * @since 2023.05.31
+     *
+     * 현재 client의 정보와 중복된 로그인 session ID 목록 조회
+     *
+     * 현재 User의 세션과 Custom session 저장소에 저장된 session 중
+     * session ID는 다르지만 userId가 같은 session List return
+     */
+    public synchronized static List<String> duplicationLoginSessionIdSelectList () {
+        List<String> result = new ArrayList<>();
+
+        try {
+            //현재 사용자의 session
+            HttpSession currentUserSession = CommonUtil.getHttpSession(false);
+            //현재 사용자의 session이 존재하고, 로그인 정보가 있을 때
+            if (currentUserSession != null && currentUserSession.getAttribute(AuthUtil.LOGIN_USER_SESSION) == null) {
+                //현재 사용자의 로그인 정보
+                HashMap<String, Object> currentLoginUser = (HashMap<String, Object>) currentUserSession.getAttribute(AuthUtil.LOGIN_USER_SESSION);
+                String currentLoginUserId = (String) currentLoginUser.get("user_id");
+
+                //Custom session 저장소에서 session ID는 다르지만 userId가 같은게 존재하는지 확인
+                for (String key : sessions.keySet()) {
+                    HttpSession session = sessions.get(key);
+                    if (session == null || session.getAttribute(AuthUtil.LOGIN_USER_SESSION) == null) {
+                        //세션이 존재하고, 로그인 정보가 없으면 continue;
+                        continue;
+                    } else if (currentUserSession.getId().equals(session.getId()) == true) {
+                        //동일한 세션 ID이면 continue;
+                        continue;
+                    } else {
+                        //로그인 정보가 있으면 -> 중복 확인
+                        HashMap<String, Object> duplicationLoginUser = (HashMap<String, Object>) session.getAttribute(AuthUtil.LOGIN_USER_SESSION);
+                        String duplicationLoginUserId = (String) duplicationLoginUser.get("user_id");
+
+                        /** 동일한 로그인 정보 일 시 -> add **/
+                        if (currentLoginUserId.equals(duplicationLoginUserId) == true) {
+                            result.add(session.getId());
+                        } else {
+                            //동일한 로그인 정보가 아니면 -> continue;
+                            continue;
+                        }
+                    }
+                }
+            }
+        } catch (NullPointerException e) {
+            LOGGER.error(e.toString());
+        }
+
+        return result;
+    }
+
+    /**
+     * @author 서영석
+     * @since 2023.05.31
+     *
+     * @param userId : 사용자(회원) ID
+     *
+     * userId와 동일한 로그인 session을 가진 session ID 목록 삭제
+     */
+    public static int duplicationLoginSessionDeleteByUserId (String userId) {
+        int result = 0;
+        try {
+            List<String> sessionIds = duplicationLoginSessionIdSelectListByUserId(userId);
+            for (int i = 0; i < sessionIds.size(); i++) {
+                //Custom session 저장소에서 session 무효화 시키기
+                sessions.get(sessionIds.get(i)).invalidate();
+                //Custom session 저장소에서 session 삭제
+                sessions.remove(sessionIds.get(i));
+            }
+        } catch (NullPointerException e) {
+            LOGGER.error(e.toString());
+        }
+        return result;
+    }
+
+    /**
+     * @author 서영석
+     * @since 2023.05.31
+     *
+     * 현재 client의 정보와 중복된 로그인 session 전부 무효화 시키기 (완전 삭제는 못시킴)
+     */
+    public static int duplicationLoginSessionDeleteAll () {
+        int result = 0;
+        try {
+            List<String> sessionIds = duplicationLoginSessionIdSelectList();
+            for (int i = 0; i < sessionIds.size(); i++) {
+                //Custom session 저장소에서 session 무효화 시키기
+                sessions.get(sessionIds.get(i)).invalidate();
+                //Custom session 저장소에서 session 삭제
+                sessions.remove(sessionIds.get(i));
+            }
+        } catch (NullPointerException e) {
+            LOGGER.error(e.toString());
+        }
+        return result;
+    }
+
+    //난수생성
+    public static String generateRandomHex(int length) {
+        SecureRandom random = new SecureRandom();
+        byte[] randomBytes = new byte[length / 2]; // 16진수 문자열의 길이에 맞게 바이트 배열 크기를 조절
+
+        random.nextBytes(randomBytes);
+
+        // 바이트 배열을 16진수 문자열로 변환
+        StringBuilder hexStringBuilder = new StringBuilder();
+        for (byte b : randomBytes) {
+            hexStringBuilder.append(String.format("%02x", b));
+        }
+
+        return hexStringBuilder.toString();
+    }
+
+    /**
+     * HttpSessionListener Interface로 부터 상속 받은 메소드
+     *
+     * tomcat에서 session이 생성되었을 때(session timeout), 발생하는 이벤트
+     * @param target : session 이벤트(생성) 객체
+     *
+     * session 생성 이벤트가 발생하면 Custom session 저장소(sessions)에 해당 session ID 값을 Key로 잡고 저장
+     * (※ Default로 session에 현재 Client의 IP를 넣어줌)
+     */
+    @Override
+    public void sessionCreated(HttpSessionEvent target) {
+        //System.out.println("sessionCreated - 현재 세션 개수 : " + sessions.size() + "개");
+        int i = 1;
+        for (String key : sessions.keySet()) {
+            HttpSession hs = sessions.get(key);
+            //System.out.println("sessionCreated - 세션 목록 " + (i++) + ". " + hs.getId());
+        }
+//        System.out.println("sessionCreated - 이제 추가될 session id : " +  target.getSession().getId());
+        //System.out.println("");
+        //System.out.println("");
+
+        // session에 key(salt) 값 넣기 시작
+        HashMap<String, Object> key = new HashMap<String, Object>();
+
+        // "SHA1PRNG"은 알고리즘 이름
+//        SecureRandom random = null;
+//        try {
+//            random = SecureRandom.getInstance("SHA1PRNG");
+//        } catch (NoSuchAlgorithmException e) {
+//            e.printStackTrace();
+//        }
+//        SecureRandom random = new SecureRandom();
+//
+//        byte[] saltBytes  = new byte[24];
+//        byte[] encKeyBytes = new byte[24];
+//        byte[] ivBytes = new byte[24];
+//        random.nextBytes(saltBytes);
+//        random.nextBytes(encKeyBytes);
+//        random.nextBytes(ivBytes);
+        //SALT 생성
+//        String salt = Base64.getEncoder().encodeToString(saltBytes);
+//        String ENC_KEY = Base64.getEncoder().encodeToString(encKeyBytes);
+//        String iv = Base64.getEncoder().encodeToString(ivBytes);
+        String salt = generateRandomHex(32);
+        String ENC_KEY = generateRandomHex(32);
+        String iv = generateRandomHex(32);
+        key.put("salt",salt);
+        key.put("ENC_KEY",ENC_KEY);
+        key.put("iv",iv);
+
+        target.getSession().setAttribute("key", key);
+
+//        System.out.println("key : "+key);
+        //종료
+
+
+        //현재 Client의 IP set
+        target.getSession().setAttribute("ip", CommonUtil.getClientIp());
+//        target.getSession().setAttribute("key",);
+//        System.out.println("target test1: "+target);
+//        System.out.println("target.getSession() test1: "+target.getSession());
+//        System.out.println("sessions test1: "+sessions);
+//        System.out.println("sessions test2: "+sessions.get(target.getSession().getId()));
+//        System.out.println("CommonUtil.getClientIp() test: "+CommonUtil.getClientIp());
+//        System.out.println("target.getSession() test: "+target.getSession());
+
+        //세션 저장소에 set
+        sessions.put(target.getSession().getId(), target.getSession());
+    }
+
+    /**
+     * HttpSessionListener Interface로 부터 상속 받은 메소드
+     *
+     * tomcat에서 session이 소멸되었을 때, 발생하는 이벤트
+     * @param target : session 이벤트(소멸) 객체
+     *
+     * session 제거 이벤트가 발생하면 Custom session 저장소(sessions)에 해당 session ID 값을 가지고 있는 session 무효화 및 제거
+     */
+    @Override
+    public void sessionDestroyed(HttpSessionEvent target) {
+        //소멸된 세션 Custom session 저장소에서 가지고 오기
+        if (sessions.get(target.getSession().getId()) != null) {
+            //소멸된 세션 무효화 시키기 (가능한지 모르겠지만, 일단 작성해봄)
+            sessions.get(target.getSession().getId()).invalidate();
+            //Custom session 저장소에서 해당 session 삭제
+            sessions.remove(target.getSession().getId());
+        } else {
+            return;
+        }
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/ajin/ajinerp/common/util/StringUtil.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/StringUtil.java
@@ -0,0 +1,712 @@
+package com.ajin.ajinerp.common.util;
+
+
+import java.io.UnsupportedEncodingException;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.Random;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.servlet.server.Encoding.Type;
+
+
+/**
+ * @author 최정우
+ * @since 2019.11.13
+ * 
+ * 문자열과 관련된 기능을 정의 해놓은 Util입니다.
+ */
+public class StringUtil {
+
+	private static final Logger LOGGER = LoggerFactory.getLogger(StringUtil.class);
+	
+	public static final String NULL_TEXT = "NULL";
+	
+	public static String toString(Object obj) {
+		if (obj == null) {
+			return null;
+		} else {
+			try {
+				return obj.toString();
+			} catch (Exception e) {
+				return null;
+			}
+		}
+	}
+	
+	public static String toStringNotNull(Object obj) {
+		if (obj == null) {
+			return "";
+		} else {
+			try {
+				return obj.toString();
+			} catch (Exception e) {
+				return "";
+			}
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2020.11.26
+     * 
+     * 객체를 문자열로 바꾼 후, 문자열 길이 반환
+     */
+	public static int stringLength(Object obj) {
+		if (obj == null) {
+			return 0;
+		} else {
+			try {
+				return obj.toString().length();
+			} catch (Exception e) {
+				return 0;
+			}
+		}
+	}
+	
+	/**
+     * @author 최정우
+     * @since 2020.11.26
+     * 
+     * 문자열이 Null or null or NULL인 경우 실제 null값 세팅 
+     */
+	public static boolean isNullText(String text) {
+		if (isEmpty(text) == false && text.toUpperCase().equals(NULL_TEXT)) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 빈 문자열 검사
+     */
+    public static boolean isEmpty(String text) {
+        return text == null || text.trim().length() == 0;
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * indexOf - 문자 검색 후, 해당 문자의 위치(index)반환
+     */
+    public static int indexOf(String text, String searchText) {
+        if (text == null || searchText == null) {
+            return -1;
+        }
+        return text.indexOf(searchText);
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * lastIndexOf - 문자 검색 후, 해당 문자의 위치(index)반환
+     */
+    public static int lastIndexOf(String text, String searchText) {
+    	if (text == null || searchText == null) {
+            return -1;
+        }
+        return text.lastIndexOf(searchText);
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * substringBetween - 특정 문자열 사이에 값을 뽑아내는 메서드
+     */
+    public static String substringBetween(String text, String startText, String endText) {
+		if (isEmpty(text) == true || isEmpty(startText) == true || isEmpty(endText) == true) {
+			return null;
+		}
+		text = text.toLowerCase();
+		startText = startText.toLowerCase();
+		endText = endText.toLowerCase();
+		
+		int start = text.indexOf(startText);
+		if (start != -1) {
+			int end = text.indexOf(endText, start + startText.length());
+			if (end != -1) {
+				return text.substring(start + startText.length(), end);
+			}
+		}
+		return null;
+	}
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 모든 공백 제거
+     */
+    public static String removeSpace(String text) {
+        if (isEmpty(text)) {
+            return text;
+        }
+        int length = text.length();
+        char[] newCharList = new char[length];
+        int count = 0;
+        for (int i = 0; i < length; i++) {
+            if (Character.isWhitespace(text.charAt(i)) == false) {
+            	newCharList[count++] = text.charAt(i);
+            }
+        }
+        if (count == length) {
+            return text;
+        }
+
+        return new String(newCharList, 0, count);
+    }
+    
+	 
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 소문자 변환
+     */
+    public static String lowerCase(String text) {
+        if (isEmpty(text) == true) {
+            return text;
+        } else {
+        	return text.toLowerCase();
+        }
+    }
+
+    /**
+     * 대문자 변환
+     */
+    public static String upperCase(String text) {
+    	if (isEmpty(text) == true) {
+            return text;
+        } else {
+        	return text.toUpperCase();
+        }
+    }
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 현재날짜(년,월,일)를 구하는 기능
+     */
+    public static String getToday() {
+		String pattern = "yyyy-MM-dd";
+		SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, Locale.KOREA);
+		Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+		return dateFormat.format(timestamp.getTime());
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 현재날짜(년,월,일)를 구하는 기능
+     */
+    public static String getToday(String pattern) {
+    	String defaultPattern = "yyyy-MM-dd";
+    	if (isEmpty(pattern) == true) {
+    		pattern = defaultPattern;
+    	}
+    	
+    	SimpleDateFormat dateFormat = null;
+		try {
+			dateFormat = new SimpleDateFormat(pattern, Locale.KOREA);
+		} catch (Exception e) {
+			dateFormat = new SimpleDateFormat(defaultPattern, Locale.KOREA);
+		}
+		Timestamp timestamp = new Timestamp(System.currentTimeMillis());		
+		return dateFormat.format(timestamp.getTime());
+    }
+    
+    
+    /**
+     * @author 김성원
+     * @since 2019.11.13
+     * 
+     * 현재날짜(년,월,일)에 특정 날짜 + -
+     */
+    public static String getTodayaddDate(String pattern, String type,  int date) {
+    	String defaultPattern = "yyyy-MM-dd";
+    	if (isEmpty(pattern) == true) {
+    		pattern = defaultPattern;
+    	}
+    	
+    	SimpleDateFormat dateFormat = null;
+		try {
+			dateFormat = new SimpleDateFormat(pattern, Locale.KOREA);
+		} catch (Exception e) {
+			dateFormat = new SimpleDateFormat(defaultPattern, Locale.KOREA);
+		}
+		
+		Timestamp timestamp = new Timestamp(System.currentTimeMillis());		
+		Calendar cal = Calendar.getInstance();
+		if(type.equals("year")) {
+			cal.add(Calendar.YEAR, date);
+		}else if(type.equals("month")) {
+			cal.add(Calendar.MONTH, date);
+		}else if(type.equals("day")) {
+			cal.add(Calendar.DATE, date);
+		}		
+		
+		return dateFormat.format(cal.getTime());
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 현재날짜(년,월,일)를 구하는 기능
+     */
+    public static String getToday(String yearSuffix, String monthSuffix, String daySuffix) {
+    	String defaultPattern = "yyyy년MM월dd일";
+    	if (isEmpty(yearSuffix) == true) {
+    		yearSuffix = "";
+    	}
+    	if (isEmpty(monthSuffix) == true) {
+    		monthSuffix = "";
+    	}
+    	if (isEmpty(daySuffix) == true) {
+    		daySuffix = "";
+    	}
+    	
+    	String pattern = "yyyy" + yearSuffix + "MM" + monthSuffix + "dd" + daySuffix;
+    	
+    	SimpleDateFormat dateFormat = null;
+		try {
+			dateFormat = new SimpleDateFormat(pattern, Locale.KOREA);
+		} catch (Exception e) {
+			dateFormat = new SimpleDateFormat(defaultPattern, Locale.KOREA);
+		}
+		Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+		return dateFormat.format(timestamp.getTime());
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 17자리의 현재일시를 구하는 기능
+     */
+    public static String getDateTime() {
+		// 문자열로 변환하기 위한 패턴 설정(년도-월-일 시:분:초:초(자정이후 초))
+		String pattern = "yyyyMMddHHmmssSSS";
+		SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, Locale.KOREA);
+		Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+		return dateFormat.format(timestamp.getTime());
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 원하는 패턴의 현재일시 구하는 기능
+     */
+    public static String getDateTime(String pattern) {
+    	// 문자열로 변환하기 위한 패턴 설정(년도-월-일 시:분:초:초(자정이후 초))
+    	String defaultPattern = "yyyyMMddHHmmssSSS";
+    	if (isEmpty(pattern)) {
+    		pattern = defaultPattern;
+    	}
+		SimpleDateFormat dateFormat = null;
+		try {
+			dateFormat = new SimpleDateFormat(pattern, Locale.KOREA);
+		} catch (Exception e) {
+			dateFormat = new SimpleDateFormat(defaultPattern, Locale.KOREA);
+		}
+		Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+		return dateFormat.format(timestamp.getTime());
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 현재 일시 - addDay => 원하는 패턴의 일시를 구하는 기능 
+     */
+    public static String getDateTime(String pattern, int addDay) {
+    	// 문자열로 변환하기 위한 패턴 설정(년도-월-일 시:분:초:초(자정이후 초))
+    	String defaultPattern = "yyyyMMddHHmmssSSS";	
+    	if (pattern == null) {
+    		pattern = defaultPattern;
+    	}
+		SimpleDateFormat dateFormat = null;
+		try {
+			dateFormat = new SimpleDateFormat(pattern, Locale.KOREA);
+		} catch (Exception e) {
+			dateFormat = new SimpleDateFormat(defaultPattern, Locale.KOREA);
+		}
+		Calendar cal = new GregorianCalendar();
+		cal.add(Calendar.DATE, addDay);
+		Date date = cal.getTime();
+		return dateFormat.format(date.getTime());
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 현재 일시 - addDay => 원하는 패턴의 일시를 구하는 기능 
+     */
+    public static String getDateTime(String pattern, int addDay, int addHour, int addMin, int addSec) {
+    	// 문자열로 변환하기 위한 패턴 설정(년도-월-일 시:분:초:초(자정이후 초))
+    	String defaultPattern = "yyyyMMddHHmmssSSS";	
+    	if (pattern == null) {
+    		pattern = defaultPattern;
+    	}
+		SimpleDateFormat dateFormat = null;
+		try {
+			dateFormat = new SimpleDateFormat(pattern, Locale.KOREA);
+		} catch (Exception e) {
+			dateFormat = new SimpleDateFormat(defaultPattern, Locale.KOREA);
+		}
+		Calendar cal = new GregorianCalendar();
+		cal.add(Calendar.DATE, addDay);
+		cal.add(Calendar.HOUR, addHour);
+		cal.add(Calendar.MINUTE, addMin);
+		cal.add(Calendar.SECOND, addSec);
+		Date date = cal.getTime();
+		return dateFormat.format(date.getTime());
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 현재 일시(17자리)와, 랜덤숫자(4자리)를 이용하여 키값 생성
+     */
+    public static String getCreateKey (String prefix) {
+		int random = new Random().nextInt(9999);
+		String result = prefix + "_" + getDateTime() + "_" + numberToText(random, 4);
+		return result;
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열이 Date문자열(yyyy-MM-dd)로 포맷 가능한지
+     * text: 문자열
+     * pattern: 문자열의 날짜 패턴
+     */
+    public static boolean isDate(String text, String pattern) {
+    	try {
+    		Date date = new SimpleDateFormat(pattern).parse(text);
+    		text = new SimpleDateFormat("yyyy-MM-dd").format(date);
+        	return true;
+    	} catch (java.text.ParseException e) {
+			// TODO Auto-generated catch block
+			return false;
+		}
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열을 날짜형태로 Convert
+     * text: 문자열
+     * pattern: 문자열의 날짜 패턴
+     * newPattern: 해당 문자열을 Converting할 날짜 패턴 
+     */
+    public static String textToDateText (String text, String pattern, String newPattern) {
+		String defaultPattern = "yyyy-MM-dd";
+		if (isEmpty(newPattern) == true) {
+			newPattern = defaultPattern;
+    	}
+		
+		SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
+		Date date = new Date();
+		try {
+			date = dateFormat.parse(text);
+			dateFormat.applyPattern(newPattern);
+			return dateFormat.format(date);
+        } catch (Exception e) {
+            //e.printStackTrace();
+            return text;
+        }
+	}
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 숫자 -> 문자열 -> 문자열 길이가 length보다 작을 때, length길이 만큼될 수 있도록 앞에 '0'을 붙여줌
+     */
+    public static String numberToText (int number, int length) {
+    	String text = Integer.toString(number);
+    	if (text.length() < length) {
+            int emptyLength = length - text.length();
+            for (int i = 0; i < emptyLength; i++) {
+            	text = "0" + text;
+            }
+        }
+        return text;
+    }
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열이 지정한 길이를 초과했을때 해당 문자열을 삭제하는 메서드
+     * @param text 원본 문자열 배열
+     * @param maxLength 지정길이
+     * @return 지정길이로 자른 문자열
+     */
+    public static String cutString(String text, int maxLength) {
+        String result = null;
+        if (text != null) {
+            if (text.length() > maxLength) {
+                result = text.substring(0, maxLength);
+            } else
+                result = text;
+        }
+        return result;
+    }
+	
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열이 지정한 길이를 초과했을때 지정한길이에다가 해당 문자열을 붙여주는 메서드.
+     * @param text 원본 문자열 배열
+     * @param addText 더할문자열
+     * @param maxLength 지정길이
+     * @return 지정길이로 잘라서 더할분자열 합친 문자열
+     */
+    public static String cutString(String text, String addText, int maxLength) {
+        String result = null;
+        if (text != null) {
+            if (text.length() > maxLength) {
+            	result = text.substring(0, maxLength) + addText;
+            } else
+            	result = text;
+        }
+        return result;
+    }
+
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * <p>기준 문자열에 포함된 모든 대상 문자(char)를 제거한다.</p>
+     *
+     * <pre>
+     * StringUtil.remove(null, *)       = null
+     * StringUtil.remove("", *)         = ""
+     * StringUtil.remove("queued", 'u') = "qeed"
+     * StringUtil.remove("queued", 'z') = "queued"
+     * </pre>
+     *
+     * @param str  입력받는 기준 문자열
+     * @param remove  입력받는 문자열에서 제거할 대상 문자열
+     * @return 제거대상 문자열이 제거된 입력문자열. 입력문자열이 null인 경우 출력문자열은 null
+     */
+    public static String remove(String text, char remove) {
+        if (isEmpty(text) || text.indexOf(remove) == -1) {
+            return text;
+        }
+        char[] chars = text.toCharArray();
+        int pos = 0;
+        for (int i = 0; i < chars.length; i++) {
+            if (chars[i] != remove) {
+                chars[pos++] = chars[i];
+            }
+        }
+        
+        return new String(chars, 0, pos);
+    }
+
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 원본 문자열의 포함된 특정 문자열을 새로운 문자열로 변환하는 메서드
+     * @param source 원본 문자열
+     * @param subject 원본 문자열에 포함된 특정 문자열
+     * @param object 변환할 문자열
+     * @return sb.toString() 새로운 문자열로 변환된 문자열
+     */
+    public static String replace(String text, String subject, String object) {
+        StringBuffer rtnStr = new StringBuffer();
+        String preStr = "";
+        String nextStr = text;
+        String srcStr  = text;
+
+        while (srcStr.indexOf(subject) >= 0) {
+            preStr = srcStr.substring(0, srcStr.indexOf(subject));
+            nextStr = srcStr.substring(srcStr.indexOf(subject) + subject.length(), srcStr.length());
+            srcStr = nextStr;
+            rtnStr.append(preStr).append(object);
+        }
+        rtnStr.append(nextStr);
+        return rtnStr.toString();
+    }
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 원본 문자열의 포함된 특정 문자열 첫번째 한개만 새로운 문자열로 변환하는 메서드
+     * @param source 원본 문자열
+     * @param subject 원본 문자열에 포함된 특정 문자열
+     * @param object 변환할 문자열
+     * @return sb.toString() 새로운 문자열로 변환된 문자열 / source 특정문자열이 없는 경우 원본 문자열
+     */
+    public static String replaceOnce(String source, String subject, String object) {
+        StringBuffer rtnStr = new StringBuffer();
+        String preStr = "";
+        String nextStr = source;
+        if (source.indexOf(subject) >= 0) {
+            preStr = source.substring(0, source.indexOf(subject));
+            nextStr = source.substring(source.indexOf(subject) + subject.length(), source.length());
+            rtnStr.append(preStr).append(object).append(nextStr);
+            return rtnStr.toString();
+        } else {
+            return source;
+        }
+    }
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * <code>subject</code>에 포함된 각각의 문자를 object로 변환한다.
+     *
+     * @param source 원본 문자열
+     * @param subject 원본 문자열에 포함된 특정 문자열
+     * @param object 변환할 문자열
+     * @return sb.toString() 새로운 문자열로 변환된 문자열
+     */
+    public static String replaceChar(String source, String subject, String object) {
+        StringBuffer rtnStr = new StringBuffer();
+        String preStr = "";
+        String nextStr = source;
+        String srcStr  = source;
+
+        char chA;
+
+        for (int i = 0; i < subject.length(); i++) {
+            chA = subject.charAt(i);
+
+            if (srcStr.indexOf(chA) >= 0) {
+                preStr = srcStr.substring(0, srcStr.indexOf(chA));
+                nextStr = srcStr.substring(srcStr.indexOf(chA) + 1, srcStr.length());
+                srcStr = rtnStr.append(preStr).append(object).append(nextStr).toString();
+            }
+        }
+
+        return srcStr;
+    }
+
+    /**
+     * @author 최정우
+     * @since 2019.11.13
+     * 
+     * 문자열을 다양한 문자셋(EUC-KR[KSC5601],UTF-8..)을 사용하여 인코딩하는 기능 역으로 디코딩하여 원래의 문자열을
+     * 복원하는 기능을 제공함 String temp = new String(문자열.getBytes("바꾸기전 인코딩"),"바꿀 인코딩");
+     * String temp = new String(문자열.getBytes("8859_1"),"KSC5601"); => UTF-8 에서
+     * EUC-KR
+     *
+     * @param text - 문자열
+     * @param encoding - 원래의 인코딩된 값
+     * @param decoding - 디코딩할 문자값
+     * @return 인(디)코딩 문자열
+     * @exception MyException
+     * @see
+     */
+    public static String textDecoding(String text, String encoding, String decoding) {
+		if (text == null) {
+			return null;
+		}
+	
+		try {
+			text = new String(text.getBytes(encoding), decoding);
+		} catch (UnsupportedEncodingException e) {
+			text = null;
+		}
+	
+		return text;
+    }
+    
+    
+    /**
+     * @author 최정우
+     * @since 2020.11.26
+     * 
+     * 문자열 특정 포맷팅으로 변환 ##-####
+     */
+    public static String formatConvert(String data, String format) {
+		
+    	StringBuilder bf = new StringBuilder();
+		
+    	if(StringUtil.isEmpty(format) || StringUtil.isEmpty(data)) {
+    		bf.append(data);
+    	}else {
+    		int num =  data.length()-1;    		
+    		for(int i = format.length()-1 ; i >= 0 ; i--) {
+    			if(format.charAt(i) == '#') {
+    				bf.insert(0, data.charAt(num--));
+    			}else {
+    				bf.insert(0,format.charAt(i));
+    			}
+    		}
+    	}
+		
+		return bf.toString();
+	}
+    
+    /**
+     * @author 김성원
+     * @since 2022.07.06
+     * 
+     * 날짜형식 문자열  특정 포맷팅으로 변환 ##-####
+     */
+    public static String dateConvert(String date) {
+    	
+    	StringBuilder bf = new StringBuilder();
+    	
+    	boolean dateForm = true;
+    	
+    	if(!date.contains("-") && date.length() > 4) {
+    		dateForm = false;
+    	}
+    	
+    	if(!StringUtil.isEmpty(date)) {
+    	
+    		String dateStr =  date.replaceAll("[^0-9]", "");
+    		// 년도 처리 
+    		if(dateStr.length() < 5) {
+    			bf.append(date.substring(0,4));
+    		}else {
+    			if(dateForm == true) {
+    				bf.append(StringUtil.formatConvert(dateStr.substring(0,6),"####-##"));
+    			}else {
+    				bf.append(StringUtil.formatConvert(dateStr.substring(0,6),"####-##"));
+    				//bf.append(dateStr.substring(0,4));
+    				//bf.append("년 ");
+    			}
+    		}
+    	}    
+		
+		return bf.toString();
+	}
+    
+    
+	
+}
 
src/main/java/com/ajin/ajinerp/common/util/bean/ApplicationContextProvider.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/bean/ApplicationContextProvider.java
@@ -0,0 +1,44 @@
+package com.ajin.ajinerp.common.util.bean;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author 최정우
+ * @since 2019.11.17
+ * 
+ * Spring 컨테이너(ApplicationContext)에 접근하기 위한 Class 입니다.
+ * ApplicationContextAware 구현체
+ */
+@Component
+public class ApplicationContextProvider implements ApplicationContextAware {
+    
+	/**
+	 * 해당 어플리케이션의 인스턴스(bean)들의 정보를 담은 객체 
+	 */
+    private static ApplicationContext applicationContext;
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.17
+     * 
+     * ApplicationContextAware를 구현하기 위한 메소드
+     * Spring 구동 시, 해당 Class가 스캔 당하면 applicationContext 객체가 생성됨
+     */
+    @Override
+    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
+        applicationContext = ctx;
+    }
+    
+    /**
+     * @author 최정우
+     * @since 2019.11.17
+     * 
+     * applicationContext 객체 호출
+     */
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+ 
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/ajin/ajinerp/common/util/bean/BeanUtil.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/bean/BeanUtil.java
@@ -0,0 +1,42 @@
+package com.ajin.ajinerp.common.util.bean;
+
+import org.springframework.beans.BeansException;
+
+/**
+ * @author 최정우
+ * @since 2020.11.25
+ * 
+ * ApplicationContextProvider에서 bean객체를 얻어 활용할 수 있도록 해주는 Util 입니다.
+ */
+public class BeanUtil {
+
+	/**
+     * @author 최정우
+     * @since 2019.11.17
+     * 
+     * 해당 어플리케이션에서 스프링 컨테이너가 관리하는 bean으로 등록된 객체를 이름으로 호출
+     */
+    public static Object getBean(String beanName) {
+    	try {
+    		return ApplicationContextProvider.getApplicationContext().getBean(beanName);
+    	} catch (BeansException e) {
+    		e.printStackTrace();
+    		return null;
+    	}
+    }
+	
+    /**
+     * @author 최정우
+     * @since 2019.11.17
+     * 
+     * 해당 어플리케이션에서 스프링 컨테이너가 관리하는 bean으로 등록된 객체를 객체의 타입으로 호출
+     */
+	public static Object getBean(Class<?> classType) {
+		try {
+    		return ApplicationContextProvider.getApplicationContext().getBean(classType);
+    	} catch (BeansException e) {
+    		e.printStackTrace();
+    		return null;
+    	}
+	}
+}
 
src/main/java/com/ajin/ajinerp/common/util/reflection/ParentLastURLClassLoader.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/reflection/ParentLastURLClassLoader.java
@@ -0,0 +1,88 @@
+package com.ajin.ajinerp.common.util.reflection;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/*
+ * 커스텀 URLClassLoder
+ * 
+ * WAS(톰캣)에서 URLClassLoder로 작동시 외부 라이브러리에 있는 jar파일을 읽지 못함
+ * 외부 라이브러리 class들을 먼저 읽기 위함
+ */
+public class ParentLastURLClassLoader extends ClassLoader
+{
+    private ChildURLClassLoader childClassLoader;
+
+    /**
+     * This class allows me to call findClass on a classloader
+     */
+    private static class FindClassClassLoader extends ClassLoader
+    {
+        public FindClassClassLoader(ClassLoader parent)
+        {
+            super(parent);
+        }
+
+        @Override
+        public Class<?> findClass(String name) throws ClassNotFoundException
+        {
+            return super.findClass(name);
+        }
+    }
+
+    /**
+     * This class delegates (child then parent) for the findClass method for a URLClassLoader.
+     * We need this because findClass is protected in URLClassLoader
+     */
+    private static class ChildURLClassLoader extends URLClassLoader
+    {
+        private FindClassClassLoader realParent;
+
+        public ChildURLClassLoader(URL[] urls, FindClassClassLoader realParent )
+        {
+            super(urls, null);
+
+            this.realParent = realParent;
+        }
+
+        @Override
+        public Class<?> findClass(String name) throws ClassNotFoundException
+        {
+            try
+            {
+                // first try to use the URLClassLoader findClass
+                return super.findClass(name);
+            }
+            catch( ClassNotFoundException e )
+            {
+                // if that fails, we ask our real parent classloader to load the class (we give up)
+                return realParent.loadClass(name);
+            }
+        }
+    }
+
+    //List<URL> classpath
+    public ParentLastURLClassLoader(URL[] urls)
+    {
+        super(Thread.currentThread().getContextClassLoader());
+
+        //URL[] urls = classpath.toArray(new URL[classpath.size()]);
+
+        childClassLoader = new ChildURLClassLoader( urls, new FindClassClassLoader(this.getParent()) );
+    }
+
+    @Override
+    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
+    {
+        try
+        {
+            // first we try to find a class inside the child classloader
+            return childClassLoader.findClass(name);
+        }
+        catch( ClassNotFoundException e )
+        {
+            // didn't find it, try the parent
+            return super.loadClass(name, resolve);
+        }
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/ajin/ajinerp/common/util/reflection/ReflectionUtil.java (added)
+++ src/main/java/com/ajin/ajinerp/common/util/reflection/ReflectionUtil.java
@@ -0,0 +1,223 @@
+package com.ajin.ajinerp.common.util.reflection;
+
+
+import com.ajin.ajinerp.common.util.StringUtil;
+import com.ajin.ajinerp.common.util.bean.BeanUtil;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URL;
+
+/**
+ * @author 최정우
+ * @since 2020.11.25
+ * 
+ * Reflection - 클래스, 인터페이스, 메소드관련 인스턴스화(로드) 및 실행 관련 Util입니다.
+ */
+public class ReflectionUtil {
+	
+	/******************************************** Class 단 ********************************************/
+	/**
+     * @author 최정우
+	 * @since 2020.11.25 
+     *
+     * classLoad - 어플리케이션 내부(+Bean객체포함) OR 외부의 class 파일을 저장된 경로를 통해  class 파일 로드 후, 객체 생성
+     * 
+	 * classFullName과 classFilePath를 통해 실제 클래스를 인스턴스화함.
+	 * 1. Class.forName을 통해 어플리케이션 내부의 class path에 있는 class객체 생성
+	 * 2. 어플리케이션 내부에 class가 존재할 때
+	 * 		2-1. 어플리케이션의 Spring 컨테이너에 해당 클래스 타입으로 생성된 Bean이 있는지 확인
+	 * 			=> 존재할 때, Bean 객체 주입
+	 * 			=> 존재하지 않을 때, 객체 생성
+	 * 3. 어플리케이션 내부에 class가 존재하지 않을 때
+	 * 		3-1. class or jar file이 저장된 경로를 이용하여 클래스를 로드 
+	 * 
+	 * 4. 객체 생성 실패시, Log 기록 및 다음 작업 목록으로 넘어감
+	 */
+    public static Object classAndBeanLoad (String classFilePath, String classFullName) {
+    	Object clazz = null;
+    	try {
+    		//Class.forName을 통해 어플리케이션 내부의 class path에 있는 class객체 생성
+			Class<?> c = forName(classFullName);
+			if (c != null) {//어플리케이션 내부에 class가 존재할 때
+				//플리케이션의 Spring 컨테이너에 해당 클래스 타입으로 생성된 Bean이 있는지 확인 후, Bean 객체 주입
+				clazz = BeanUtil.getBean(c);
+				
+				if (clazz == null) {//Bean 객체 존재하지 않을 때
+					clazz = newInstance(c);
+				}
+			} else {//어플리케이션 내부에 class가 존재하지 않을 때
+				//class or jar file이 저장된 경로
+				//file 경로 값이 있을 때만 클래스를 로드함
+				if (StringUtil.isEmpty(classFilePath) == false) {
+					if (StringUtil.isEmpty(classFullName) == false) {
+						clazz = classLoad(classFilePath, classFullName);
+					} else {
+						clazz = classLoad(classFilePath);
+					}
+				}
+			}
+    	} catch (Exception e) {
+    		e.printStackTrace();
+    	}
+    	
+    	return clazz;
+    }
+	
+	/**
+     * @author 최정우
+	 * @since 2019.11.17
+     * 
+     * forName - 어플리케이션 내부의 class path에 있는 class객체 생성
+     * 
+     * classFullName = package명 + class명
+     */
+    public static Class<?> forName (String classFullName) {
+    	try {
+    		return Class.forName(classFullName);
+    	} catch (Exception e) {
+    		e.printStackTrace();
+    		return null;
+    	} 
+    }
+    
+    /**
+     * @author 최정우
+	 * @since 2019.11.17 
+     *
+     * classLoad - 어플리케이션 내부 OR 외부의 class 파일을 저장된 경로를 통해  class 파일 로드 후, 객체 생성
+     * 주로 외부 class파일을 로드할 때 쓰임
+     * 
+     * classFilePath - 클래스파일 절대경로
+     * classFullName = package명 + class명
+     */
+    public static Object classLoad (String classFilePath, String classFullName) {
+    	try {
+    		File file = new File(classFilePath);
+    		//실제 경로상에 있는 .class파일을 통해 class를 읽어옴
+    		ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(new URL[] { file.toURI().toURL() });
+    		//읽어온 .class파일을 이용해 해당되는  패키지 내에 있는 class를 로드해 옴
+    		Class<?> c = classLoader.loadClass(classFullName);
+    		//class를 new 객체 생성함
+    		return newInstance(c);
+    	} catch (Exception e) {
+    		e.printStackTrace();
+    		return null;
+    	}
+    }
+    
+    /**
+     * @author 최정우
+	 * @since 2019.11.17
+     * 
+     * classLoad - 어플리케이션 내부 OR 외부의 class 파일을 저장된 경로를 통해  class 파일 로드 후, 객체 생성
+     * 주로 외부 class파일을 로드할 때 쓰임
+     * class명을 모를 때 사용
+     * 
+     * classFilePath - 클래스파일 절대경로
+     */
+    public static Object classLoad (String classFilePath) {
+    	try {
+    		File file = new File(classFilePath);
+    		//실제 경로상에 있는 .class파일을 통해 class를 읽어옴
+    		ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(new URL[] { file.toURI().toURL() });
+    		//읽어온 .class파일을 이용해 해당되는  패키지 내에 있는 class를 로드해 옴
+    		Class<?> c = classLoader.getClass();
+    		//class를 new 객체 생성함
+    		return newInstance(c);
+    	} catch (Exception e) {
+    		e.printStackTrace();
+    		return null;
+    	}
+    }
+    
+    /**
+     * @author 최정우
+	 * @since 2019.11.17
+     * 
+     * newInstance - 객체 인스턴스화(로드)
+     */
+    public static Object newInstance(Class<?> c) {
+    	try {
+			return c.newInstance();
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+    }
+    
+    /******************************************** Class 단 ********************************************/
+    
+    
+    
+    /******************************************** Method 단 ********************************************/
+    /**
+     * @author 최정우
+	 * @since 2019.11.17
+     * 
+     * invokeByMethodName - 메소드 이름을 통한 메소드 호출
+     * 
+     * clazz - 인스턴스화(로드)된 객체
+     * methodName - 메소드명
+     * paramValues - 메소드의 파라메터 값
+     * paramTypes - 메소드의 파라메터 타입
+     * (주의 paramValues, paramTypes 순서가 같아야함)
+     */
+    public static Object invokeByMethodName (Object clazz, String methodName, Object[] paramValues, Class<?>[] paramTypes) {
+    	try {
+    		Method method = null;
+        	if (paramValues != null && paramTypes != null 
+        		&& paramValues.length > 0 && paramTypes.length > 0
+        		&& paramValues.length == paramTypes.length) {
+        		
+        		System.out.println("clazz getPackage : " + clazz.getClass().getPackage());
+        		System.out.println("clazz name : " + clazz.getClass().getName());
+        		if (paramValues != null && paramValues.length > 0) {
+        			System.out.println("clazz param value : " + paramValues[0].toString());
+            		System.out.println("clazz param type : " + paramTypes[0].getTypeName());
+        		}
+        		
+        		//메소드 객체 가지고오기
+        		method = clazz.getClass().getMethod(methodName, paramTypes);
+        	} else {
+        		//메소드 객체 가지고오기
+        		method = clazz.getClass().getMethod(methodName);
+        	}
+        	
+        	//메소드 호출
+    		return invoke(clazz, method, paramValues);
+        	
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+    	
+    }
+    
+    /**
+     * @author 최정우
+	 * @since 2019.11.17
+     * 
+     * invoke - 메소드 호출
+     * 
+     * clazz - 인스턴스화(로드)된 객체
+     * method - 메소드 객체
+     * paramValues - 메소드의 파라메터 값
+     */
+    public static Object invoke (Object clazz, Method method, Object[] paramValues) {
+    	try {
+        	if (paramValues != null && paramValues.length > 0) {
+        		//메소드 호출
+        		return method.invoke(clazz, paramValues);
+        	} else {
+        		//메소드 호출
+        		return method.invoke(clazz);
+        	}
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+    	
+    }
+    /******************************************** Method 단 ********************************************/
+}
 
src/main/java/com/ajin/ajinerp/common/vo/CheckMessage.java (added)
+++ src/main/java/com/ajin/ajinerp/common/vo/CheckMessage.java
@@ -0,0 +1,73 @@
+package com.ajin.ajinerp.common.vo;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author 김성원
+ * @since 2024.01.04
+ * 
+ * 데이터 체크, 유효성 검사 등과 같이 검사와 관련된 변수를 정의한 Class 입니다.
+ */
+@Getter
+@Setter
+public class CheckMessage {
+	
+	public CheckMessage() {}
+	
+	public CheckMessage(boolean isSuccess) {
+		this.isSuccess = isSuccess;
+	}
+	
+	public CheckMessage(String message) {
+		this.message = message;
+	}
+	
+	public CheckMessage(boolean isSuccess, String message) {
+		this.isSuccess = isSuccess;
+		this.message = message;
+	}
+	
+	public CheckMessage(boolean isSuccess, String message, int status) {
+		this.isSuccess = isSuccess;
+		this.message = message;
+		this.status = status;
+	}
+	
+	public CheckMessage(boolean isSuccess, String message, String error) {
+		this.isSuccess = isSuccess;
+		this.message = message;
+		this.error = error;
+	}
+	
+	/**
+	 * 체크 완료(사용가능 or 성공) 여부
+	 */
+	private boolean isSuccess;
+	
+	/**
+	 * 체크한 상태의 메세지값
+	 */
+	private String message;
+	
+	/**
+	 * 에러 메세지
+	 */
+	private String error;
+	
+	/**
+	 * 체크한 상태의 상태값 -> 상황마다 다름
+	 */
+	private int status;	
+	
+	
+	// 오류처리
+	public void setError(String message) {	
+		this.message = message;
+		this.error = message;
+		this.isSuccess = false;
+		this.status = 0;
+	}
+	
+	
+}
 
src/main/java/com/ajin/ajinerp/common/vo/CustomeResultMap.java (added)
+++ src/main/java/com/ajin/ajinerp/common/vo/CustomeResultMap.java
@@ -0,0 +1,27 @@
+package com.ajin.ajinerp.common.vo;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.http.HttpStatus;
+
+import java.util.HashMap;
+
+/**
+ * 컨트롤러의 데이터를 반환할 기본 Map클래스
+ *
+ * @author 김성원
+ * @since 2023.12.28
+ */
+@Getter
+@Setter
+public class CustomeResultMap {
+
+    // 결과메세지
+    private CheckMessage checkMessage;
+    
+    // 결과 데이터 
+    private HashMap<String,Object> resultData = new HashMap<String,Object>();
+
+    public CustomeResultMap(){
+        checkMessage = new CheckMessage(true, "성공" , 200);
+    }
+}
 
src/main/java/com/ajin/ajinerp/common/vo/SearchObject.java (added)
+++ src/main/java/com/ajin/ajinerp/common/vo/SearchObject.java
@@ -0,0 +1,43 @@
+package com.ajin.ajinerp.common.vo;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author 김성원
+ * @since 2024.01.09
+ * 
+ * 공통 검색용 VO 입니다
+ */
+@Getter
+@Setter
+public class SearchObject {
+	
+	/**
+	 * 검색 key
+	 */
+	private String key;
+
+	/**
+	 * 검색 값
+	 */
+	private Object value;
+	
+	
+	/**
+	 * 검색 key
+	 */
+	private String key2;
+
+	/**
+	 * 검색 값
+	 */
+	private Object value2;
+	
+	
+	/** 
+	 * 데이터 타입(날짜 : date, 숫자 : int, 문자 : string, 체크 : bool, 날짜쌍 : dates)
+	 */
+	private String type;
+
+	
+}
 
src/main/java/com/ajin/ajinerp/common/vo/SearchVO.java (added)
+++ src/main/java/com/ajin/ajinerp/common/vo/SearchVO.java
@@ -0,0 +1,80 @@
+package com.ajin.ajinerp.common.vo;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.sun.jna.platform.win32.Sspi;
+import com.sun.jna.platform.win32.Sspi.TimeStamp;
+import jdk.jfr.Timestamp;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author 김성원
+ * @since 2024.01.09
+ * 
+ * 공통 검색 VO 입니다
+ */
+@Getter
+@Setter
+public class SearchVO {
+	
+	/**
+	 * 검색Keyword
+	 */
+	private List<SearchObject> searchObjectList = new ArrayList<SearchObject>();
+		
+
+	/** 
+	 * 정렬 기준 (컬럼명)
+	 */
+	private String order = "";
+
+	/** 
+	 * 정렬 타입
+	 * true - ASC
+	 * false - DESC
+	 */
+	private boolean orderASC = true;
+	
+	/** 
+	 * 현재페이지
+	 */
+	private int currentPage = 1;
+
+	/** 
+	 * 페이지갯수
+	 */
+	private int perPage = 10;
+
+	/**
+	 * 총 개시물 수
+	 */
+	private int totalRows;
+
+	/**
+	 * 검색 key
+	 */
+	private String searchText;
+
+	/**
+	 * 검색 값
+	 */
+	private Object value;
+
+
+	/**
+	 * 검색 key
+	 */
+	private String key2;
+
+	/**
+	 * 검색 값
+	 */
+	private Object value2;
+
+
+	/**
+	 * 데이터 타입(날짜 : date, 숫자 : int, 문자 : string, 체크 : bool, 날짜쌍 : dates)
+	 */
+	private String type;
+}
 
src/main/java/com/ajin/ajinerp/user/member/dao/LoginDAO.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/dao/LoginDAO.java
@@ -0,0 +1,25 @@
+package com.ajin.ajinerp.user.member.dao;
+
+import com.ajin.ajinerp.user.member.vo.LoginLogVO;
+import com.ajin.ajinerp.user.member.vo.Member;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author 이세현
+ * @since 2024.03.08
+ *
+ * 로그인 처리 관련 DAO 클래스
+ */
+@Mapper
+public interface LoginDAO {
+    /**
+     * @author 이세현
+     * @since 2024.03.08
+     *
+     * 로그인 LOG정보 저장
+     */
+    public int insertLoginLog (Member member) throws Exception;
+
+    public int insertLoginLog (LoginLogVO loginLog) throws Exception;
+
+}
 
src/main/java/com/ajin/ajinerp/user/member/dao/MemberDAO.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/dao/MemberDAO.java
@@ -0,0 +1,73 @@
+package com.ajin.ajinerp.user.member.dao;
+
+import com.ajin.ajinerp.user.member.vo.Member;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author 이세현
+ * @since 2024-03-08
+ *
+ * 회원(개인) 관련된 SQL문에 접근하는 DAO Class
+ */
+@Mapper
+public interface MemberDAO {
+    /**
+     * @author 이세현
+     * @since 2024.03.08
+     *
+     * 회원 이름 조회
+     */
+    Member getNameById(String userId) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 이미지 조회
+     */
+    Member getImageById(String userId) throws Exception;
+
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 아이디로 회원(COUNT) 조회
+     */
+    int getCountById(String userId) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 조회
+     */
+    Member getMember(Member member) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 조회(COUNT)
+     */
+    int getMemberCount(Member member) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 마지막 비밀번호 변경일 조회
+     */
+    int checkPasswordExpiry(String userId) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 비밀번호 변경
+     */
+    int updatePassword(Member member) throws Exception;
+
+
+
+}
 
src/main/java/com/ajin/ajinerp/user/member/service/LoginService.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/service/LoginService.java
@@ -0,0 +1,31 @@
+package com.ajin.ajinerp.user.member.service;
+
+import com.ajin.ajinerp.common.vo.CustomeResultMap;
+import com.ajin.ajinerp.user.member.vo.Member;
+
+public interface LoginService {
+    /**
+     * @author 이세현
+     * @since 2024.03.08
+     *
+     * 유저로그인 처리
+     */
+    CustomeResultMap userLogin(Member member) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.08
+     *
+     * 세션 무효화 (로그아웃 처리)
+     */
+    CustomeResultMap userLogout() throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.11
+     *
+     * 로그인 정보 확인
+     */
+    CustomeResultMap getLoginInfo() throws Exception;
+
+}
 
src/main/java/com/ajin/ajinerp/user/member/service/MemberService.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/service/MemberService.java
@@ -0,0 +1,58 @@
+package com.ajin.ajinerp.user.member.service;
+
+
+import com.ajin.ajinerp.common.vo.CustomeResultMap;
+import com.ajin.ajinerp.user.member.vo.Member;
+
+public interface MemberService {
+
+    /**
+     * @author 이세현
+     * @since 2024.03.08
+     *
+     * 아이디로 사용자조회
+     */
+    CustomeResultMap getNameById(String userId) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.08
+     *
+     * 회원 이미지 조회
+     */
+    CustomeResultMap getImageById(String userId) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.08
+     *
+     * 아이디 비밀번호로 사용자 조회
+     */
+    int getMemberCount(Member member) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 조회 및 로그 남기기
+     */
+    CustomeResultMap getMemberCountWithLog(Member member) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 마지막 비밀번호 변경일 조회
+     */
+    CustomeResultMap checkPasswordExpiry(Member member) throws Exception;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 비밀번호 변경
+     */
+    CustomeResultMap updatePassword(Member member) throws Exception;
+
+
+}
 
src/main/java/com/ajin/ajinerp/user/member/service/impl/LoginServiceImpl.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/service/impl/LoginServiceImpl.java
@@ -0,0 +1,275 @@
+package com.ajin.ajinerp.user.member.service.impl;
+
+import com.ajin.ajinerp.common.util.*;
+import com.ajin.ajinerp.common.vo.CustomeResultMap;
+import com.ajin.ajinerp.user.member.dao.LoginDAO;
+import com.ajin.ajinerp.user.member.dao.MemberDAO;
+import com.ajin.ajinerp.user.member.service.LoginService;
+import com.ajin.ajinerp.user.member.service.MemberService;
+import com.ajin.ajinerp.user.member.vo.LoginLogVO;
+import com.ajin.ajinerp.user.member.vo.Member;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpSession;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.List;
+
+import static com.ajin.ajinerp.user.member.vo.LoginStatus.*;
+
+/**
+ * @author 이세현
+ * @since 2024.03.08
+ *
+ * 로그인 관련 처리 서비스 클래스
+ */
+
+@Service
+@RequiredArgsConstructor
+public class LoginServiceImpl implements LoginService {
+    private final MemberService memberService;
+    private final MemberDAO memberDAO;
+    private final LoginDAO loginDAO;
+
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public CustomeResultMap userLogin(Member member) {
+//        CustomeResultMap resultMap = new CustomeResultMap();
+//        LoginLogVO loginLogVO = new LoginLogVO();
+//        try {
+//            loginLogInit(loginLogVO);
+//
+//            if (member.getUserid() != null) {
+//                Member memberById = memberDAO.getMember(member);
+//                if (memberById != null) {
+//                    if (member.getNewwrd().equals(memberById.getNewwrd())) {
+//                        if(memberDAO.getMemberCount(member) != 1){
+//                            //로그인 실패
+//
+//                        }
+//                        //로그인성공 로그 + 세션저장
+//                        loginLogVO.updateLoginStatus(LOGIN_SUCCESS,member.getUserid(),member.getUsernm());
+//                        resultMap = login(member);
+//                    } else {
+//                        //로그인실패
+//                        loginLogVO.updateLoginStatus(LOGIN_FAILED_PASSWORD_NOT_MATCH,member.getUserid(),member.getUsernm());
+//                        throw new Exception("비밀번호가 틀렸습니다.");
+//                    }
+//                } else {
+//                    //아이디가 존재하지 않습니다.
+//                    loginLogVO.updateLoginStatus(ID_NOT_FOUND,member.getUserid());
+//                }
+//            } else {
+//                //아이디를 입력하세요.
+//                loginLogVO.updateLoginStatus(NO_ID_INPUT);
+//            }
+//
+//            int result = loginDAO.insertLoginLog(loginLogVO);
+//            if(result != 1){
+//                throw new Exception("로그 등록 실패입니다.");
+//            }
+//            resultMap.getResultData().put("result",loginDAO.insertLoginLog(loginLogVO));
+//        } catch(Exception e){
+//            e.printStackTrace();
+//            resultMap.getCheckMessage().setError(loginLogVO.getErrxxx());
+//        }
+//
+//        return resultMap;
+//    }
+@Override
+@Transactional(rollbackFor = Exception.class)
+public CustomeResultMap userLogin(Member member) throws Exception {
+    CustomeResultMap resultMap = new CustomeResultMap();
+    LoginLogVO loginLogVO = new LoginLogVO();
+    loginLogInit(loginLogVO);
+
+    try {
+        if (member.getUserid() == null) {
+            loginLogVO.updateLoginStatus(NO_ID_INPUT);
+            // 로그 기록
+            loginDAO.insertLoginLog(loginLogVO);
+
+            resultMap.getCheckMessage().setError("아이디를 입력하세요.");
+            return resultMap;
+        }
+
+        Member memberById = memberDAO.getNameById(member.getUserid());
+        if (memberById == null) {
+            loginLogVO.updateLoginStatus(ID_NOT_FOUND, member.getUserid());
+            // 로그 기록
+            loginDAO.insertLoginLog(loginLogVO);
+
+            resultMap.getCheckMessage().setError("아이디가 존재하지 않습니다.");
+            return resultMap;
+        }
+
+        if (memberDAO.getMemberCount(member) != 1) {
+            loginLogVO.updateLoginStatus(LOGIN_FAILED_PASSWORD_NOT_MATCH, member.getUserid(), member.getUsernm());
+            // 로그 기록
+            loginDAO.insertLoginLog(loginLogVO);
+
+            resultMap.getCheckMessage().setError("비밀번호가 틀렸습니다.");
+            return resultMap;
+        }
+
+        // 로그인 성공 로그 + 세션 저장
+        loginLogVO.updateLoginStatus(LOGIN_SUCCESS, member.getUserid(), member.getUsernm());
+        resultMap = login(member);
+
+        // 여기서도 로그 기록
+        loginDAO.insertLoginLog(loginLogVO);
+    } catch (Exception e) {
+        e.printStackTrace();
+        resultMap.getCheckMessage().setError("로그 등록에 실패했습니다.");
+    }
+    return resultMap;
+}
+
+    private void loginLogInit(LoginLogVO loginLogVO){
+        HttpServletRequest request = CommonUtil.getHttpServletRequest();
+        String ip = null;
+        ip = request.getHeader("X-Forwarded-For");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_CLIENT_IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Real-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-RealIP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("REMOTE_ADDR");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+
+        loginLogVO.setIpaddr(ip);
+        loginLogVO.setTmlreg(System.getenv("COMPUTERNAME"));
+    }
+
+    /**
+     * @author 김성원
+     * @since 2024.01.09
+     *
+     * 유저로그인 처리(userId, userPassword)
+     */
+    public CustomeResultMap login(Member member) throws Exception {
+
+        // 결과맵 생성
+        CustomeResultMap resultMap = new CustomeResultMap();
+
+        //사용자 정보 조회
+        Member memberInfo = memberDAO.getMember(member);
+
+        // 사용자 정보 있음
+        if(memberInfo != null && !StringUtil.isEmpty(memberInfo.getUserid())) {
+
+            /** 로그인 처리이후, 세션처리 (시작) **/
+            //세션 무효화 처리
+            userLogout();
+
+            //현재 로그인할 userId와 동일한 로그인 session을 가진 session ID 목록 조회
+            List<String> sessionIds = SesssionEventListener.duplicationLoginSessionIdSelectListByUserId(member.getUserid());
+
+            /**
+             * 현재 로그인 요청한 userId와 동일한 접속 session이 1명 이상 있고,
+             * 중복 로그인 가능 유무가 true이면 -> 중복 로그인 처리 진행
+             * 중복 로그인 가능 유무가 false이면 -> 기존 로그인 유저를 session에서 제거
+             */
+            if (sessionIds.size() > 0 && AuthUtil.IS_POSSIBLE_DUPLICATION_LOGIN == true) {
+                //기존 로그인 유저 session에서 제거
+                SesssionEventListener.duplicationLoginSessionDeleteByUserId(member.getUserid());
+            }
+
+            //현재 Http Request 객체 가지고 오기
+            HttpServletRequest request = CommonUtil.getHttpServletRequest();
+
+            //기존 session이 존재하더라도 제거하고 새로운 세션 생성
+            HttpSession session = request.getSession(true);
+
+            //세션 timeout(30분)
+            session.setMaxInactiveInterval(60 * 30);
+
+            // session 저장 정보 생성
+            HashMap<String, Object> LoginUserInfo = new HashMap<>();
+            LoginUserInfo.put("userid", memberInfo.getUserid());
+            LoginUserInfo.put("prjcod", memberInfo.getPrjcod());
+            LoginUserInfo.put("kangub", memberInfo.getKangub());
+            LoginUserInfo.put("usernm", memberInfo.getUsernm());
+            LoginUserInfo.put("compcd", memberInfo.getCompcd());
+            // session 생성
+            session.setAttribute(AuthUtil.LOGIN_USER_SESSION, LoginUserInfo);
+            resultMap.getResultData().put("LoginUserInfo", LoginUserInfo);
+            resultMap.getCheckMessage().setMessage(memberInfo.getUserid() + "님 환영합니다.");
+
+            // 성공로직 아래에 만들기
+//                loginDAO.loginSuccess(member);
+
+
+        }else {
+            resultMap.getCheckMessage().setError("일치하는 정보가 없습니다.<br />사용자 정보를 확인해 주세요");
+        }
+        return resultMap;
+    }
+
+
+    /**
+     * @author 이세현
+     * @since 2024.03.11
+     *
+     * 세션 무효화 (로그아웃 처리)
+     */
+    @Override
+    public CustomeResultMap userLogout() throws Exception {
+        //현재 Http Request 객체 가지고 오기
+        HttpServletRequest request = CommonUtil.getHttpServletRequest();
+        //기존 session이 존재하면 session값을 받아오고, 존재하지 않으면 null
+        HttpSession session = request.getSession(false);
+        //기존 session이 존재하면 -> session 삭제
+        if (session != null) {
+            //session 삭제
+            session.invalidate();
+        }
+        CustomeResultMap map = new CustomeResultMap();
+        return map;
+    }
+
+
+    /**
+     * @author 이세현
+     * @since 2024.03.11
+     *
+     * 로그인 정보 확인
+     */
+    @Override
+    public CustomeResultMap getLoginInfo() throws Exception {
+        // 결과맵 생성
+        CustomeResultMap resultMap = new CustomeResultMap();
+        //현재 Http Request 객체 가지고 오기
+        HttpServletRequest request = CommonUtil.getHttpServletRequest();
+        //기존 session이 존재하면 session값을 받아오고, 존재하지 않으면 null
+        HttpSession session = request.getSession(false);
+        // 세션 받아오기
+        if(session != null &&  session.getAttribute(AuthUtil.LOGIN_USER_SESSION) != null) {
+            HashMap<String, Object> LoginUserInfo = (HashMap<String, Object>)session.getAttribute(AuthUtil.LOGIN_USER_SESSION);
+            resultMap.getResultData().put("LoginUserInfo", LoginUserInfo);
+        }else {
+            resultMap.getCheckMessage().setStatus(0);
+        }
+        return resultMap;
+    }
+
+}
 
src/main/java/com/ajin/ajinerp/user/member/service/impl/MemberServiceImpl.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/service/impl/MemberServiceImpl.java
@@ -0,0 +1,217 @@
+package com.ajin.ajinerp.user.member.service.impl;
+
+import com.ajin.ajinerp.common.util.CommonUtil;
+import com.ajin.ajinerp.common.vo.CheckMessage;
+import com.ajin.ajinerp.common.vo.CustomeResultMap;
+import com.ajin.ajinerp.user.member.dao.LoginDAO;
+import com.ajin.ajinerp.user.member.dao.MemberDAO;
+import com.ajin.ajinerp.user.member.service.MemberService;
+import com.ajin.ajinerp.user.member.vo.LoginLogVO;
+import com.ajin.ajinerp.user.member.vo.Member;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Base64;
+
+import static com.ajin.ajinerp.user.member.vo.LoginStatus.*;
+
+/**
+ * @author 이세현
+ * @since 2024.03.08
+ *
+ * 사용자 관련 처리 서비스 로직
+ */
+
+@Service
+@RequiredArgsConstructor
+public class MemberServiceImpl implements MemberService {
+    private final MemberDAO memberDAO;
+    private final LoginDAO loginDAO;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 아이디로 이름 조회
+     */
+    @Override
+    public CustomeResultMap getNameById(String userid) throws Exception {
+        Member member = memberDAO.getNameById(userid);
+        CustomeResultMap resultMap = new CustomeResultMap();
+        resultMap.getResultData().put("member",member);
+        return resultMap;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 아이디로 이미지 조회
+     */
+    @Override
+    public CustomeResultMap getImageById(String userid) throws Exception {
+        userid = (userid == null || userid.isEmpty()) ? "ADMIN" : userid;
+        Member member = memberDAO.getImageById(userid);
+        if( member == null || member.getImagex() == null || member.getImagex().length == 0) {
+            memberDAO.getImageById("ADMIN");
+        }
+        if( member != null && member.getImagex() != null){
+            member.setBase64Image(encodeFileToBase64(member.getImagex()));
+        }
+        CustomeResultMap resultMap = new CustomeResultMap();
+        resultMap.getResultData().put("member",member);
+        return resultMap;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 바이너리 데이터 -> base64 인코딩
+     */
+    private String encodeFileToBase64(byte[] imagex) {
+        return Base64.getEncoder().encodeToString(imagex);
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 조회(COUNT)
+     */
+    @Override
+    public int getMemberCount(Member member) throws Exception {
+        return memberDAO.getMemberCount(member);
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 마지막 비밀번호 변경일 조회
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public CustomeResultMap checkPasswordExpiry(Member member) throws Exception {
+        CustomeResultMap resultMap = new CustomeResultMap();
+        if (getMemberCount(member) == 1) {
+            resultMap.getResultData().put("haveToChangePassword", memberDAO.checkPasswordExpiry(member.getUserid()) == 1);
+        } else {
+            resultMap.getCheckMessage().setError("비밀번호가 일치하지 않습니다.");
+        }
+        return resultMap;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 비밀번호 변경
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public CustomeResultMap updatePassword(Member member) throws Exception {
+        CustomeResultMap resultMap = new CustomeResultMap();
+
+        Member getMember = memberDAO.getMember(member);
+        if(getMember == null ||getMember.getUserid().isEmpty() || !member.getUserid().equals(getMember.getUserid())){
+            resultMap.getCheckMessage().setMessage("비밀번호가 일치하지 않습니다.");
+        }
+        member.setNewwrd(member.getChangeWrd());
+        member.setOldwrd(getMember.getNewwrd());
+        member.setIduptx(member.getUserid());
+        member.setTmlupt(System.getenv("COMPUTERNAME"));
+
+        resultMap.getResultData().put("result", memberDAO.updatePassword(member) == 1);
+        return resultMap;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 조회 및 로그 남기기
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public CustomeResultMap getMemberCountWithLog(Member member) throws Exception {
+        CustomeResultMap resultMap = new CustomeResultMap();
+        LoginLogVO loginLogVO = new LoginLogVO();
+        int result = 0;
+        loginLogInit(loginLogVO);
+
+        if (member.getUserid().isEmpty()) {
+            loginLogVO.updateLoginStatus(NO_ID_INPUT);
+        } else {
+            processLoginAttempt(member, loginLogVO);
+        }
+
+        result = loginDAO.insertLoginLog(loginLogVO);
+        if (result != 1) {
+            resultMap.getCheckMessage().setError("로그 저장에 실패하였습니다.");
+        }
+        resultMap.getResultData().put("result", memberDAO.getMemberCount(member) == 1);
+        return resultMap;
+    }
+    
+    //로그 남기기
+    private void processLoginAttempt(Member member, LoginLogVO loginLogVO) throws Exception {
+        String requestURL = member.getRequestURL();
+        int countById = memberDAO.getCountById(member.getUserid());
+
+        if(countById != 1){
+            loginLogVO.updateLoginStatus(ID_NOT_FOUND, member.getUserid());
+            return;
+        }
+
+        boolean isPasswordMatch = memberDAO.getMemberCount(member) == 1;
+        if("IMAGECHANGE".equals(requestURL)){
+            if(isPasswordMatch){
+                loginLogVO.updateLoginStatus(IMG_CHANGE_CONNECTION_SUCCESS, member.getUserid(), member.getUsernm());
+            } else {
+                loginLogVO.updateLoginStatus(LOGIN_FAILED_PASSWORD_NOT_MATCH, member.getUserid(), member.getUsernm());
+            }
+        } else if("PASSWORDCHANGE".equals(requestURL)){
+            if(isPasswordMatch){
+                loginLogVO.updateLoginStatus(PW_CHANGE_CONNECTION_SUCCESS, member.getUserid(), member.getUsernm());
+            } else {
+                loginLogVO.updateLoginStatus(PW_CHANGE_FAILED_PASSWORD_NOT_MATCH, member.getUserid(), member.getUsernm());
+            }
+        }
+    }
+
+    private void loginLogInit(LoginLogVO loginLogVO){
+        HttpServletRequest request = CommonUtil.getHttpServletRequest();
+        String ip = null;
+        ip = request.getHeader("X-Forwarded-For");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_CLIENT_IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Real-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-RealIP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("REMOTE_ADDR");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+
+        loginLogVO.setIpaddr(ip);
+        loginLogVO.setTmlreg(System.getenv("COMPUTERNAME"));
+    }
+}
 
src/main/java/com/ajin/ajinerp/user/member/vo/LoginLogVO.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/vo/LoginLogVO.java
@@ -0,0 +1,48 @@
+package com.ajin.ajinerp.user.member.vo;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author 이세현
+ * @since 2024.03.08
+ *
+ * 로그인 로그 VO
+ */
+@Getter @Setter
+public class LoginLogVO {
+    //접속자ID
+    private String userid;
+    //접속자명
+    private String usernm;
+    //성공여부
+    private String contgu;
+    //접속내용
+    private String errxxx;
+    //접속단말IP
+    private String ipaddr;
+    //접속단말명
+    private String tmlreg;
+    //MAC address
+    private String macadd;
+    //접속일자(YYYYMMDD)
+    private String sysdat;
+    //접속시간(HHMMSS)
+    private String systim;
+
+    public void updateLoginStatus(LoginStatus status, String userid, String usernm) {
+        this.userid = userid;
+        this.usernm = usernm;
+        updateLoginStatus(status);
+    }
+
+    public void updateLoginStatus(LoginStatus status, String userid) {
+        this.userid = userid;
+        updateLoginStatus(status);
+    }
+
+    public void updateLoginStatus(LoginStatus status) {
+        this.contgu = status.getContgu();
+        this.errxxx = status.getErrxxx();
+    }
+}
 
src/main/java/com/ajin/ajinerp/user/member/vo/LoginStatus.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/vo/LoginStatus.java
@@ -0,0 +1,24 @@
+package com.ajin.ajinerp.user.member.vo;
+
+import lombok.Getter;
+
+@Getter
+public enum LoginStatus {
+    LOGIN_SUCCESS("Y", "LOGIN 연결 성공!!!"),
+    NO_ID_INPUT("N", "ID를 입력하세요."),
+    ID_NOT_FOUND("N", "해당 ID가 존재하지 않습니다."),
+    PW_CHANGE_FAILED_PASSWORD_NOT_MATCH("N", "PW Change 화면 연결 실패!!! Password가 일치하지 않습니다."),
+    IMG_CHANGE_CONNECTION_SUCCESS("Y", "이미지 변경 선택 성공!!!"),
+    LOGIN_FAILED_PASSWORD_NOT_MATCH("N", "LOGIN 연결 실패!!! Password가 일치하지 않습니다."),
+    PW_CHANGE_CONNECTION_SUCCESS("Y", "PW Change 화면 연결 성공!!!");
+
+    //성공여부
+    private final String contgu;
+    //접속내역
+    private final String errxxx;
+
+    LoginStatus(String contgu, String errxxx) {
+        this.contgu = contgu;
+        this.errxxx = errxxx;
+    }
+}
 
src/main/java/com/ajin/ajinerp/user/member/vo/Member.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/vo/Member.java
@@ -0,0 +1,55 @@
+package com.ajin.ajinerp.user.member.vo;
+
+import com.ajin.ajinerp.common.util.CryptoUtil;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author 이세현
+ * @since 2024.03.08
+ *
+ * 회원정보 VO
+ */
+
+@Getter @Setter
+public class Member {
+    //사용자ID(사원번호)
+    private String userid;
+    //사용자명
+    private String usernm;
+    //구 비밀번호
+    private String oldwrd;
+    //새 비밀번호
+    private String newwrd;
+    //비밀번호 변경일
+    private String pw_ymd;
+    //업무구분
+    private String prjcod;
+    //사업장코드
+    private String compcd;
+    //부서코드
+    private String buscod;
+    //권한(0.총괄관리자, 1.일반관리자, 2.총괄사용자, 3.일반사용자)
+    private Integer kangub;
+    //사용여부(Y/N)
+    private String saygub;
+    //로그인 성공여부
+    private String loginResult;
+    //로그 작성시 사용할 메세지
+    private String errorMsg;
+    //이미지(Long Raw)
+    private byte[] imagex;
+    //이미지(BASE64)
+    private String base64Image;
+    //갱신자id
+    private String iduptx;
+    //갱신일시
+    private String dtmupt;
+    //갱신단말명
+    private String tmlupt;
+    //요청URL
+    private String requestURL;
+    //변경할 비밀번호
+    private String changeWrd;
+
+}
 
src/main/java/com/ajin/ajinerp/user/member/web/LoginController.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/web/LoginController.java
@@ -0,0 +1,54 @@
+package com.ajin.ajinerp.user.member.web;
+
+import com.ajin.ajinerp.common.vo.CustomeResultMap;
+import com.ajin.ajinerp.user.member.service.LoginService;
+import com.ajin.ajinerp.user.member.vo.Member;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class LoginController {
+    private final LoginService loginService;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.11
+     *
+     * 로그인
+     */
+    @PostMapping(value = "/login.json")
+    public CustomeResultMap login(@RequestBody Member member) throws Exception {
+        CustomeResultMap map = loginService.userLogin(member);
+        return map;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.11
+     *
+     * 로그아웃
+     */
+    @PostMapping(value = "/logout.json")
+    public CustomeResultMap logout() throws Exception {
+        CustomeResultMap map = loginService.userLogout();
+        return map;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.11
+     *
+     * 로그인 정보 조회
+     */
+    @PostMapping(value = "/getLoginInfo.json")
+    public CustomeResultMap getLoginInfo() throws Exception {
+        CustomeResultMap map = loginService.getLoginInfo();
+        return map;
+    }
+
+
+
+}
 
src/main/java/com/ajin/ajinerp/user/member/web/MemberController.java (added)
+++ src/main/java/com/ajin/ajinerp/user/member/web/MemberController.java
@@ -0,0 +1,63 @@
+package com.ajin.ajinerp.user.member.web;
+
+import com.ajin.ajinerp.common.vo.CustomeResultMap;
+import com.ajin.ajinerp.user.member.service.MemberService;
+import com.ajin.ajinerp.user.member.vo.Member;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequiredArgsConstructor
+public class MemberController {
+    private final MemberService memberService;
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 아이디로 이름, 이미지 조회
+     */
+    @PostMapping(value = "/getMemberById.json")
+    public CustomeResultMap getMemberById(@RequestBody Member member) throws Exception {
+        CustomeResultMap map = new CustomeResultMap();
+        map.getResultData().put("getNameById",memberService.getNameById(member.getUserid()));
+        map.getResultData().put("getImageById",memberService.getImageById(member.getUserid()));
+        return map;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 사용자 마지막 비밀번호 변경날짜 확인
+     */
+    @PostMapping(value = "/checkPasswordExpiry.json")
+    public CustomeResultMap checkPasswordExpiry(@RequestBody Member member) throws Exception {
+        CustomeResultMap map = memberService.checkPasswordExpiry(member);
+        return map;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 사용자 비밀번호 변경
+     */
+    @PostMapping(value = "/updatePassword.json")
+    public CustomeResultMap updatePassword(@RequestBody Member member) throws Exception {
+        CustomeResultMap map = memberService.updatePassword(member);
+        return map;
+    }
+
+    /**
+     * @author 이세현
+     * @since 2024.03.10
+     *
+     * 회원 조회
+     */
+    @PostMapping(value = "/getUserInfo.json")
+    public CustomeResultMap getUserInfo(@RequestBody Member member) throws Exception {
+        CustomeResultMap map = memberService.getMemberCountWithLog(member);
+        return map;
+    }
+}
src/main/resources/application.properties
--- src/main/resources/application.properties
+++ src/main/resources/application.properties
@@ -4,10 +4,14 @@
 #Datasource Configuration
 spring.datasource.hikari.maximum-pool-size=4
 spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
-spring.datasource.url=jdbc:log4jdbc:postgresql://210.180.118.83:5432/bi_manager?currentSchema=bi_manager
-spring.datasource.username=takensoft
-spring.datasource.password=tts96314728!@
-spring.sql.init.platform=postgres
+#spring.datasource.url=jdbc:log4jdbc:postgresql://210.180.118.83:5432/ajin_test?currentSchema=ajin_test
+#spring.datasource.username=takensoft
+#spring.datasource.password=tts96314728!@
+#spring.sql.init.platform=postgres
+spring.datasource.url=jdbc:log4jdbc:oracle:thin:@192.168.0.17:1521/xe
+spring.datasource.username=c##takensoft
+spring.datasource.password=123456
+spring.sql.init.platform=oracle
 
 
 # Mapper Xml Location
@@ -15,14 +19,14 @@
 mybatis.config-location=classpath:/spring/mapper/mybatis-config.xml
 
 
-# 업로드 파일 용량 설정
+# ���ε� ���� �뷮 ����
 spring.servlet.multipart.maxFileSize=200MB
 spring.servlet.multipart.maxRequestSize=200MB
-##catalina log 설정
+##catalina log ����
 #logging.level.org.apache.catalina=INFO
 #logging.file.name=logs/catalina.out
 #
-##access log 설정
+##access log ����
 #server.tomcat.basedir=./
 #server.tomcat.accesslog.enabled=true
 #server.tomcat.accesslog.directory=logs/access
(파일 끝에 줄바꿈 문자 없음)
src/main/resources/spring/mapper/mybatis-config.xml
--- src/main/resources/spring/mapper/mybatis-config.xml
+++ src/main/resources/spring/mapper/mybatis-config.xml
@@ -14,7 +14,10 @@
     </settings>
 
     <typeAliases>
-
+        <!-- 회원정보 객체 -->
+        <typeAlias type="com.ajin.ajinerp.user.member.vo.Member" alias="Member"/>
+        <!-- 로그인로그 객체 -->
+        <typeAlias type="com.ajin.ajinerp.user.member.vo.LoginLogVO" alias="LoginLogVO"/>
     </typeAliases>
 
 </configuration>
(파일 끝에 줄바꿈 문자 없음)
 
src/main/resources/spring/mapper/user/login-SQL.xml (added)
+++ src/main/resources/spring/mapper/user/login-SQL.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+
+<mapper namespace="com.ajin.ajinerp.user.member.dao.LoginDAO">
+	<!-- 로그인 매퍼 -->
+	<resultMap id="loginLog" type="LoginLogVO">
+		<result property="userid" column="USERID" />
+		<result property="usernm" column="USERNM" />
+		<result property="contgu" column="CONTGU" />
+		<result property="errxxx" column="ERRXXX" />
+		<result property="ipaddr" column="IPADDR" />
+		<result property="tmlreg" column="TMLREG" />
+		<result property="macadd" column="MACADD" />
+		<result property="sysdat" column="SYSDAT" />
+		<result property="systim" column="SYSTIM" />
+	</resultMap>
+
+	<!-- 2.로그인 로그 -->
+	<insert id="insertLoginLog" parameterType="LoginLogVO">
+		INSERT INTO USLOGXXT
+			(
+			 USERID,
+			 USERNM,
+			 CONTGU,
+			 ERRXXX,
+			 IPADDR,
+			 TMLREG,
+			 SYSDAT,
+			 SYSTIM)
+		VALUES
+			(#{userid},
+			 #{usernm},
+			 #{contgu},
+			 #{errxxx},
+			 #{ipaddr},
+			 #{tmlreg},
+    		TO_CHAR(SYSDATE, 'YYYYMMDD'), -- 현재 날짜를 YYYYMMDD 포맷으로 변환
+     		TO_CHAR(SYSDATE, 'HH24MISS')) -- 현재 시간을 HH24MISS 포맷으로 변환
+	</insert>
+</mapper>(파일 끝에 줄바꿈 문자 없음)
 
src/main/resources/spring/mapper/user/member-SQL.xml (added)
+++ src/main/resources/spring/mapper/user/member-SQL.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+
+<mapper namespace="com.ajin.ajinerp.user.member.dao.MemberDAO">
+
+	<!-- 사용자 매퍼 -->
+	<resultMap id="memberResult" type="Member">
+		<result property="userid" column="USERID" />
+		<result property="usernm" column="USERNM" />
+		<result property="oldwrd" column="OLDWRD" />
+		<result property="newwrd" column="NEWWRD" />
+		<result property="pw_ymd" column="PW_YMD" />
+		<result property="prjcod" column="PRJCOD" />
+		<result property="compcd" column="COMPCD" />
+		<result property="buscod" column="BUSCOD" />
+		<result property="kangub" column="KANGUB" />
+		<result property="saygub" column="SAYGUB" />
+		<result property="iduptx" column="IDUPTX" />
+		<result property="dtmupt" column="DTMUPT" />
+		<result property="tmlupt" column="TMLUPT" />
+		<result property="imagex" column="IMAGEX" />
+	</resultMap>
+
+	<!-- 1.아이디로 이름 조회-->
+	<select id="getNameById" parameterType="String" resultMap="memberResult">
+			SELECT NVL(USERID, '') AS USERID
+			, NVL(USERNM, '') AS USERNM
+			FROM US01001T
+			WHERE USERID = #{userid}
+			AND SAYGUB = 'Y'
+	</select>
+
+	<!-- 2. 아이디로 회원 조회 COUNT-->
+	<select id="getCountById" parameterType="String" resultType="int">
+		SELECT COUNT(*)
+		FROM US01001T
+		WHERE USERID = #{userid}
+		AND SAYGUB = 'Y'
+	</select>
+
+	<!-- 2.아이디로 이미지 조회	-->
+	<select id="getImageById" parameterType="String" resultMap="memberResult">
+		SELECT IMAGEX
+		FROM US01003T
+		WHERE USERID = #{userid}
+	</select>
+
+	<!-- 3.사용자 이미지 변경-->
+	<update id="updateUserImage" parameterType="String">
+		UPDATE US01003T
+		SET IMAGEX = #{imagex}
+		WHERE USERID = #{userid}
+	</update>
+
+	<!-- 4.사용자 마지막 비밀번호 변경 날짜 조회 -->
+	<select id="checkPasswordExpiry" parameterType="String" resultType="int">
+		SELECT COUNT(*) FROM US01001T WHERE USERID = #{userid} AND MONTHS_BETWEEN(SYSDATE, TO_DATE(PW_YMD, 'YYYYMMDD')) >= 2
+	</select>
+
+	<!-- 5.사용자 비밀번호 변경	-->
+	<update id="updatePassword" parameterType="Member">
+		UPDATE US01001T
+		SET OLDWRD = #{oldwrd}
+		, 	NEWWRD = #{newwrd}
+		,	IDUPTX = #{userid}
+		,	DTMUPT = SYSDATE
+		,	TMLUPT = #{tmlupt}
+		WHERE USERID = #{userid}
+	</update>
+
+	<!-- 6.아이디 비밀번호로 회원 조회 -->
+	<select id="getMember" parameterType="Member" resultMap="memberResult">
+		SELECT NVL(USERID, '') AS USERID
+			 , NVL(USERNM, '') AS USERNM
+			 , NVL(OLDWRD, '') AS OLDWRD
+	 		 , NVL(NEWWRD, '') AS NEWWRD
+			 , NVL(PW_YMD, '') AS PW_YMD
+			 , NVL(PRJCOD, '') AS PRJCOD
+			 , NVL(COMPCD, '') AS COMPCD
+			 , NVL(KANGUB, '') AS KANGUB
+			 , NVL(SAYGUB, '') AS SAYGUB
+		FROM US01001T
+		WHERE USERID = #{userid}
+		AND NEWWRD = #{newwrd}
+		AND SAYGUB = 'Y'
+	</select>
+
+	<!-- 7.아이디 비밀번호로 회원 조회 COUNT	-->
+	<select id="getMemberCount" parameterType="Member" resultType="int">
+		SELECT COUNT(*)
+		FROM US01001T
+		WHERE USERID = #{userid}
+		AND NEWWRD = #{newwrd}
+		AND SAYGUB = 'Y'
+	</select>
+</mapper>(파일 끝에 줄바꿈 문자 없음)
Add a comment
List